1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package clean implements the ``go clean'' command.
6package clean
7
8import (
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
28var CmdClean = &base.Command{
29	UsageLine: "go clean [clean flags] [build flags] [packages]",
30	Short:     "remove object files and cached files",
31	Long: `
32Clean removes object files from package source directories.
33The go command builds most objects in a temporary directory,
34so go clean is mainly concerned with object files left by other
35tools or by manual invocations of go build.
36
37If a package argument is given or the -i or -r flag is set,
38clean removes the following files from each of the
39source 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
53In the list, DIR represents the final path element of the
54directory, and MAINFILE is the base name of any Go source
55file in the directory that is not included when building
56the package.
57
58The -i flag causes clean to remove the corresponding installed
59archive or binary (what 'go install' would create).
60
61The -n flag causes clean to print the remove commands it would execute,
62but not run them.
63
64The -r flag causes clean to be applied recursively to all the
65dependencies of the packages named by the import paths.
66
67The -x flag causes clean to print remove commands as it executes them.
68
69The -cache flag causes clean to remove the entire go build cache.
70
71The -testcache flag causes clean to expire all test results in the
72go build cache.
73
74The -modcache flag causes clean to remove the entire module
75download cache, including unpacked source code of versioned
76dependencies.
77
78For more about build flags, see 'go help build'.
79
80For more about specifying packages, see 'go help packages'.
81	`,
82}
83
84var (
85	cleanI         bool // clean -i flag
86	cleanR         bool // clean -r flag
87	cleanCache     bool // clean -cache flag
88	cleanModcache  bool // clean -modcache flag
89	cleanTestcache bool // clean -testcache flag
90)
91
92func init() {
93	// break init cycle
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	// -n and -x are important enough to be
103	// mentioned explicitly in the docs but they
104	// are part of the build flags.
105
106	work.AddBuildFlags(CmdClean, work.DefaultBuildFlags)
107}
108
109func runClean(ctx context.Context, cmd *base.Command, args []string) {
110	// golang.org/issue/29925: only load packages before cleaning if
111	// either the flags and arguments explicitly imply a package,
112	// or no other target (such as a cache) was requested to be cleaned.
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			// Remove the cache subdirectories but not the top cache directory.
132			// The top cache directory may have been created with special permissions
133			// and not something that we want to remove. Also, we'd like to preserve
134			// the access log for future analysis, even if the cache is cleared.
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						// Only print the first error - there may be many.
144						// This also mimics what os.RemoveAll(dir) would do.
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		// Instead of walking through the entire cache looking for test results,
168		// we write a file to the cache indicating that all test results from before
169		// right now are to be ignored.
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
211var cleaned = map[*load.Package]bool{}
212
213// TODO: These are dregs left by Makefile-based builds.
214// Eventually, can stop deleting these.
215var cleanDir = map[string]bool{
216	"_test": true,
217	"_obj":  true,
218}
219
220var cleanFile = map[string]bool{
221	"_testmain.go": true,
222	"test.out":     true,
223	"build.out":    true,
224	"a.out":        true,
225}
226
227var cleanExt = map[string]bool{
228	".5":  true,
229	".6":  true,
230	".8":  true,
231	".a":  true,
232	".o":  true,
233	".so": true,
234}
235
236func 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		// Record which files are not in package main.
258		// The others are.
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	// Remove dir-named executable only if this is package main.
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	// Remove package test executables.
284	allRemove = append(allRemove,
285		elem+".test",
286		elem+".test.exe",
287		p.DefaultExecName()+".test",
288		p.DefaultExecName()+".test.exe",
289	)
290
291	// Remove a potential executable, test executable for each .go file in the directory that
292	// is not part of the directory's package.
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			// TODO(adg,rsc): check that this .go file is actually
310			// in "package main", and therefore capable of building
311			// to an executable file.
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			// TODO: Remove once Makefiles are forgotten.
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// removeFile tries to remove file f, if error other than file doesn't exist
369// occurs, it will report the error.
370func removeFile(f string) {
371	err := os.Remove(f)
372	if err == nil || os.IsNotExist(err) {
373		return
374	}
375	// Windows does not allow deletion of a binary file while it is executing.
376	if base.ToolIsWindows {
377		// Remove lingering ~ file from last attempt.
378		if _, err2 := os.Stat(f + "~"); err2 == nil {
379			os.Remove(f + "~")
380		}
381		// Try to move it out of the way. If the move fails,
382		// which is likely, we'll try again the
383		// next time we do an install of this binary.
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