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