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 moduleLoader := modload.NewLoader()
124 moduleLoader.InitWorkfile()
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 (!moduleLoader.Enabled() || moduleLoader.HasModRoot()) &&
147 !cleanCache && !cleanModcache && !cleanTestcache && !cleanFuzzcache {
148 cleanPkg = true
149 }
150
151 if cleanPkg {
152 for _, pkg := range load.PackagesAndErrors(moduleLoader, 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 var cleanFile = map[string]bool{
276 "_testmain.go": true,
277 "test.out": true,
278 "build.out": true,
279 "a.out": true,
280 }
281
282 var cleanExt = map[string]bool{
283 ".5": true,
284 ".6": true,
285 ".8": true,
286 ".a": true,
287 ".o": true,
288 ".so": true,
289 }
290
291 func clean(p *load.Package) {
292 if cleaned[p] {
293 return
294 }
295 cleaned[p] = true
296
297 if p.Dir == "" {
298 base.Errorf("%v", p.Error)
299 return
300 }
301 dirs, err := os.ReadDir(p.Dir)
302 if err != nil {
303 base.Errorf("go: %s: %v", p.Dir, err)
304 return
305 }
306
307 sh := work.NewShell("", &load.TextPrinter{Writer: os.Stdout})
308
309 packageFile := map[string]bool{}
310 if p.Name != "main" {
311
312
313 keep := func(list []string) {
314 for _, f := range list {
315 packageFile[f] = true
316 }
317 }
318 keep(p.GoFiles)
319 keep(p.CgoFiles)
320 keep(p.TestGoFiles)
321 keep(p.XTestGoFiles)
322 }
323
324 _, elem := filepath.Split(p.Dir)
325 toRemove := map[string]bool{}
326
327
328 if p.Name == "main" {
329 toRemove[elem] = true
330 toRemove[elem+".exe"] = true
331 toRemove[p.DefaultExecName()] = true
332 toRemove[p.DefaultExecName()+".exe"] = true
333 }
334
335
336 toRemove[elem+".test"] = true
337 toRemove[elem+".test.exe"] = true
338 toRemove[p.DefaultExecName()+".test"] = true
339 toRemove[p.DefaultExecName()+".test.exe"] = true
340
341
342
343 for _, dir := range dirs {
344 name := dir.Name()
345 if packageFile[name] {
346 continue
347 }
348
349 if dir.IsDir() {
350 continue
351 }
352
353 if base, found := strings.CutSuffix(name, "_test.go"); found {
354 toRemove[base+".test"] = true
355 toRemove[base+".test.exe"] = true
356 }
357
358 if base, found := strings.CutSuffix(name, ".go"); found {
359
360
361
362 toRemove[base] = true
363 toRemove[base+".exe"] = true
364 }
365 }
366
367 for _, dir := range dirs {
368 name := dir.Name()
369 if dir.IsDir() {
370 continue
371 }
372 if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
373 removeFile(sh, filepath.Join(p.Dir, name))
374 }
375 }
376
377 if cleanI && p.Target != "" {
378 removeFile(sh, p.Target)
379 }
380
381 if cleanR {
382 for _, p1 := range p.Internal.Imports {
383 clean(p1)
384 }
385 }
386 }
387
388
389
390 func removeFile(sh *work.Shell, f string) {
391 if cfg.BuildN || cfg.BuildX {
392 sh.ShowCmd("", "rm -f %s", f)
393 }
394 if cfg.BuildN {
395 return
396 }
397 err := os.Remove(f)
398 if err == nil || os.IsNotExist(err) {
399 return
400 }
401
402 if runtime.GOOS == "windows" {
403
404 if _, err2 := os.Stat(f + "~"); err2 == nil {
405 os.Remove(f + "~")
406 }
407
408
409
410 if err2 := os.Rename(f, f+"~"); err2 == nil {
411 os.Remove(f + "~")
412 return
413 }
414 }
415 base.Error(err)
416 }
417
View as plain text