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