...

Source file src/cmd/go/internal/clean/clean.go

Documentation: cmd/go/internal/clean

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package clean implements the ``go clean'' command.
     6  package clean
     7  
     8  import (
     9  	"fmt"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"cmd/go/internal/base"
    18  	"cmd/go/internal/cache"
    19  	"cmd/go/internal/cfg"
    20  	"cmd/go/internal/load"
    21  	"cmd/go/internal/lockedfile"
    22  	"cmd/go/internal/modfetch"
    23  	"cmd/go/internal/modload"
    24  	"cmd/go/internal/work"
    25  )
    26  
    27  var CmdClean = &base.Command{
    28  	UsageLine: "go clean [clean flags] [build flags] [packages]",
    29  	Short:     "remove object files and cached files",
    30  	Long: `
    31  Clean removes object files from package source directories.
    32  The go command builds most objects in a temporary directory,
    33  so go clean is mainly concerned with object files left by other
    34  tools or by manual invocations of go build.
    35  
    36  Specifically, clean removes the following files from each of the
    37  source directories corresponding to the import paths:
    38  
    39  	_obj/            old object directory, left from Makefiles
    40  	_test/           old test directory, left from Makefiles
    41  	_testmain.go     old gotest file, left from Makefiles
    42  	test.out         old test log, left from Makefiles
    43  	build.out        old test log, left from Makefiles
    44  	*.[568ao]        object files, left from Makefiles
    45  
    46  	DIR(.exe)        from go build
    47  	DIR.test(.exe)   from go test -c
    48  	MAINFILE(.exe)   from go build MAINFILE.go
    49  	*.so             from SWIG
    50  
    51  In the list, DIR represents the final path element of the
    52  directory, and MAINFILE is the base name of any Go source
    53  file in the directory that is not included when building
    54  the package.
    55  
    56  The -i flag causes clean to remove the corresponding installed
    57  archive or binary (what 'go install' would create).
    58  
    59  The -n flag causes clean to print the remove commands it would execute,
    60  but not run them.
    61  
    62  The -r flag causes clean to be applied recursively to all the
    63  dependencies of the packages named by the import paths.
    64  
    65  The -x flag causes clean to print remove commands as it executes them.
    66  
    67  The -cache flag causes clean to remove the entire go build cache.
    68  
    69  The -testcache flag causes clean to expire all test results in the
    70  go build cache.
    71  
    72  The -modcache flag causes clean to remove the entire module
    73  download cache, including unpacked source code of versioned
    74  dependencies.
    75  
    76  For more about build flags, see 'go help build'.
    77  
    78  For more about specifying packages, see 'go help packages'.
    79  	`,
    80  }
    81  
    82  var (
    83  	cleanI         bool // clean -i flag
    84  	cleanR         bool // clean -r flag
    85  	cleanCache     bool // clean -cache flag
    86  	cleanModcache  bool // clean -modcache flag
    87  	cleanTestcache bool // clean -testcache flag
    88  )
    89  
    90  func init() {
    91  	// break init cycle
    92  	CmdClean.Run = runClean
    93  
    94  	CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
    95  	CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
    96  	CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
    97  	CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
    98  	CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
    99  
   100  	// -n and -x are important enough to be
   101  	// mentioned explicitly in the docs but they
   102  	// are part of the build flags.
   103  
   104  	work.AddBuildFlags(CmdClean)
   105  }
   106  
   107  func runClean(cmd *base.Command, args []string) {
   108  	if len(args) > 0 || !modload.Enabled() || modload.HasModRoot() {
   109  		for _, pkg := range load.PackagesAndErrors(args) {
   110  			clean(pkg)
   111  		}
   112  	}
   113  
   114  	var b work.Builder
   115  	b.Print = fmt.Print
   116  
   117  	if cleanCache {
   118  		dir := cache.DefaultDir()
   119  		if dir != "off" {
   120  			// Remove the cache subdirectories but not the top cache directory.
   121  			// The top cache directory may have been created with special permissions
   122  			// and not something that we want to remove. Also, we'd like to preserve
   123  			// the access log for future analysis, even if the cache is cleared.
   124  			subdirs, _ := filepath.Glob(filepath.Join(dir, "[0-9a-f][0-9a-f]"))
   125  			if len(subdirs) > 0 {
   126  				if cfg.BuildN || cfg.BuildX {
   127  					b.Showcmd("", "rm -r %s", strings.Join(subdirs, " "))
   128  				}
   129  				printedErrors := false
   130  				for _, d := range subdirs {
   131  					// Only print the first error - there may be many.
   132  					// This also mimics what os.RemoveAll(dir) would do.
   133  					if err := os.RemoveAll(d); err != nil && !printedErrors {
   134  						printedErrors = true
   135  						base.Errorf("go clean -cache: %v", err)
   136  					}
   137  				}
   138  			}
   139  		}
   140  	}
   141  
   142  	if cleanTestcache && !cleanCache {
   143  		// Instead of walking through the entire cache looking for test results,
   144  		// we write a file to the cache indicating that all test results from before
   145  		// right now are to be ignored.
   146  		dir := cache.DefaultDir()
   147  		if dir != "off" {
   148  			f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
   149  			if err == nil {
   150  				now := time.Now().UnixNano()
   151  				buf, _ := ioutil.ReadAll(f)
   152  				prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
   153  				if now > prev {
   154  					if err = f.Truncate(0); err == nil {
   155  						if _, err = f.Seek(0, 0); err == nil {
   156  							_, err = fmt.Fprintf(f, "%d\n", now)
   157  						}
   158  					}
   159  				}
   160  				if closeErr := f.Close(); err == nil {
   161  					err = closeErr
   162  				}
   163  			}
   164  			if err != nil {
   165  				base.Errorf("go clean -testcache: %v", err)
   166  			}
   167  		}
   168  	}
   169  
   170  	if cleanModcache {
   171  		if modfetch.PkgMod == "" {
   172  			base.Fatalf("go clean -modcache: no module cache")
   173  		}
   174  		if cfg.BuildN || cfg.BuildX {
   175  			b.Showcmd("", "rm -rf %s", modfetch.PkgMod)
   176  		}
   177  		if !cfg.BuildN {
   178  			if err := modfetch.RemoveAll(modfetch.PkgMod); err != nil {
   179  				base.Errorf("go clean -modcache: %v", err)
   180  			}
   181  		}
   182  	}
   183  }
   184  
   185  var cleaned = map[*load.Package]bool{}
   186  
   187  // TODO: These are dregs left by Makefile-based builds.
   188  // Eventually, can stop deleting these.
   189  var cleanDir = map[string]bool{
   190  	"_test": true,
   191  	"_obj":  true,
   192  }
   193  
   194  var cleanFile = map[string]bool{
   195  	"_testmain.go": true,
   196  	"test.out":     true,
   197  	"build.out":    true,
   198  	"a.out":        true,
   199  }
   200  
   201  var cleanExt = map[string]bool{
   202  	".5":  true,
   203  	".6":  true,
   204  	".8":  true,
   205  	".a":  true,
   206  	".o":  true,
   207  	".so": true,
   208  }
   209  
   210  func clean(p *load.Package) {
   211  	if cleaned[p] {
   212  		return
   213  	}
   214  	cleaned[p] = true
   215  
   216  	if p.Dir == "" {
   217  		base.Errorf("can't load package: %v", p.Error)
   218  		return
   219  	}
   220  	dirs, err := ioutil.ReadDir(p.Dir)
   221  	if err != nil {
   222  		base.Errorf("go clean %s: %v", p.Dir, err)
   223  		return
   224  	}
   225  
   226  	var b work.Builder
   227  	b.Print = fmt.Print
   228  
   229  	packageFile := map[string]bool{}
   230  	if p.Name != "main" {
   231  		// Record which files are not in package main.
   232  		// The others are.
   233  		keep := func(list []string) {
   234  			for _, f := range list {
   235  				packageFile[f] = true
   236  			}
   237  		}
   238  		keep(p.GoFiles)
   239  		keep(p.CgoFiles)
   240  		keep(p.TestGoFiles)
   241  		keep(p.XTestGoFiles)
   242  	}
   243  
   244  	_, elem := filepath.Split(p.Dir)
   245  	var allRemove []string
   246  
   247  	// Remove dir-named executable only if this is package main.
   248  	if p.Name == "main" {
   249  		allRemove = append(allRemove,
   250  			elem,
   251  			elem+".exe",
   252  		)
   253  	}
   254  
   255  	// Remove package test executables.
   256  	allRemove = append(allRemove,
   257  		elem+".test",
   258  		elem+".test.exe",
   259  	)
   260  
   261  	// Remove a potential executable for each .go file in the directory that
   262  	// is not part of the directory's package.
   263  	for _, dir := range dirs {
   264  		name := dir.Name()
   265  		if packageFile[name] {
   266  			continue
   267  		}
   268  		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
   269  			// TODO(adg,rsc): check that this .go file is actually
   270  			// in "package main", and therefore capable of building
   271  			// to an executable file.
   272  			base := name[:len(name)-len(".go")]
   273  			allRemove = append(allRemove, base, base+".exe")
   274  		}
   275  	}
   276  
   277  	if cfg.BuildN || cfg.BuildX {
   278  		b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
   279  	}
   280  
   281  	toRemove := map[string]bool{}
   282  	for _, name := range allRemove {
   283  		toRemove[name] = true
   284  	}
   285  	for _, dir := range dirs {
   286  		name := dir.Name()
   287  		if dir.IsDir() {
   288  			// TODO: Remove once Makefiles are forgotten.
   289  			if cleanDir[name] {
   290  				if cfg.BuildN || cfg.BuildX {
   291  					b.Showcmd(p.Dir, "rm -r %s", name)
   292  					if cfg.BuildN {
   293  						continue
   294  					}
   295  				}
   296  				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
   297  					base.Errorf("go clean: %v", err)
   298  				}
   299  			}
   300  			continue
   301  		}
   302  
   303  		if cfg.BuildN {
   304  			continue
   305  		}
   306  
   307  		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
   308  			removeFile(filepath.Join(p.Dir, name))
   309  		}
   310  	}
   311  
   312  	if cleanI && p.Target != "" {
   313  		if cfg.BuildN || cfg.BuildX {
   314  			b.Showcmd("", "rm -f %s", p.Target)
   315  		}
   316  		if !cfg.BuildN {
   317  			removeFile(p.Target)
   318  		}
   319  	}
   320  
   321  	if cleanR {
   322  		for _, p1 := range p.Internal.Imports {
   323  			clean(p1)
   324  		}
   325  	}
   326  }
   327  
   328  // removeFile tries to remove file f, if error other than file doesn't exist
   329  // occurs, it will report the error.
   330  func removeFile(f string) {
   331  	err := os.Remove(f)
   332  	if err == nil || os.IsNotExist(err) {
   333  		return
   334  	}
   335  	// Windows does not allow deletion of a binary file while it is executing.
   336  	if base.ToolIsWindows {
   337  		// Remove lingering ~ file from last attempt.
   338  		if _, err2 := os.Stat(f + "~"); err2 == nil {
   339  			os.Remove(f + "~")
   340  		}
   341  		// Try to move it out of the way. If the move fails,
   342  		// which is likely, we'll try again the
   343  		// next time we do an install of this binary.
   344  		if err2 := os.Rename(f, f+"~"); err2 == nil {
   345  			os.Remove(f + "~")
   346  			return
   347  		}
   348  	}
   349  	base.Errorf("go clean: %v", err)
   350  }
   351  

View as plain text