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