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. 6package clean 7 8import ( 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 27var CmdClean = &base.Command{ 28 UsageLine: "go clean [clean flags] [build flags] [packages]", 29 Short: "remove object files and cached files", 30 Long: ` 31Clean removes object files from package source directories. 32The go command builds most objects in a temporary directory, 33so go clean is mainly concerned with object files left by other 34tools or by manual invocations of go build. 35 36Specifically, clean removes the following files from each of the 37source directories corresponding to the import paths: 38 39 _obj/ old object directory, left from Makefiles 40 _test/ old test directory, left from Makefiles 41 _testmain.go old gotest file, left from Makefiles 42 test.out old test log, left from Makefiles 43 build.out old test log, left from Makefiles 44 *.[568ao] object files, left from Makefiles 45 46 DIR(.exe) from go build 47 DIR.test(.exe) from go test -c 48 MAINFILE(.exe) from go build MAINFILE.go 49 *.so from SWIG 50 51In the list, DIR represents the final path element of the 52directory, and MAINFILE is the base name of any Go source 53file in the directory that is not included when building 54the package. 55 56The -i flag causes clean to remove the corresponding installed 57archive or binary (what 'go install' would create). 58 59The -n flag causes clean to print the remove commands it would execute, 60but not run them. 61 62The -r flag causes clean to be applied recursively to all the 63dependencies of the packages named by the import paths. 64 65The -x flag causes clean to print remove commands as it executes them. 66 67The -cache flag causes clean to remove the entire go build cache. 68 69The -testcache flag causes clean to expire all test results in the 70go build cache. 71 72The -modcache flag causes clean to remove the entire module 73download cache, including unpacked source code of versioned 74dependencies. 75 76For more about build flags, see 'go help build'. 77 78For more about specifying packages, see 'go help packages'. 79 `, 80} 81 82var ( 83 cleanI bool // clean -i flag 84 cleanR bool // clean -r flag 85 cleanCache bool // clean -cache flag 86 cleanModcache bool // clean -modcache flag 87 cleanTestcache bool // clean -testcache flag 88) 89 90func init() { 91 // break init cycle 92 CmdClean.Run = runClean 93 94 CmdClean.Flag.BoolVar(&cleanI, "i", false, "") 95 CmdClean.Flag.BoolVar(&cleanR, "r", false, "") 96 CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "") 97 CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "") 98 CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "") 99 100 // -n and -x are important enough to be 101 // mentioned explicitly in the docs but they 102 // are part of the build flags. 103 104 work.AddBuildFlags(CmdClean) 105} 106 107func runClean(cmd *base.Command, args []string) { 108 if len(args) > 0 || !modload.Enabled() || modload.HasModRoot() { 109 for _, pkg := range load.PackagesAndErrors(args) { 110 clean(pkg) 111 } 112 } 113 114 var b work.Builder 115 b.Print = fmt.Print 116 117 if cleanCache { 118 dir := cache.DefaultDir() 119 if dir != "off" { 120 // Remove the cache subdirectories but not the top cache directory. 121 // The top cache directory may have been created with special permissions 122 // and not something that we want to remove. Also, we'd like to preserve 123 // the access log for future analysis, even if the cache is cleared. 124 subdirs, _ := filepath.Glob(filepath.Join(dir, "[0-9a-f][0-9a-f]")) 125 if len(subdirs) > 0 { 126 if cfg.BuildN || cfg.BuildX { 127 b.Showcmd("", "rm -r %s", strings.Join(subdirs, " ")) 128 } 129 printedErrors := false 130 for _, d := range subdirs { 131 // Only print the first error - there may be many. 132 // This also mimics what os.RemoveAll(dir) would do. 133 if err := os.RemoveAll(d); err != nil && !printedErrors { 134 printedErrors = true 135 base.Errorf("go clean -cache: %v", err) 136 } 137 } 138 } 139 } 140 } 141 142 if cleanTestcache && !cleanCache { 143 // Instead of walking through the entire cache looking for test results, 144 // we write a file to the cache indicating that all test results from before 145 // right now are to be ignored. 146 dir := cache.DefaultDir() 147 if dir != "off" { 148 f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt")) 149 if err == nil { 150 now := time.Now().UnixNano() 151 buf, _ := ioutil.ReadAll(f) 152 prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64) 153 if now > prev { 154 if err = f.Truncate(0); err == nil { 155 if _, err = f.Seek(0, 0); err == nil { 156 _, err = fmt.Fprintf(f, "%d\n", now) 157 } 158 } 159 } 160 if closeErr := f.Close(); err == nil { 161 err = closeErr 162 } 163 } 164 if err != nil { 165 base.Errorf("go clean -testcache: %v", err) 166 } 167 } 168 } 169 170 if cleanModcache { 171 if modfetch.PkgMod == "" { 172 base.Fatalf("go clean -modcache: no module cache") 173 } 174 if cfg.BuildN || cfg.BuildX { 175 b.Showcmd("", "rm -rf %s", modfetch.PkgMod) 176 } 177 if !cfg.BuildN { 178 if err := modfetch.RemoveAll(modfetch.PkgMod); err != nil { 179 base.Errorf("go clean -modcache: %v", err) 180 } 181 } 182 } 183} 184 185var cleaned = map[*load.Package]bool{} 186 187// TODO: These are dregs left by Makefile-based builds. 188// Eventually, can stop deleting these. 189var cleanDir = map[string]bool{ 190 "_test": true, 191 "_obj": true, 192} 193 194var cleanFile = map[string]bool{ 195 "_testmain.go": true, 196 "test.out": true, 197 "build.out": true, 198 "a.out": true, 199} 200 201var cleanExt = map[string]bool{ 202 ".5": true, 203 ".6": true, 204 ".8": true, 205 ".a": true, 206 ".o": true, 207 ".so": true, 208} 209 210func 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. 330func 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} 351