github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/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/bir3/gocompiler/src/cmd/gocmd/internal/base"
    20  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/cache"
    21  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg"
    22  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/load"
    23  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/lockedfile"
    24  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch"
    25  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/modload"
    26  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/str"
    27  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/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  	var b work.Builder
   154  	b.Print = fmt.Print
   155  
   156  	if cleanCache {
   157  		dir := cache.DefaultDir()
   158  		if dir != "off" {
   159  			// Remove the cache subdirectories but not the top cache directory.
   160  			// The top cache directory may have been created with special permissions
   161  			// and not something that we want to remove. Also, we'd like to preserve
   162  			// the access log for future analysis, even if the cache is cleared.
   163  			subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
   164  			printedErrors := false
   165  			if len(subdirs) > 0 {
   166  				if cfg.BuildN || cfg.BuildX {
   167  					b.Showcmd("", "rm -r %s", strings.Join(subdirs, " "))
   168  				}
   169  				if !cfg.BuildN {
   170  					for _, d := range subdirs {
   171  						// Only print the first error - there may be many.
   172  						// This also mimics what os.RemoveAll(dir) would do.
   173  						if err := os.RemoveAll(d); err != nil && !printedErrors {
   174  							printedErrors = true
   175  							base.Errorf("go: %v", err)
   176  						}
   177  					}
   178  				}
   179  			}
   180  
   181  			logFile := filepath.Join(dir, "log.txt")
   182  			if cfg.BuildN || cfg.BuildX {
   183  				b.Showcmd("", "rm -f %s", logFile)
   184  			}
   185  			if !cfg.BuildN {
   186  				if err := os.RemoveAll(logFile); err != nil && !printedErrors {
   187  					printedErrors = true
   188  					base.Errorf("go: %v", err)
   189  				}
   190  			}
   191  		}
   192  	}
   193  
   194  	if cleanTestcache && !cleanCache {
   195  		// Instead of walking through the entire cache looking for test results,
   196  		// we write a file to the cache indicating that all test results from before
   197  		// right now are to be ignored.
   198  		dir := cache.DefaultDir()
   199  		if dir != "off" {
   200  			f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
   201  			if err == nil {
   202  				now := time.Now().UnixNano()
   203  				buf, _ := io.ReadAll(f)
   204  				prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
   205  				if now > prev {
   206  					if err = f.Truncate(0); err == nil {
   207  						if _, err = f.Seek(0, 0); err == nil {
   208  							_, err = fmt.Fprintf(f, "%d\n", now)
   209  						}
   210  					}
   211  				}
   212  				if closeErr := f.Close(); err == nil {
   213  					err = closeErr
   214  				}
   215  			}
   216  			if err != nil {
   217  				if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
   218  					base.Errorf("go: %v", err)
   219  				}
   220  			}
   221  		}
   222  	}
   223  
   224  	if cleanModcache {
   225  		if cfg.GOMODCACHE == "" {
   226  			base.Fatalf("go: cannot clean -modcache without a module cache")
   227  		}
   228  		if cfg.BuildN || cfg.BuildX {
   229  			b.Showcmd("", "rm -rf %s", cfg.GOMODCACHE)
   230  		}
   231  		if !cfg.BuildN {
   232  			if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
   233  				base.Errorf("go: %v", err)
   234  			}
   235  		}
   236  	}
   237  
   238  	if cleanFuzzcache {
   239  		fuzzDir := cache.Default().FuzzDir()
   240  		if cfg.BuildN || cfg.BuildX {
   241  			b.Showcmd("", "rm -rf %s", fuzzDir)
   242  		}
   243  		if !cfg.BuildN {
   244  			if err := os.RemoveAll(fuzzDir); err != nil {
   245  				base.Errorf("go: %v", err)
   246  			}
   247  		}
   248  	}
   249  }
   250  
   251  var cleaned = map[*load.Package]bool{}
   252  
   253  // TODO: These are dregs left by Makefile-based builds.
   254  // Eventually, can stop deleting these.
   255  var cleanDir = map[string]bool{
   256  	"_test": true,
   257  	"_obj":  true,
   258  }
   259  
   260  var cleanFile = map[string]bool{
   261  	"_testmain.go": true,
   262  	"test.out":     true,
   263  	"build.out":    true,
   264  	"a.out":        true,
   265  }
   266  
   267  var cleanExt = map[string]bool{
   268  	".5":  true,
   269  	".6":  true,
   270  	".8":  true,
   271  	".a":  true,
   272  	".o":  true,
   273  	".so": true,
   274  }
   275  
   276  func clean(p *load.Package) {
   277  	if cleaned[p] {
   278  		return
   279  	}
   280  	cleaned[p] = true
   281  
   282  	if p.Dir == "" {
   283  		base.Errorf("%v", p.Error)
   284  		return
   285  	}
   286  	dirs, err := os.ReadDir(p.Dir)
   287  	if err != nil {
   288  		base.Errorf("go: %s: %v", p.Dir, err)
   289  		return
   290  	}
   291  
   292  	var b work.Builder
   293  	b.Print = fmt.Print
   294  
   295  	packageFile := map[string]bool{}
   296  	if p.Name != "main" {
   297  		// Record which files are not in package main.
   298  		// The others are.
   299  		keep := func(list []string) {
   300  			for _, f := range list {
   301  				packageFile[f] = true
   302  			}
   303  		}
   304  		keep(p.GoFiles)
   305  		keep(p.CgoFiles)
   306  		keep(p.TestGoFiles)
   307  		keep(p.XTestGoFiles)
   308  	}
   309  
   310  	_, elem := filepath.Split(p.Dir)
   311  	var allRemove []string
   312  
   313  	// Remove dir-named executable only if this is package main.
   314  	if p.Name == "main" {
   315  		allRemove = append(allRemove,
   316  			elem,
   317  			elem+".exe",
   318  			p.DefaultExecName(),
   319  			p.DefaultExecName()+".exe",
   320  		)
   321  	}
   322  
   323  	// Remove package test executables.
   324  	allRemove = append(allRemove,
   325  		elem+".test",
   326  		elem+".test.exe",
   327  		p.DefaultExecName()+".test",
   328  		p.DefaultExecName()+".test.exe",
   329  	)
   330  
   331  	// Remove a potential executable, test executable for each .go file in the directory that
   332  	// is not part of the directory's package.
   333  	for _, dir := range dirs {
   334  		name := dir.Name()
   335  		if packageFile[name] {
   336  			continue
   337  		}
   338  
   339  		if dir.IsDir() {
   340  			continue
   341  		}
   342  
   343  		if base, found := strings.CutSuffix(name, "_test.go"); found {
   344  			allRemove = append(allRemove, base+".test", base+".test.exe")
   345  		}
   346  
   347  		if base, found := strings.CutSuffix(name, ".go"); found {
   348  			// TODO(adg,rsc): check that this .go file is actually
   349  			// in "package main", and therefore capable of building
   350  			// to an executable file.
   351  			allRemove = append(allRemove, base, base+".exe")
   352  		}
   353  	}
   354  
   355  	if cfg.BuildN || cfg.BuildX {
   356  		b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
   357  	}
   358  
   359  	toRemove := map[string]bool{}
   360  	for _, name := range allRemove {
   361  		toRemove[name] = true
   362  	}
   363  	for _, dir := range dirs {
   364  		name := dir.Name()
   365  		if dir.IsDir() {
   366  			// TODO: Remove once Makefiles are forgotten.
   367  			if cleanDir[name] {
   368  				if cfg.BuildN || cfg.BuildX {
   369  					b.Showcmd(p.Dir, "rm -r %s", name)
   370  					if cfg.BuildN {
   371  						continue
   372  					}
   373  				}
   374  				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
   375  					base.Errorf("go: %v", err)
   376  				}
   377  			}
   378  			continue
   379  		}
   380  
   381  		if cfg.BuildN {
   382  			continue
   383  		}
   384  
   385  		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
   386  			removeFile(filepath.Join(p.Dir, name))
   387  		}
   388  	}
   389  
   390  	if cleanI && p.Target != "" {
   391  		if cfg.BuildN || cfg.BuildX {
   392  			b.Showcmd("", "rm -f %s", p.Target)
   393  		}
   394  		if !cfg.BuildN {
   395  			removeFile(p.Target)
   396  		}
   397  	}
   398  
   399  	if cleanR {
   400  		for _, p1 := range p.Internal.Imports {
   401  			clean(p1)
   402  		}
   403  	}
   404  }
   405  
   406  // removeFile tries to remove file f, if error other than file doesn't exist
   407  // occurs, it will report the error.
   408  func removeFile(f string) {
   409  	err := os.Remove(f)
   410  	if err == nil || os.IsNotExist(err) {
   411  		return
   412  	}
   413  	// Windows does not allow deletion of a binary file while it is executing.
   414  	if runtime.GOOS == "windows" {
   415  		// Remove lingering ~ file from last attempt.
   416  		if _, err2 := os.Stat(f + "~"); err2 == nil {
   417  			os.Remove(f + "~")
   418  		}
   419  		// Try to move it out of the way. If the move fails,
   420  		// which is likely, we'll try again the
   421  		// next time we do an install of this binary.
   422  		if err2 := os.Rename(f, f+"~"); err2 == nil {
   423  			os.Remove(f + "~")
   424  			return
   425  		}
   426  	}
   427  	base.Errorf("go: %v", err)
   428  }