github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/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 "context" 10 "fmt" 11 "io" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/go-asm/go/cmd/go/base" 20 "github.com/go-asm/go/cmd/go/cache" 21 "github.com/go-asm/go/cmd/go/cfg" 22 "github.com/go-asm/go/cmd/go/load" 23 "github.com/go-asm/go/cmd/go/lockedfile" 24 "github.com/go-asm/go/cmd/go/modfetch" 25 "github.com/go-asm/go/cmd/go/modload" 26 "github.com/go-asm/go/cmd/go/str" 27 "github.com/go-asm/go/cmd/go/work" 28 ) 29 30 var CmdClean = &base.Command{ 31 UsageLine: "go clean [clean flags] [build flags] [packages]", 32 Short: "remove object files and cached files", 33 Long: ` 34 Clean removes object files from package source directories. 35 The go command builds most objects in a temporary directory, 36 so go clean is mainly concerned with object files left by other 37 tools or by manual invocations of go build. 38 39 If a package argument is given or the -i or -r flag is set, 40 clean removes the following files from each of the 41 source directories corresponding to the import paths: 42 43 _obj/ old object directory, left from Makefiles 44 _test/ old test directory, left from Makefiles 45 _testmain.go old gotest file, left from Makefiles 46 test.out old test log, left from Makefiles 47 build.out old test log, left from Makefiles 48 *.[568ao] object files, left from Makefiles 49 50 DIR(.exe) from go build 51 DIR.test(.exe) from go test -c 52 MAINFILE(.exe) from go build MAINFILE.go 53 *.so from SWIG 54 55 In the list, DIR represents the final path element of the 56 directory, and MAINFILE is the base name of any Go source 57 file in the directory that is not included when building 58 the package. 59 60 The -i flag causes clean to remove the corresponding installed 61 archive or binary (what 'go install' would create). 62 63 The -n flag causes clean to print the remove commands it would execute, 64 but not run them. 65 66 The -r flag causes clean to be applied recursively to all the 67 dependencies of the packages named by the import paths. 68 69 The -x flag causes clean to print remove commands as it executes them. 70 71 The -cache flag causes clean to remove the entire go build cache. 72 73 The -testcache flag causes clean to expire all test results in the 74 go build cache. 75 76 The -modcache flag causes clean to remove the entire module 77 download cache, including unpacked source code of versioned 78 dependencies. 79 80 The -fuzzcache flag causes clean to remove files stored in the Go build 81 cache for fuzz testing. The fuzzing engine caches files that expand 82 code coverage, so removing them may make fuzzing less effective until 83 new inputs are found that provide the same coverage. These files are 84 distinct from those stored in testdata directory; clean does not remove 85 those files. 86 87 For more about build flags, see 'go help build'. 88 89 For more about specifying packages, see 'go help packages'. 90 `, 91 } 92 93 var ( 94 cleanI bool // clean -i flag 95 cleanR bool // clean -r flag 96 cleanCache bool // clean -cache flag 97 cleanFuzzcache bool // clean -fuzzcache flag 98 cleanModcache bool // clean -modcache flag 99 cleanTestcache bool // clean -testcache flag 100 ) 101 102 func init() { 103 // break init cycle 104 CmdClean.Run = runClean 105 106 CmdClean.Flag.BoolVar(&cleanI, "i", false, "") 107 CmdClean.Flag.BoolVar(&cleanR, "r", false, "") 108 CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "") 109 CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "") 110 CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "") 111 CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "") 112 113 // -n and -x are important enough to be 114 // mentioned explicitly in the docs but they 115 // are part of the build flags. 116 117 work.AddBuildFlags(CmdClean, work.DefaultBuildFlags) 118 } 119 120 func runClean(ctx context.Context, cmd *base.Command, args []string) { 121 if len(args) > 0 { 122 cacheFlag := "" 123 switch { 124 case cleanCache: 125 cacheFlag = "-cache" 126 case cleanTestcache: 127 cacheFlag = "-testcache" 128 case cleanFuzzcache: 129 cacheFlag = "-fuzzcache" 130 case cleanModcache: 131 cacheFlag = "-modcache" 132 } 133 if cacheFlag != "" { 134 base.Fatalf("go: clean %s cannot be used with package arguments", cacheFlag) 135 } 136 } 137 138 // golang.org/issue/29925: only load packages before cleaning if 139 // either the flags and arguments explicitly imply a package, 140 // or no other target (such as a cache) was requested to be cleaned. 141 cleanPkg := len(args) > 0 || cleanI || cleanR 142 if (!modload.Enabled() || modload.HasModRoot()) && 143 !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache { 144 cleanPkg = true 145 } 146 147 if cleanPkg { 148 for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) { 149 clean(pkg) 150 } 151 } 152 153 sh := work.NewShell("", fmt.Print) 154 155 if cleanCache { 156 dir := cache.DefaultDir() 157 if dir != "off" { 158 // Remove the cache subdirectories but not the top cache directory. 159 // The top cache directory may have been created with special permissions 160 // and not something that we want to remove. Also, we'd like to preserve 161 // the access log for future analysis, even if the cache is cleared. 162 subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]")) 163 printedErrors := false 164 if len(subdirs) > 0 { 165 if err := sh.RemoveAll(subdirs...); err != nil && !printedErrors { 166 printedErrors = true 167 base.Error(err) 168 } 169 } 170 171 logFile := filepath.Join(dir, "log.txt") 172 if err := sh.RemoveAll(logFile); err != nil && !printedErrors { 173 printedErrors = true 174 base.Error(err) 175 } 176 } 177 } 178 179 if cleanTestcache && !cleanCache { 180 // Instead of walking through the entire cache looking for test results, 181 // we write a file to the cache indicating that all test results from before 182 // right now are to be ignored. 183 dir := cache.DefaultDir() 184 if dir != "off" { 185 f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt")) 186 if err == nil { 187 now := time.Now().UnixNano() 188 buf, _ := io.ReadAll(f) 189 prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64) 190 if now > prev { 191 if err = f.Truncate(0); err == nil { 192 if _, err = f.Seek(0, 0); err == nil { 193 _, err = fmt.Fprintf(f, "%d\n", now) 194 } 195 } 196 } 197 if closeErr := f.Close(); err == nil { 198 err = closeErr 199 } 200 } 201 if err != nil { 202 if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) { 203 base.Error(err) 204 } 205 } 206 } 207 } 208 209 if cleanModcache { 210 if cfg.GOMODCACHE == "" { 211 base.Fatalf("go: cannot clean -modcache without a module cache") 212 } 213 if cfg.BuildN || cfg.BuildX { 214 sh.ShowCmd("", "rm -rf %s", cfg.GOMODCACHE) 215 } 216 if !cfg.BuildN { 217 if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil { 218 base.Error(err) 219 } 220 } 221 } 222 223 if cleanFuzzcache { 224 fuzzDir := cache.Default().FuzzDir() 225 if err := sh.RemoveAll(fuzzDir); err != nil { 226 base.Error(err) 227 } 228 } 229 } 230 231 var cleaned = map[*load.Package]bool{} 232 233 // TODO: These are dregs left by Makefile-based builds. 234 // Eventually, can stop deleting these. 235 var cleanDir = map[string]bool{ 236 "_test": true, 237 "_obj": true, 238 } 239 240 var cleanFile = map[string]bool{ 241 "_testmain.go": true, 242 "test.out": true, 243 "build.out": true, 244 "a.out": true, 245 } 246 247 var cleanExt = map[string]bool{ 248 ".5": true, 249 ".6": true, 250 ".8": true, 251 ".a": true, 252 ".o": true, 253 ".so": true, 254 } 255 256 func clean(p *load.Package) { 257 if cleaned[p] { 258 return 259 } 260 cleaned[p] = true 261 262 if p.Dir == "" { 263 base.Errorf("%v", p.Error) 264 return 265 } 266 dirs, err := os.ReadDir(p.Dir) 267 if err != nil { 268 base.Errorf("go: %s: %v", p.Dir, err) 269 return 270 } 271 272 sh := work.NewShell("", fmt.Print) 273 274 packageFile := map[string]bool{} 275 if p.Name != "main" { 276 // Record which files are not in package main. 277 // The others are. 278 keep := func(list []string) { 279 for _, f := range list { 280 packageFile[f] = true 281 } 282 } 283 keep(p.GoFiles) 284 keep(p.CgoFiles) 285 keep(p.TestGoFiles) 286 keep(p.XTestGoFiles) 287 } 288 289 _, elem := filepath.Split(p.Dir) 290 var allRemove []string 291 292 // Remove dir-named executable only if this is package main. 293 if p.Name == "main" { 294 allRemove = append(allRemove, 295 elem, 296 elem+".exe", 297 p.DefaultExecName(), 298 p.DefaultExecName()+".exe", 299 ) 300 } 301 302 // Remove package test executables. 303 allRemove = append(allRemove, 304 elem+".test", 305 elem+".test.exe", 306 p.DefaultExecName()+".test", 307 p.DefaultExecName()+".test.exe", 308 ) 309 310 // Remove a potential executable, test executable for each .go file in the directory that 311 // is not part of the directory's package. 312 for _, dir := range dirs { 313 name := dir.Name() 314 if packageFile[name] { 315 continue 316 } 317 318 if dir.IsDir() { 319 continue 320 } 321 322 if base, found := strings.CutSuffix(name, "_test.go"); found { 323 allRemove = append(allRemove, base+".test", base+".test.exe") 324 } 325 326 if base, found := strings.CutSuffix(name, ".go"); found { 327 // TODO(adg,rsc): check that this .go file is actually 328 // in "package main", and therefore capable of building 329 // to an executable file. 330 allRemove = append(allRemove, base, base+".exe") 331 } 332 } 333 334 if cfg.BuildN || cfg.BuildX { 335 sh.ShowCmd(p.Dir, "rm -f %s", strings.Join(allRemove, " ")) 336 } 337 338 toRemove := map[string]bool{} 339 for _, name := range allRemove { 340 toRemove[name] = true 341 } 342 for _, dir := range dirs { 343 name := dir.Name() 344 if dir.IsDir() { 345 // TODO: Remove once Makefiles are forgotten. 346 if cleanDir[name] { 347 if err := sh.RemoveAll(filepath.Join(p.Dir, name)); err != nil { 348 base.Error(err) 349 } 350 } 351 continue 352 } 353 354 if cfg.BuildN { 355 continue 356 } 357 358 if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] { 359 removeFile(filepath.Join(p.Dir, name)) 360 } 361 } 362 363 if cleanI && p.Target != "" { 364 if cfg.BuildN || cfg.BuildX { 365 sh.ShowCmd("", "rm -f %s", p.Target) 366 } 367 if !cfg.BuildN { 368 removeFile(p.Target) 369 } 370 } 371 372 if cleanR { 373 for _, p1 := range p.Internal.Imports { 374 clean(p1) 375 } 376 } 377 } 378 379 // removeFile tries to remove file f, if error other than file doesn't exist 380 // occurs, it will report the error. 381 func removeFile(f string) { 382 err := os.Remove(f) 383 if err == nil || os.IsNotExist(err) { 384 return 385 } 386 // Windows does not allow deletion of a binary file while it is executing. 387 if runtime.GOOS == "windows" { 388 // Remove lingering ~ file from last attempt. 389 if _, err2 := os.Stat(f + "~"); err2 == nil { 390 os.Remove(f + "~") 391 } 392 // Try to move it out of the way. If the move fails, 393 // which is likely, we'll try again the 394 // next time we do an install of this binary. 395 if err2 := os.Rename(f, f+"~"); err2 == nil { 396 os.Remove(f + "~") 397 return 398 } 399 } 400 base.Error(err) 401 }