aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/go/internal/clean/clean.go
blob: 1089211f0ce0d3881f4ddbd64cd1779f7263db09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package clean implements the ``go clean'' command.
package clean

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"cmd/go/internal/base"
	"cmd/go/internal/cache"
	"cmd/go/internal/cfg"
	"cmd/go/internal/load"
	"cmd/go/internal/lockedfile"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/modload"
	"cmd/go/internal/work"
)

var CmdClean = &base.Command{
	UsageLine: "go clean [clean flags] [build flags] [packages]",
	Short:     "remove object files and cached files",
	Long: `
Clean removes object files from package source directories.
The go command builds most objects in a temporary directory,
so go clean is mainly concerned with object files left by other
tools or by manual invocations of go build.

If a package argument is given or the -i or -r flag is set,
clean removes the following files from each of the
source directories corresponding to the import paths:

	_obj/            old object directory, left from Makefiles
	_test/           old test directory, left from Makefiles
	_testmain.go     old gotest file, left from Makefiles
	test.out         old test log, left from Makefiles
	build.out        old test log, left from Makefiles
	*.[568ao]        object files, left from Makefiles

	DIR(.exe)        from go build
	DIR.test(.exe)   from go test -c
	MAINFILE(.exe)   from go build MAINFILE.go
	*.so             from SWIG

In the list, DIR represents the final path element of the
directory, and MAINFILE is the base name of any Go source
file in the directory that is not included when building
the package.

The -i flag causes clean to remove the corresponding installed
archive or binary (what 'go install' would create).

The -n flag causes clean to print the remove commands it would execute,
but not run them.

The -r flag causes clean to be applied recursively to all the
dependencies of the packages named by the import paths.

The -x flag causes clean to print remove commands as it executes them.

The -cache flag causes clean to remove the entire go build cache.

The -testcache flag causes clean to expire all test results in the
go build cache.

The -modcache flag causes clean to remove the entire module
download cache, including unpacked source code of versioned
dependencies.

For more about build flags, see 'go help build'.

For more about specifying packages, see 'go help packages'.
	`,
}

var (
	cleanI         bool // clean -i flag
	cleanR         bool // clean -r flag
	cleanCache     bool // clean -cache flag
	cleanModcache  bool // clean -modcache flag
	cleanTestcache bool // clean -testcache flag
)

func init() {
	// break init cycle
	CmdClean.Run = runClean

	CmdClean.Flag.BoolVar(&cleanI, "i", false, "")
	CmdClean.Flag.BoolVar(&cleanR, "r", false, "")
	CmdClean.Flag.BoolVar(&cleanCache, "cache", false, "")
	CmdClean.Flag.BoolVar(&cleanModcache, "modcache", false, "")
	CmdClean.Flag.BoolVar(&cleanTestcache, "testcache", false, "")

	// -n and -x are important enough to be
	// mentioned explicitly in the docs but they
	// are part of the build flags.

	work.AddBuildFlags(CmdClean, work.DefaultBuildFlags)
}

func runClean(ctx context.Context, cmd *base.Command, args []string) {
	// golang.org/issue/29925: only load packages before cleaning if
	// either the flags and arguments explicitly imply a package,
	// or no other target (such as a cache) was requested to be cleaned.
	cleanPkg := len(args) > 0 || cleanI || cleanR
	if (!modload.Enabled() || modload.HasModRoot()) &&
		!cleanCache && !cleanModcache && !cleanTestcache {
		cleanPkg = true
	}

	if cleanPkg {
		for _, pkg := range load.PackagesAndErrors(ctx, load.PackageOpts{}, args) {
			clean(pkg)
		}
	}

	var b work.Builder
	b.Print = fmt.Print

	if cleanCache {
		dir := cache.DefaultDir()
		if dir != "off" {
			// Remove the cache subdirectories but not the top cache directory.
			// The top cache directory may have been created with special permissions
			// and not something that we want to remove. Also, we'd like to preserve
			// the access log for future analysis, even if the cache is cleared.
			subdirs, _ := filepath.Glob(filepath.Join(dir, "[0-9a-f][0-9a-f]"))
			printedErrors := false
			if len(subdirs) > 0 {
				if cfg.BuildN || cfg.BuildX {
					b.Showcmd("", "rm -r %s", strings.Join(subdirs, " "))
				}
				if !cfg.BuildN {
					for _, d := range subdirs {
						// Only print the first error - there may be many.
						// This also mimics what os.RemoveAll(dir) would do.
						if err := os.RemoveAll(d); err != nil && !printedErrors {
							printedErrors = true
							base.Errorf("go: %v", err)
						}
					}
				}
			}

			logFile := filepath.Join(dir, "log.txt")
			if cfg.BuildN || cfg.BuildX {
				b.Showcmd("", "rm -f %s", logFile)
			}
			if !cfg.BuildN {
				if err := os.RemoveAll(logFile); err != nil && !printedErrors {
					printedErrors = true
					base.Errorf("go: %v", err)
				}
			}
		}
	}

	if cleanTestcache && !cleanCache {
		// Instead of walking through the entire cache looking for test results,
		// we write a file to the cache indicating that all test results from before
		// right now are to be ignored.
		dir := cache.DefaultDir()
		if dir != "off" {
			f, err := lockedfile.Edit(filepath.Join(dir, "testexpire.txt"))
			if err == nil {
				now := time.Now().UnixNano()
				buf, _ := io.ReadAll(f)
				prev, _ := strconv.ParseInt(strings.TrimSpace(string(buf)), 10, 64)
				if now > prev {
					if err = f.Truncate(0); err == nil {
						if _, err = f.Seek(0, 0); err == nil {
							_, err = fmt.Fprintf(f, "%d\n", now)
						}
					}
				}
				if closeErr := f.Close(); err == nil {
					err = closeErr
				}
			}
			if err != nil {
				if _, statErr := os.Stat(dir); !os.IsNotExist(statErr) {
					base.Errorf("go: %v", err)
				}
			}
		}
	}

	if cleanModcache {
		if cfg.GOMODCACHE == "" {
			base.Fatalf("go: cannot clean -modcache without a module cache")
		}
		if cfg.BuildN || cfg.BuildX {
			b.Showcmd("", "rm -rf %s", cfg.GOMODCACHE)
		}
		if !cfg.BuildN {
			if err := modfetch.RemoveAll(cfg.GOMODCACHE); err != nil {
				base.Errorf("go: %v", err)
			}
		}
	}
}

