Source file src/cmd/go/internal/clean/clean.go

Documentation: cmd/go/internal/clean

     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  	"cmd/go/internal/base"
    18  	"cmd/go/internal/cache"
    19  	"cmd/go/internal/cfg"
    20  	"cmd/go/internal/load"
    21  	"cmd/go/internal/lockedfile"
    22  	"cmd/go/internal/modfetch"
    23  	"cmd/go/internal/modload"
    24  	"cmd/go/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)
   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  				base.Errorf("go clean -testcache: %v", err)
   182  			}
   183  		}
   184  	}
   185  
   186  	if cleanModcache {
   187  		if modfetch.PkgMod == "" {
   188  			base.Fatalf("go clean -modcache: no module cache")
   189  		}
   190  		if cfg.BuildN || cfg.BuildX {
   191  			b.Showcmd("", "rm -rf %s", modfetch.PkgMod)
   192  		}
   193  		if !cfg.BuildN {
   194  			if err := modfetch.RemoveAll(modfetch.PkgMod); err != nil {
   195  				base.Errorf("go clean -modcache: %v", err)
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  var cleaned = map[*load.Package]bool{}
   202  
   203  // TODO: These are dregs left by Makefile-based builds.
   204  // Eventually, can stop deleting these.
   205  var cleanDir = map[string]bool{
   206  	"_test": true,
   207  	"_obj":  true,
   208  }
   209  
   210  var cleanFile = map[string]bool{
   211  	"_testmain.go": true,
   212  	"test.out":     true,
   213  	"build.out":    true,
   214  	"a.out":        true,
   215  }
   216  
   217  var cleanExt = map[string]bool{
   218  	".5":  true,
   219  	".6":  true,
   220  	".8":  true,
   221  	".a":  true,
   222  	".o":  true,
   223  	".so": true,
   224  }
   225  
   226  func clean(p *load.Package) {
   227  	if cleaned[p] {
   228  		return
   229  	}
   230  	cleaned[p] = true
   231  
   232  	if p.Dir == "" {
   233  		base.Errorf("can't load package: %v", p.Error)
   234  		return
   235  	}
   236  	dirs, err := ioutil.ReadDir(p.Dir)
   237  	if err != nil {
   238  		base.Errorf("go clean %s: %v", p.Dir, err)
   239  		return
   240  	}
   241  
   242  	var b work.Builder
   243  	b.Print = fmt.Print
   244  
   245  	packageFile := map[string]bool{}
   246  	if p.Name != "main" {
   247  		// Record which files are not in package main.
   248  		// The others are.
   249  		keep := func(list []string) {
   250  			for _, f := range list {
   251  				packageFile[f] = true
   252  			}
   253  		}
   254  		keep(p.GoFiles)
   255  		keep(p.CgoFiles)
   256  		keep(p.TestGoFiles)
   257  		keep(p.XTestGoFiles)
   258  	}
   259  
   260  	_, elem := filepath.Split(p.Dir)
   261  	var allRemove []string
   262  
   263  	// Remove dir-named executable only if this is package main.
   264  	if p.Name == "main" {
   265  		allRemove = append(allRemove,
   266  			elem,
   267  			elem+".exe",
   268  		)
   269  	}
   270  
   271  	// Remove package test executables.
   272  	allRemove = append(allRemove,
   273  		elem+".test",
   274  		elem+".test.exe",
   275  	)
   276  
   277  	// Remove a potential executable for each .go file in the directory that
   278  	// is not part of the directory's package.
   279  	for _, dir := range dirs {
   280  		name := dir.Name()
   281  		if packageFile[name] {
   282  			continue
   283  		}
   284  		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
   285  			// TODO(adg,rsc): check that this .go file is actually
   286  			// in "package main", and therefore capable of building
   287  			// to an executable file.
   288  			base := name[:len(name)-len(".go")]
   289  			allRemove = append(allRemove, base, base+".exe")
   290  		}
   291  	}
   292  
   293  	if cfg.BuildN || cfg.BuildX {
   294  		b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
   295  	}
   296  
   297  	toRemove := map[string]bool{}
   298  	for _, name := range allRemove {
   299  		toRemove[name] = true
   300  	}
   301  	for _, dir := range dirs {
   302  		name := dir.Name()
   303  		if dir.IsDir() {
   304  			// TODO: Remove once Makefiles are forgotten.
   305  			if cleanDir[name] {
   306  				if cfg.BuildN || cfg.BuildX {
   307  					b.Showcmd(p.Dir, "rm -r %s", name)
   308  					if cfg.BuildN {
   309  						continue
   310  					}
   311  				}
   312  				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
   313  					base.Errorf("go clean: %v", err)
   314  				}
   315  			}
   316  			continue
   317  		}
   318  
   319  		if cfg.BuildN {
   320  			continue
   321  		}
   322  
   323  		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
   324  			removeFile(filepath.Join(p.Dir, name))
   325  		}
   326  	}
   327  
   328  	if cleanI && p.Target != "" {
   329  		if cfg.BuildN || cfg.BuildX {
   330  			b.Showcmd("", "rm -f %s", p.Target)
   331  		}
   332  		if !cfg.BuildN {
   333  			removeFile(p.Target)
   334  		}
   335  	}
   336  
   337  	if cleanR {
   338  		for _, p1 := range p.Internal.Imports {
   339  			clean(p1)
   340  		}
   341  	}
   342  }
   343  
   344  // removeFile tries to remove file f, if error other than file doesn't exist
   345  // occurs, it will report the error.
   346  func removeFile(f string) {
   347  	err := os.Remove(f)
   348  	if err == nil || os.IsNotExist(err) {
   349  		return
   350  	}
   351  	// Windows does not allow deletion of a binary file while it is executing.
   352  	if base.ToolIsWindows {
   353  		// Remove lingering ~ file from last attempt.
   354  		if _, err2 := os.Stat(f + "~"); err2 == nil {
   355  			os.Remove(f + "~")
   356  		}
   357  		// Try to move it out of the way. If the move fails,
   358  		// which is likely, we'll try again the
   359  		// next time we do an install of this binary.
   360  		if err2 := os.Rename(f, f+"~"); err2 == nil {
   361  			os.Remove(f + "~")
   362  			return
   363  		}
   364  	}
   365  	base.Errorf("go clean: %v", err)
   366  }
   367  

View as plain text