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