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