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