...

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

     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