github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/clean/clean.go (about)

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