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