1  
     2  
     3  
     4  
     5  
     6  package clean
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/fs"
    14  	"os"
    15  	"path/filepath"
    16  	"runtime"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"cmd/go/internal/base"
    22  	"cmd/go/internal/cache"
    23  	"cmd/go/internal/cfg"
    24  	"cmd/go/internal/load"
    25  	"cmd/go/internal/lockedfile"
    26  	"cmd/go/internal/modfetch"
    27  	"cmd/go/internal/modload"
    28  	"cmd/go/internal/str"
    29  	"cmd/go/internal/work"
    30  )
    31  
    32  var CmdClean = &base.Command{
    33  	UsageLine: "go clean [-i] [-r] [-cache] [-testcache] [-modcache] [-fuzzcache] [build flags] [packages]",
    34  	Short:     "remove object files and cached files",
    35  	Long: `
    36  Clean removes object files from package source directories.
    37  The go command builds most objects in a temporary directory,
    38  so go clean is mainly concerned with object files left by other
    39  tools or by manual invocations of go build.
    40  
    41  If a package argument is given or the -i or -r flag is set,
    42  clean removes the following files from each of the
    43  source directories corresponding to the import paths:
    44  
    45  	_obj/            old object directory, left from Makefiles
    46  	_test/           old test directory, left from Makefiles
    47  	_testmain.go     old gotest file, left from Makefiles
    48  	test.out         old test log, left from Makefiles
    49  	build.out        old test log, left from Makefiles
    50  	*.[568ao]        object files, left from Makefiles
    51  
    52  	DIR(.exe)        from go build
    53  	DIR.test(.exe)   from go test -c
    54  	MAINFILE(.exe)   from go build MAINFILE.go
    55  	*.so             from SWIG
    56  
    57  In the list, DIR represents the final path element of the
    58  directory, and MAINFILE is the base name of any Go source
    59  file in the directory that is not included when building
    60  the package.
    61  
    62  The -i flag causes clean to remove the corresponding installed
    63  archive or binary (what 'go install' would create).
    64  
    65  The -n flag causes clean to print the remove commands it would execute,
    66  but not run them.
    67  
    68  The -r flag causes clean to be applied recursively to all the
    69  dependencies of the packages named by the import paths.
    70  
    71  The -x flag causes clean to print remove commands as it executes them.
    72  
    73  The -cache flag causes clean to remove the entire go build cache.
    74  
    75  The -testcache flag causes clean to expire all test results in the
    76  go build cache.
    77  
    78  The -modcache flag causes clean to remove the entire module
    79  download cache, including unpacked source code of versioned
    80  dependencies.
    81  
    82  The -fuzzcache flag causes clean to remove files stored in the Go build
    83  cache for fuzz testing. The fuzzing engine caches files that expand
    84  code coverage, so removing them may make fuzzing less effective until
    85  new inputs are found that provide the same coverage. These files are
    86  distinct from those stored in testdata directory; clean does not remove
    87  those files.
    88  
    89  For more about build flags, see 'go help build'.
    90  
    91  For more about specifying packages, see 'go help packages'.
    92  	`,
    93  }
    94  
    95  var (
    96  	cleanI         bool 
    97  	cleanR         bool 
    98  	cleanCache     bool 
    99  	cleanFuzzcache bool 
   100  	cleanModcache  bool 
   101  	cleanTestcache bool 
   102  )
   103  
   104  func init() {
   105  	
   106  	CmdClean.Run = runClean
   107  
   108  	CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
   109  	CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
   110  	CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
   111  	CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "")
   112  	CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
   113  	CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
   114  
   115  	
   116  	
   117  	
   118  
   119  	work.AddBuildFlags(CmdClean, work.OmitBuildOnlyFlags)
   120  }
   121  
   122  func runClean(ctx context.Context, cmd *base.Command, args []string) {
   123  	moduleLoaderState := modload.NewState()
   124  	moduleLoaderState.InitWorkfile()
   125  	if len(args) > 0 {
   126  		cacheFlag := ""
   127  		switch {
   128  		case cleanCache:
   129  			cacheFlag = "-cache"
   130  		case cleanTestcache:
   131  			cacheFlag = "-testcache"
   132  		case cleanFuzzcache:
   133  			cacheFlag = "-fuzzcache"
   134  		case cleanModcache:
   135  			cacheFlag = "-modcache"
   136  		}
   137  		if cacheFlag != "" {
   138  			base.Fatalf("go: clean %s cannot be used with package arguments", cacheFlag)
   139  		}
   140  	}
   141  
   142  	
   143  	
   144  	
   145  	cleanPkg := len(args) > 0 || cleanI || cleanR
   146  	if (!moduleLoaderState.Enabled() || moduleLoaderState.HasModRoot()) &&
   147  		!cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
   148  		cleanPkg = true
   149  	}
   150  
   151  	if cleanPkg {
   152  		for _, pkg := range load.PackagesAndErrors(moduleLoaderState, ctx, load.PackageOpts{}, args) {
   153  			clean(pkg)
   154  		}
   155  	}
   156  
   157  	sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
   158  
   159  	if cleanCache {
   160  		dir, _, err := cache.DefaultDir()
   161  		if err != nil {
   162  			base.Fatal(err)
   163  		}
   164  		if dir != "off" {
   165  			
   166  			
   167  			
   168  			
   169  			subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
   170  			printedErrors := false
   171  			if len(subdirs) > 0 {
   172  				if err := sh.RemoveAll(subdirs...); err != nil && !printedErrors {
   173  					printedErrors = true
   174  					base.Error(err)
   175  				}
   176  			}
   177  
   178  			logFile := filepath.Join(dir, "log.txt")
   179  			if err := sh.RemoveAll(logFile); err != nil && !printedErrors {
   180  				printedErrors = true
   181  				base.Error(err)
   182  			}
   183  		}
   184  	}
   185  
   186  	if cleanTestcache && !cleanCache {
   187  		
   188  		
   189  		
   190  		dir, _, err := cache.DefaultDir()
   191  		if err != nil {
   192  			base.Fatal(err)
   193  		}
   194  		if dir != "off" {
   195  			f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
   196  			if err == nil {
   197  				now := time.Now().UnixNano()
   198  				buf, _ := io.ReadAll(f)
   199  				prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
   200  				if now > prev {
   201  					if err = f.Truncate(0); err == nil {
   202  						if _, err = f.Seek(0, 0); err == nil {
   203  							_, err = fmt.Fprintf(f, "%d\n", now)
   204  						}
   205  					}
   206  				}
   207  				if closeErr := f.Close(); err == nil {
   208  					err = closeErr
   209  				}
   210  			}
   211  			if err != nil {
   212  				if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
   213  					base.Error(err)
   214  				}
   215  			}
   216  		}
   217  	}
   218  
   219  	if cleanModcache {
   220  		if cfg.GOMODCACHE == "" {
   221  			base.Fatalf("go: cannot clean -modcache without a module cache")
   222  		}
   223  		if cfg.BuildN || cfg.BuildX {
   224  			sh.ShowCmd("", "rm -rf %s", cfg.GOMODCACHE)
   225  		}
   226  		if !cfg.BuildN {
   227  			if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
   228  				base.Error(err)
   229  
   230  				
   231  				
   232  				
   233  				
   234  				
   235  				if runtime.GOOS == "openbsd" && errors.Is(err, fs.ErrExist) {
   236  					logFilesInGOMODCACHE()
   237  				}
   238  			}
   239  		}
   240  	}
   241  
   242  	if cleanFuzzcache {
   243  		fuzzDir := cache.Default().FuzzDir()
   244  		if err := sh.RemoveAll(fuzzDir); err != nil {
   245  			base.Error(err)
   246  		}
   247  	}
   248  }
   249  
   250  
   251  func logFilesInGOMODCACHE() {
   252  	var found []string
   253  	werr := filepath.WalkDir(cfg.GOMODCACHE, func(path string, d fs.DirEntry, err error) error {
   254  		if err != nil {
   255  			return err
   256  		}
   257  		var mode string
   258  		info, err := d.Info()
   259  		if err == nil {
   260  			mode = info.Mode().String()
   261  		} else {
   262  			mode = fmt.Sprintf("<err: %s>", info.Mode())
   263  		}
   264  		found = append(found, fmt.Sprintf("%s (mode: %s)", path, mode))
   265  		return nil
   266  	})
   267  	if werr != nil {
   268  		base.Errorf("walking files in GOMODCACHE (for debugging go.dev/issue/68087): %v", werr)
   269  	}
   270  	base.Errorf("files in GOMODCACHE (for debugging go.dev/issue/68087):\n%s", strings.Join(found, "\n"))
   271  }
   272  
   273  var cleaned = map[*load.Package]bool{}
   274  
   275  
   276  
   277  var cleanDir = map[string]bool{
   278  	"_test": true,
   279  	"_obj":  true,
   280  }
   281  
   282  var cleanFile = map[string]bool{
   283  	"_testmain.go": true,
   284  	"test.out":     true,
   285  	"build.out":    true,
   286  	"a.out":        true,
   287  }
   288  
   289  var cleanExt = map[string]bool{
   290  	".5":  true,
   291  	".6":  true,
   292  	".8":  true,
   293  	".a":  true,
   294  	".o":  true,
   295  	".so": true,
   296  }
   297  
   298  func clean(p *load.Package) {
   299  	if cleaned[p] {
   300  		return
   301  	}
   302  	cleaned[p] = true
   303  
   304  	if p.Dir == "" {
   305  		base.Errorf("%v", p.Error)
   306  		return
   307  	}
   308  	dirs, err := os.ReadDir(p.Dir)
   309  	if err != nil {
   310  		base.Errorf("go: %s: %v", p.Dir, err)
   311  		return
   312  	}
   313  
   314  	sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
   315  
   316  	packageFile := map[string]bool{}
   317  	if p.Name != "main" {
   318  		
   319  		
   320  		keep := func(list []string) {
   321  			for _, f := range list {
   322  				packageFile[f] = true
   323  			}
   324  		}
   325  		keep(p.GoFiles)
   326  		keep(p.CgoFiles)
   327  		keep(p.TestGoFiles)
   328  		keep(p.XTestGoFiles)
   329  	}
   330  
   331  	_, elem := filepath.Split(p.Dir)
   332  	var allRemove []string
   333  
   334  	
   335  	if p.Name == "main" {
   336  		allRemove = append(allRemove,
   337  			elem,
   338  			elem+".exe",
   339  			p.DefaultExecName(),
   340  			p.DefaultExecName()+".exe",
   341  		)
   342  	}
   343  
   344  	
   345  	allRemove = append(allRemove,
   346  		elem+".test",
   347  		elem+".test.exe",
   348  		p.DefaultExecName()+".test",
   349  		p.DefaultExecName()+".test.exe",
   350  	)
   351  
   352  	
   353  	
   354  	for _, dir := range dirs {
   355  		name := dir.Name()
   356  		if packageFile[name] {
   357  			continue
   358  		}
   359  
   360  		if dir.IsDir() {
   361  			continue
   362  		}
   363  
   364  		if base, found := strings.CutSuffix(name, "_test.go"); found {
   365  			allRemove = append(allRemove, base+".test", base+".test.exe")
   366  		}
   367  
   368  		if base, found := strings.CutSuffix(name, ".go"); found {
   369  			
   370  			
   371  			
   372  			allRemove = append(allRemove, base, base+".exe")
   373  		}
   374  	}
   375  
   376  	if cfg.BuildN || cfg.BuildX {
   377  		sh.ShowCmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
   378  	}
   379  
   380  	toRemove := map[string]bool{}
   381  	for _, name := range allRemove {
   382  		toRemove[name] = true
   383  	}
   384  	for _, dir := range dirs {
   385  		name := dir.Name()
   386  		if dir.IsDir() {
   387  			
   388  			if cleanDir[name] {
   389  				if err := sh.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
   390  					base.Error(err)
   391  				}
   392  			}
   393  			continue
   394  		}
   395  
   396  		if cfg.BuildN {
   397  			continue
   398  		}
   399  
   400  		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
   401  			removeFile(filepath.Join(p.Dir, name))
   402  		}
   403  	}
   404  
   405  	if cleanI && p.Target != "" {
   406  		if cfg.BuildN || cfg.BuildX {
   407  			sh.ShowCmd("", "rm -f %s", p.Target)
   408  		}
   409  		if !cfg.BuildN {
   410  			removeFile(p.Target)
   411  		}
   412  	}
   413  
   414  	if cleanR {
   415  		for _, p1 := range p.Internal.Imports {
   416  			clean(p1)
   417  		}
   418  	}
   419  }
   420  
   421  
   422  
   423  func removeFile(f string) {
   424  	err := os.Remove(f)
   425  	if err == nil || os.IsNotExist(err) {
   426  		return
   427  	}
   428  	
   429  	if runtime.GOOS == "windows" {
   430  		
   431  		if _, err2 := os.Stat(f + "~"); err2 == nil {
   432  			os.Remove(f + "~")
   433  		}
   434  		
   435  		
   436  		
   437  		if err2 := os.Rename(f, f+"~"); err2 == nil {
   438  			os.Remove(f + "~")
   439  			return
   440  		}
   441  	}
   442  	base.Error(err)
   443  }
   444  
View as plain text