github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/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/bir3/gocompiler/src/cmd/gocmd/internal/base" 20 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cache" 21 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg" 22 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/load" 23 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/lockedfile" 24 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modfetch" 25 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/modload" 26 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/str" 27 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/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 var b work.Builder 154 b.Print = fmt.Print 155 156 if cleanCache { 157 dir := cache.DefaultDir() 158 if dir != "off" { 159 // Remove the cache subdirectories but not the top cache directory. 160 // The top cache directory may have been created with special permissions 161 // and not something that we want to remove. Also, we'd like to preserve 162 // the access log for future analysis, even if the cache is cleared. 163 subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]")) 164 printedErrors := false 165 if len(subdirs) > 0 { 166 if cfg.BuildN || cfg.BuildX { 167 b.Showcmd("", "rm -r %s", strings.Join(subdirs, " ")) 168 } 169 if !cfg.BuildN { 170 for _, d := range subdirs { 171 // Only print the first error - there may be many. 172 // This also mimics what os.RemoveAll(dir) would do. 173 if err := os.RemoveAll(d); err != nil && !printedErrors { 174 printedErrors = true 175 base.Errorf("go: %v", err) 176 } 177 } 178 } 179 } 180 181 logFile := filepath.Join(dir, "log.txt") 182 if cfg.BuildN || cfg.BuildX { 183 b.Showcmd("", "rm -f %s", logFile) 184 } 185 if !cfg.BuildN { 186 if err := os.RemoveAll(logFile); err != nil && !printedErrors { 187 printedErrors = true 188 base.Errorf("go: %v", err) 189 } 190 } 191 } 192 } 193 194 if cleanTestcache && !cleanCache { 195 // Instead of walking through the entire cache looking for test results, 196 // we write a file to the cache indicating that all test results from before 197 // right now are to be ignored. 198 dir := cache.DefaultDir() 199 if dir != "off" { 200 f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt")) 201 if err == nil { 202 now := time.Now().UnixNano() 203 buf, _ := io.ReadAll(f) 204 prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64) 205 if now > prev { 206 if err = f.Truncate(0); err == nil { 207 if _, err = f.Seek(0, 0); err == nil { 208 _, err = fmt.Fprintf(f, "%d\n", now) 209 } 210 } 211 } 212 if closeErr := f.Close(); err == nil { 213 err = closeErr 214 } 215 } 216 if err != nil { 217 if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) { 218 base.Errorf("go: %v", err) 219 } 220 } 221 } 222 } 223 224 if cleanModcache { 225 if cfg.GOMODCACHE == "" { 226 base.Fatalf("go: cannot clean -modcache without a module cache") 227 } 228 if cfg.BuildN || cfg.BuildX { 229 b.Showcmd("", "rm -rf %s", cfg.GOMODCACHE) 230 } 231 if !cfg.BuildN { 232 if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil { 233 base.Errorf("go: %v", err) 234 } 235 } 236 } 237 238 if cleanFuzzcache { 239 fuzzDir := cache.Default().FuzzDir() 240 if cfg.BuildN || cfg.BuildX { 241 b.Showcmd("", "rm -rf %s", fuzzDir) 242 } 243 if !cfg.BuildN { 244 if err := os.RemoveAll(fuzzDir); err != nil { 245 base.Errorf("go: %v", err) 246 } 247 } 248 } 249 } 250 251 var cleaned = map[*load.Package]bool{} 252 253 // TODO: These are dregs left by Makefile-based builds. 254 // Eventually, can stop deleting these. 255 var cleanDir = map[string]bool{ 256 "_test": true, 257 "_obj": true, 258 } 259 260 var cleanFile = map[string]bool{ 261 "_testmain.go": true, 262 "test.out": true, 263 "build.out": true, 264 "a.out": true, 265 } 266 267 var cleanExt = map[string]bool{ 268 ".5": true, 269 ".6": true, 270 ".8": true, 271 ".a": true, 272 ".o": true, 273 ".so": true, 274 } 275 276 func clean(p *load.Package) { 277 if cleaned[p] { 278 return 279 } 280 cleaned[p] = true 281 282 if p.Dir == "" { 283 base.Errorf("%v", p.Error) 284 return 285 } 286 dirs, err := os.ReadDir(p.Dir) 287 if err != nil { 288 base.Errorf("go: %s: %v", p.Dir, err) 289 return 290 } 291 292 var b work.Builder 293 b.Print = fmt.Print 294 295 packageFile := map[string]bool{} 296 if p.Name != "main" { 297 // Record which files are not in package main. 298 // The others are. 299 keep := func(list []string) { 300 for _, f := range list { 301 packageFile[f] = true 302 } 303 } 304 keep(p.GoFiles) 305 keep(p.CgoFiles) 306 keep(p.TestGoFiles) 307 keep(p.XTestGoFiles) 308 } 309 310 _, elem := filepath.Split(p.Dir) 311 var allRemove []string 312 313 // Remove dir-named executable only if this is package main. 314 if p.Name == "main" { 315 allRemove = append(allRemove, 316 elem, 317 elem+".exe", 318 p.DefaultExecName(), 319 p.DefaultExecName()+".exe", 320 ) 321 } 322 323 // Remove package test executables. 324 allRemove = append(allRemove, 325 elem+".test", 326 elem+".test.exe", 327 p.DefaultExecName()+".test", 328 p.DefaultExecName()+".test.exe", 329 ) 330 331 // Remove a potential executable, test executable for each .go file in the directory that 332 // is not part of the directory's package. 333 for _, dir := range dirs { 334 name := dir.Name() 335 if packageFile[name] { 336 continue 337 } 338 339 if dir.IsDir() { 340 continue 341 } 342 343 if base, found := strings.CutSuffix(name, "_test.go"); found { 344 allRemove = append(allRemove, base+".test", base+".test.exe") 345 } 346 347 if base, found := strings.CutSuffix(name, ".go"); found { 348 // TODO(adg,rsc): check that this .go file is actually 349 // in "package main", and therefore capable of building 350 // to an executable file. 351 allRemove = append(allRemove, base, base+".exe") 352 } 353 } 354 355 if cfg.BuildN || cfg.BuildX { 356 b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " ")) 357 } 358 359 toRemove := map[string]bool{} 360 for _, name := range allRemove { 361 toRemove[name] = true 362 } 363 for _, dir := range dirs { 364 name := dir.Name() 365 if dir.IsDir() { 366 // TODO: Remove once Makefiles are forgotten. 367 if cleanDir[name] { 368 if cfg.BuildN || cfg.BuildX { 369 b.Showcmd(p.Dir, "rm -r %s", name) 370 if cfg.BuildN { 371 continue 372 } 373 } 374 if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil { 375 base.Errorf("go: %v", err) 376 } 377 } 378 continue 379 } 380 381 if cfg.BuildN { 382 continue 383 } 384 385 if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] { 386 removeFile(filepath.Join(p.Dir, name)) 387 } 388 } 389 390 if cleanI && p.Target != "" { 391 if cfg.BuildN || cfg.BuildX { 392 b.Showcmd("", "rm -f %s", p.Target) 393 } 394 if !cfg.BuildN { 395 removeFile(p.Target) 396 } 397 } 398 399 if cleanR { 400 for _, p1 := range p.Internal.Imports { 401 clean(p1) 402 } 403 } 404 } 405 406 // removeFile tries to remove file f, if error other than file doesn't exist 407 // occurs, it will report the error. 408 func removeFile(f string) { 409 err := os.Remove(f) 410 if err == nil || os.IsNotExist(err) { 411 return 412 } 413 // Windows does not allow deletion of a binary file while it is executing. 414 if runtime.GOOS == "windows" { 415 // Remove lingering ~ file from last attempt. 416 if _, err2 := os.Stat(f + "~"); err2 == nil { 417 os.Remove(f + "~") 418 } 419 // Try to move it out of the way. If the move fails, 420 // which is likely, we'll try again the 421 // next time we do an install of this binary. 422 if err2 := os.Rename(f, f+"~"); err2 == nil { 423 os.Remove(f + "~") 424 return 425 } 426 } 427 base.Errorf("go: %v", err) 428 }