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 if len(args) > 0 {
124 cacheFlag := ""
125 switch {
126 case cleanCache:
127 cacheFlag = "-cache"
128 case cleanTestcache:
129 cacheFlag = "-testcache"
130 case cleanFuzzcache:
131 cacheFlag = "-fuzzcache"
132 case cleanModcache:
133 cacheFlag = "-modcache"
134 }
135 if cacheFlag != "" {
136 base.Fatalf("go: clean %s cannot be used with package arguments", cacheFlag)
137 }
138 }
139
140
141
142
143 cleanPkg := len(args) > 0 || cleanI || cleanR
144 if (!modload.Enabled() || modload.HasModRoot()) &&
145 !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
146 cleanPkg = true
147 }
148
149 if cleanPkg {
150 for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) {
151 clean(pkg)
152 }
153 }
154
155 sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
156
157 if cleanCache {
158 dir, _, err := cache.DefaultDir()
159 if err != nil {
160 base.Fatal(err)
161 }
162 if dir != "off" {
163
164
165
166
167 subdirs, _ := filepath.Glob(filepath.Join(str.QuoteGlob(dir), "[0-9a-f][0-9a-f]"))
168 printedErrors := false
169 if len(subdirs) > 0 {
170 if err := sh.RemoveAll(subdirs...); err != nil && !printedErrors {
171 printedErrors = true
172 base.Error(err)
173 }
174 }
175
176 logFile := filepath.Join(dir, "log.txt")
177 if err := sh.RemoveAll(logFile); err != nil && !printedErrors {
178 printedErrors = true
179 base.Error(err)
180 }
181 }
182 }
183
184 if cleanTestcache && !cleanCache {
185
186
187
188 dir, _, err := cache.DefaultDir()
189 if err != nil {
190 base.Fatal(err)
191 }
192 if dir != "off" {
193 f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
194 if err == nil {
195 now := time.Now().UnixNano()
196 buf, _ := io.ReadAll(f)
197 prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
198 if now > prev {
199 if err = f.Truncate(0); err == nil {
200 if _, err = f.Seek(0, 0); err == nil {
201 _, err = fmt.Fprintf(f, "%d\n", now)
202 }
203 }
204 }
205 if closeErr := f.Close(); err == nil {
206 err = closeErr
207 }
208 }
209 if err != nil {
210 if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
211 base.Error(err)
212 }
213 }
214 }
215 }
216
217 if cleanModcache {
218 if cfg.GOMODCACHE == "" {
219 base.Fatalf("go: cannot clean -modcache without a module cache")
220 }
221 if cfg.BuildN || cfg.BuildX {
222 sh.ShowCmd("", "rm -rf %s", cfg.GOMODCACHE)
223 }
224 if !cfg.BuildN {
225 if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
226 base.Error(err)
227
228
229
230
231
232
233 if runtime.GOOS == "openbsd" && errors.Is(err, fs.ErrExist) {
234 logFilesInGOMODCACHE()
235 }
236 }
237 }
238 }
239
240 if cleanFuzzcache {
241 fuzzDir := cache.Default().FuzzDir()
242 if err := sh.RemoveAll(fuzzDir); err != nil {
243 base.Error(err)
244 }
245 }
246 }
247
248
249 func logFilesInGOMODCACHE() {
250 var found []string
251 werr := filepath.WalkDir(cfg.GOMODCACHE, func(path string, d fs.DirEntry, err error) error {
252 if err != nil {
253 return err
254 }
255 var mode string
256 info, err := d.Info()
257 if err == nil {
258 mode = info.Mode().String()
259 } else {
260 mode = fmt.Sprintf("<err: %s>", info.Mode())
261 }
262 found = append(found, fmt.Sprintf("%s (mode: %s)", path, mode))
263 return nil
264 })
265 if werr != nil {
266 base.Errorf("walking files in GOMODCACHE (for debugging go.dev/issue/68087): %v", werr)
267 }
268 base.Errorf("files in GOMODCACHE (for debugging go.dev/issue/68087):\n%s", strings.Join(found, "\n"))
269 }
270
271 var cleaned = map[*load.Package]bool{}
272
273
274
275 var cleanDir = map[string]bool{
276 "_test": true,
277 "_obj": true,
278 }
279
280 var cleanFile = map[string]bool{
281 "_testmain.go": true,
282 "test.out": true,
283 "build.out": true,
284 "a.out": true,
285 }
286
287 var cleanExt = map[string]bool{
288 ".5": true,
289 ".6": true,
290 ".8": true,
291 ".a": true,
292 ".o": true,
293 ".so": true,
294 }
295
296 func clean(p *load.Package) {
297 if cleaned[p] {
298 return
299 }
300 cleaned[p] = true
301
302 if p.Dir == "" {
303 base.Errorf("%v", p.Error)
304 return
305 }
306 dirs, err := os.ReadDir(p.Dir)
307 if err != nil {
308 base.Errorf("go: %s: %v", p.Dir, err)
309 return
310 }
311
312 sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
313
314 packageFile := map[string]bool{}
315 if p.Name != "main" {
316
317
318 keep := func(list []string) {
319 for _, f := range list {
320 packageFile[f] = true
321 }
322 }
323 keep(p.GoFiles)
324 keep(p.CgoFiles)
325 keep(p.TestGoFiles)
326 keep(p.XTestGoFiles)
327 }
328
329 _, elem := filepath.Split(p.Dir)
330 var allRemove []string
331
332
333 if p.Name == "main" {
334 allRemove = append(allRemove,
335 elem,
336 elem+".exe",
337 p.DefaultExecName(),
338 p.DefaultExecName()+".exe",
339 )
340 }
341
342
343 allRemove = append(allRemove,
344 elem+".test",
345 elem+".test.exe",
346 p.DefaultExecName()+".test",
347 p.DefaultExecName()+".test.exe",
348 )
349
350
351
352 for _, dir := range dirs {
353 name := dir.Name()
354 if packageFile[name] {
355 continue
356 }
357
358 if dir.IsDir() {
359 continue
360 }
361
362 if base, found := strings.CutSuffix(name, "_test.go"); found {
363 allRemove = append(allRemove, base+".test", base+".test.exe")
364 }
365
366 if base, found := strings.CutSuffix(name, ".go"); found {
367
368
369
370 allRemove = append(allRemove, base, base+".exe")
371 }
372 }
373
374 if cfg.BuildN || cfg.BuildX {
375 sh.ShowCmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
376 }
377
378 toRemove := map[string]bool{}
379 for _, name := range allRemove {
380 toRemove[name] = true
381 }
382 for _, dir := range dirs {
383 name := dir.Name()
384 if dir.IsDir() {
385
386 if cleanDir[name] {
387 if err := sh.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
388 base.Error(err)
389 }
390 }
391 continue
392 }
393
394 if cfg.BuildN {
395 continue
396 }
397
398 if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
399 removeFile(filepath.Join(p.Dir, name))
400 }
401 }
402
403 if cleanI && p.Target != "" {
404 if cfg.BuildN || cfg.BuildX {
405 sh.ShowCmd("", "rm -f %s", p.Target)
406 }
407 if !cfg.BuildN {
408 removeFile(p.Target)
409 }
410 }
411
412 if cleanR {
413 for _, p1 := range p.Internal.Imports {
414 clean(p1)
415 }
416 }
417 }
418
419
420
421 func removeFile(f string) {
422 err := os.Remove(f)
423 if err == nil || os.IsNotExist(err) {
424 return
425 }
426
427 if runtime.GOOS == "windows" {
428
429 if _, err2 := os.Stat(f + "~"); err2 == nil {
430 os.Remove(f + "~")
431 }
432
433
434
435 if err2 := os.Rename(f, f+"~"); err2 == nil {
436 os.Remove(f + "~")
437 return
438 }
439 }
440 base.Error(err)
441 }
442
View as plain text