1
2
3
4
5
6 package clean
7
8 import (
9 "context"
10 "errors"
11 "fmt"
12 "io"
13 "io/fs"
14 "os"
15 "path/filepath"
16 "runtime"
17 "strconv"
18 "strings"
19 "time"
20
21 "cmd/go/internal/base"
22 "cmd/go/internal/cache"
23 "cmd/go/internal/cfg"
24 "cmd/go/internal/load"
25 "cmd/go/internal/lockedfile"
26 "cmd/go/internal/modfetch"
27 "cmd/go/internal/modload"
28 "cmd/go/internal/str"
29 "cmd/go/internal/work"
30 )
31
32 var CmdClean = &base.Command{
33 UsageLine: "go clean [-i] [-r] [-cache] [-testcache] [-modcache] [-fuzzcache] [build flags] [packages]",
34 Short: "remove object files and cached files",
35 Long: `
36 Clean removes object files from package source directories.
37 The go command builds most objects in a temporary directory,
38 so go clean is mainly concerned with object files left by other
39 tools or by manual invocations of go build.
40
41 If a package argument is given or the -i or -r flag is set,
42 clean removes the following files from each of the
43 source directories corresponding to the import paths:
44
45 _obj/ old object directory, left from Makefiles
46 _test/ old test directory, left from Makefiles
47 _testmain.go old gotest file, left from Makefiles
48 test.out old test log, left from Makefiles
49 build.out old test log, left from Makefiles
50 *.[568ao] object files, left from Makefiles
51
52 DIR(.exe) from go build
53 DIR.test(.exe) from go test -c
54 MAINFILE(.exe) from go build MAINFILE.go
55 *.so from SWIG
56
57 In the list, DIR represents the final path element of the
58 directory, and MAINFILE is the base name of any Go source
59 file in the directory that is not included when building
60 the package.
61
62 The -i flag causes clean to remove the corresponding installed
63 archive or binary (what 'go install' would create).
64
65 The -n flag causes clean to print the remove commands it would execute,
66 but not run them.
67
68 The -r flag causes clean to be applied recursively to all the
69 dependencies of the packages named by the import paths.
70
71 The -x flag causes clean to print remove commands as it executes them.
72
73 The -cache flag causes clean to remove the entire go build cache.
74
75 The -testcache flag causes clean to expire all test results in the
76 go build cache.
77
78 The -modcache flag causes clean to remove the entire module
79 download cache, including unpacked source code of versioned
80 dependencies.
81
82 The -fuzzcache flag causes clean to remove files stored in the Go build
83 cache for fuzz testing. The fuzzing engine caches files that expand
84 code coverage, so removing them may make fuzzing less effective until
85 new inputs are found that provide the same coverage. These files are
86 distinct from those stored in testdata directory; clean does not remove
87 those files.
88
89 For more about build flags, see 'go help build'.
90
91 For more about specifying packages, see 'go help packages'.
92 `,
93 }
94
95 var (
96 cleanI bool
97 cleanR bool
98 cleanCache bool
99 cleanFuzzcache bool
100 cleanModcache bool
101 cleanTestcache bool
102 )
103
104 func init() {
105
106 CmdClean.Run = runClean
107
108 CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
109 CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
110 CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
111 CmdClean.Flag.BoolVar(&cleanFuzzcache, "fuzzcache", false, "")
112 CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
113 CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")
114
115
116
117
118
119 work.AddBuildFlags(CmdClean, work.OmitBuildOnlyFlags)
120 }
121
122 func runClean(ctx context.Context, cmd *base.Command, args []string) {
123 moduleLoaderState := modload.NewState()
124 modload.InitWorkfile(moduleLoaderState)
125 if len(args) > 0 {
126 cacheFlag := ""
127 switch {
128 case cleanCache:
129 cacheFlag = "-cache"
130 case cleanTestcache:
131 cacheFlag = "-testcache"
132 case cleanFuzzcache:
133 cacheFlag = "-fuzzcache"
134 case cleanModcache:
135 cacheFlag = "-modcache"
136 }
137 if cacheFlag != "" {
138 base.Fatalf("go: clean %s cannot be used with package arguments", cacheFlag)
139 }
140 }
141
142
143
144
145 cleanPkg := len(args) > 0 || cleanI || cleanR
146 if (!modload.Enabled(moduleLoaderState) || modload.HasModRoot(moduleLoaderState)) &&
147 !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
148 cleanPkg = true
149 }
150
151 if cleanPkg {
152 for _, pkg := range load.PackagesAndErrors(moduleLoaderState, ctx, load.PackageOpts{}, args) {
153 clean(pkg)
154 }
155 }
156
157 sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
158
159 if cleanCache {
160 dir, _, err := cache.DefaultDir()
161 if err != nil {
162 base.Fatal(err)
163 }
164 if dir != "off" {
165
166
167
168
169 subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
170 printedErrors := false
171 if len(subdirs) > 0 {
172 if err := sh.RemoveAll(subdirs...); err != nil && !printedErrors {
173 printedErrors = true
174 base.Error(err)
175 }
176 }
177
178 logFile := filepath.Join(dir, "log.txt")
179 if err := sh.RemoveAll(logFile); err != nil && !printedErrors {
180 printedErrors = true
181 base.Error(err)
182 }
183 }
184 }
185
186 if cleanTestcache && !cleanCache {
187
188
189
190 dir, _, err := cache.DefaultDir()
191 if err != nil {
192 base.Fatal(err)
193 }
194 if dir != "off" {
195 f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
196 if err == nil {
197 now := time.Now().UnixNano()
198 buf, _ := io.ReadAll(f)
199 prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
200 if now > prev {
201 if err = f.Truncate(0); err == nil {
202 if _, err = f.Seek(0, 0); err == nil {
203 _, err = fmt.Fprintf(f, "%d\n", now)
204 }
205 }
206 }
207 if closeErr := f.Close(); err == nil {
208 err = closeErr
209 }
210 }
211 if err != nil {
212 if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
213 base.Error(err)
214 }
215 }
216 }
217 }
218
219 if cleanModcache {
220 if cfg.GOMODCACHE == "" {
221 base.Fatalf("go: cannot clean -modcache without a module cache")
222 }
223 if cfg.BuildN || cfg.BuildX {
224 sh.ShowCmd("", "rm -rf %s", cfg.GOMODCACHE)
225 }
226 if !cfg.BuildN {
227 if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
228 base.Error(err)
229
230
231
232
233
234
235 if runtime.GOOS == "openbsd" && errors.Is(err, fs.ErrExist) {
236 logFilesInGOMODCACHE()
237 }
238 }
239 }
240 }
241
242 if cleanFuzzcache {
243 fuzzDir := cache.Default().FuzzDir()
244 if err := sh.RemoveAll(fuzzDir); err != nil {
245 base.Error(err)
246 }
247 }
248 }
249
250
251 func logFilesInGOMODCACHE() {
252 var found []string
253 werr := filepath.WalkDir(cfg.GOMODCACHE, func(path string, d fs.DirEntry, err error) error {
254 if err != nil {
255 return err
256 }
257 var mode string
258 info, err := d.Info()
259 if err == nil {
260 mode = info.Mode().String()
261 } else {
262 mode = fmt.Sprintf("<err: %s>", info.Mode())
263 }
264 found = append(found, fmt.Sprintf("%s (mode: %s)", path, mode))
265 return nil
266 })
267 if werr != nil {
268 base.Errorf("walking files in GOMODCACHE (for debugging go.dev/issue/68087): %v", werr)
269 }
270 base.Errorf("files in GOMODCACHE (for debugging go.dev/issue/68087):\n%s", strings.Join(found, "\n"))
271 }
272
273 var cleaned = map[*load.Package]bool{}
274
275
276
277 var cleanDir = map[string]bool{
278 "_test": true,
279 "_obj": true,
280 }
281
282 var cleanFile = map[string]bool{
283 "_testmain.go": true,
284 "test.out": true,
285 "build.out": true,
286 "a.out": true,
287 }
288
289 var cleanExt = map[string]bool{
290 ".5": true,
291 ".6": true,
292 ".8": true,
293 ".a": true,
294 ".o": true,
295 ".so": true,
296 }
297
298 func clean(p *load.Package) {
299 if cleaned[p] {
300 return
301 }
302 cleaned[p] = true
303
304 if p.Dir == "" {
305 base.Errorf("%v", p.Error)
306 return
307 }
308 dirs, err := os.ReadDir(p.Dir)
309 if err != nil {
310 base.Errorf("go: %s: %v", p.Dir, err)
311 return
312 }
313
314 sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
315
316 packageFile := map[string]bool{}
317 if p.Name != "main" {
318
319
320 keep := func(list []string) {
321 for _, f := range list {
322 packageFile[f] = true
323 }
324 }
325 keep(p.GoFiles)
326 keep(p.CgoFiles)
327 keep(p.TestGoFiles)
328 keep(p.XTestGoFiles)
329 }
330
331 _, elem := filepath.Split(p.Dir)
332 var allRemove []string
333
334
335 if p.Name == "main" {
336 allRemove = append(allRemove,
337 elem,
338 elem+".exe",
339 p.DefaultExecName(),
340 p.DefaultExecName()+".exe",
341 )
342 }
343
344
345 allRemove = append(allRemove,
346 elem+".test",
347 elem+".test.exe",
348 p.DefaultExecName()+".test",
349 p.DefaultExecName()+".test.exe",
350 )
351
352
353
354 for _, dir := range dirs {
355 name := dir.Name()
356 if packageFile[name] {
357 continue
358 }
359
360 if dir.IsDir() {
361 continue
362 }
363
364 if base, found := strings.CutSuffix(name, "_test.go"); found {
365 allRemove = append(allRemove, base+".test", base+".test.exe")
366 }
367
368 if base, found := strings.CutSuffix(name, ".go"); found {
369
370
371
372 allRemove = append(allRemove, base, base+".exe")
373 }
374 }
375
376 if cfg.BuildN || cfg.BuildX {
377 sh.ShowCmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
378 }
379
380 toRemove := map[string]bool{}
381 for _, name := range allRemove {
382 toRemove[name] = true
383 }
384 for _, dir := range dirs {
385 name := dir.Name()
386 if dir.IsDir() {
387
388 if cleanDir[name] {
389 if err := sh.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
390 base.Error(err)
391 }
392 }
393 continue
394 }
395
396 if cfg.BuildN {
397 continue
398 }
399
400 if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
401 removeFile(filepath.Join(p.Dir, name))
402 }
403 }
404
405 if cleanI && p.Target != "" {
406 if cfg.BuildN || cfg.BuildX {
407 sh.ShowCmd("", "rm -f %s", p.Target)
408 }
409 if !cfg.BuildN {
410 removeFile(p.Target)
411 }
412 }
413
414 if cleanR {
415 for _, p1 := range p.Internal.Imports {
416 clean(p1)
417 }
418 }
419 }
420
421
422
423 func removeFile(f string) {
424 err := os.Remove(f)
425 if err == nil || os.IsNotExist(err) {
426 return
427 }
428
429 if runtime.GOOS == "windows" {
430
431 if _, err2 := os.Stat(f + "~"); err2 == nil {
432 os.Remove(f + "~")
433 }
434
435
436
437 if err2 := os.Rename(f, f+"~"); err2 == nil {
438 os.Remove(f + "~")
439 return
440 }
441 }
442 base.Error(err)
443 }
444
View as plain text