Home | History | Annotate | Download | only in 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 	"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 }
    309