var cleaned = map[*load.Package]bool{}

// TODO: These are dregs left by Makefile-based builds.
// Eventually, can stop deleting these.
var cleanDir = map[string]bool{
	"_test": true,
	"_obj":  true,
}

var cleanFile = map[string]bool{
	"_testmain.go": true,
	"test.out":     true,
	"build.out":    true,
	"a.out":        true,
}

var cleanExt = map[string]bool{
	".5":  true,
	".6":  true,
	".8":  true,
	".a":  true,
	".o":  true,
	".so": true,
}

func clean(p *load.Package) {
	if cleaned[p] {
		return
	}
	cleaned[p] = true

	if p.Dir == "" {
		base.Errorf("%v", p.Error)
		return
	}
	dirs, err := os.ReadDir(p.Dir)
	if err != nil {
		base.Errorf("go: %s: %v", p.Dir, err)
		return
	}

	var b work.Builder
	b.Print = fmt.Print

	packageFile := map[string]bool{}
	if p.Name != "main" {
		// Record which files are not in package main.
		// The others are.
		keep := func(list []string) {
			for _, f := range list {
				packageFile[f] = true
			}
		}
		keep(p.GoFiles)
		keep(p.CgoFiles)
		keep(p.TestGoFiles)
		keep(p.XTestGoFiles)
	}

	_, elem := filepath.Split(p.Dir)
	var allRemove []string

	// Remove dir-named executable only if this is package main.
	if p.Name == "main" {
		allRemove = append(allRemove,
			elem,
			elem+".exe",
			p.DefaultExecName(),
			p.DefaultExecName()+".exe",
		)
	}

	// Remove package test executables.
	allRemove = append(allRemove,
		elem+".test",
		elem+".test.exe",
		p.DefaultExecName()+".test",
		p.DefaultExecName()+".test.exe",
	)

	// Remove a potential executable, test executable for each .go file in the directory that
	// is not part of the directory's package.
	for _, dir := range dirs {
		name := dir.Name()
		if packageFile[name] {
			continue
		}

		if dir.IsDir() {
			continue
		}

		if strings.HasSuffix(name, "_test.go") {
			base := name[:len(name)-len("_test.go")]
			allRemove = append(allRemove, base+".test", base+".test.exe")
		}

		if strings.HasSuffix(name, ".go") {
			// TODO(adg,rsc): check that this .go file is actually
			// in "package main", and therefore capable of building
			// to an executable file.
			base := name[:len(name)-len(".go")]
			allRemove = append(allRemove, base, base+".exe")
		}
	}

	if cfg.BuildN || cfg.BuildX {
		b.Showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
	}

	toRemove := map[string]bool{}
	for _, name := range allRemove {
		toRemove[name] = true
	}
	for _, dir := range dirs {
		name := dir.Name()
		if dir.IsDir() {
			// TODO: Remove once Makefiles are forgotten.
			if cleanDir[name] {
				if cfg.BuildN || cfg.BuildX {
					b.Showcmd(p.Dir, "rm -r %s", name)
					if cfg.BuildN {
						continue
					}
				}
				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
					base.Errorf("go: %v", err)
				}
			}
			continue
		}

		if cfg.BuildN {
			continue
		}

		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
			removeFile(filepath.Join(p.Dir, name))
		}
	}

	if cleanI && p.Target != "" {
		if cfg.BuildN || cfg.BuildX {
			b.Showcmd("", "rm -f %s", p.Target)
		}
		if !cfg.BuildN {
			removeFile(p.Target)
		}
	}

	if cleanR {
		for _, p1 := range p.Internal.Imports {
			clean(p1)
		}
	}
}

// removeFile tries to remove file f, if error other than file doesn't exist
// occurs, it will report the error.
func removeFile(f string) {
	err := os.Remove(f)
	if err == nil || os.IsNotExist(err) {
		return
	}
	// Windows does not allow deletion of a binary file while it is executing.
	if base.ToolIsWindows {
		// Remove lingering ~ file from last attempt.
		if _, err2 := os.Stat(f + "~"); err2 == nil {
			os.Remove(f + "~")
		}
		// Try to move it out of the way. If the move fails,
		// which is likely, we'll try again the
		// next time we do an install of this binary.
		if err2 := os.Rename(f, f+"~"); err2 == nil {
			os.Remove(f + "~")
			return
		}
	}
	base.Errorf("go: %v", err)
}