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