Black Lives Matter. Support the Equal Justice Initiative.

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

View as plain text