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