Home | History | Annotate | Download | only in go
      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 main
      6 
      7 import (
      8 	"fmt"
      9 	"io/ioutil"
     10 	"os"
     11 	"path/filepath"
     12 	"strings"
     13 )
     14 
     15 var cmdClean = &Command{
     16 	UsageLine: "clean [-i] [-r] [-n] [-x] [build flags] [packages]",
     17 	Short:     "remove object files",
     18 	Long: `
     19 Clean removes object files from package source directories.
     20 The go command builds most objects in a temporary directory,
     21 so go clean is mainly concerned with object files left by other
     22 tools or by manual invocations of go build.
     23 
     24 Specifically, clean removes the following files from each of the
     25 source directories corresponding to the import paths:
     26 
     27 	_obj/            old object directory, left from Makefiles
     28 	_test/           old test directory, left from Makefiles
     29 	_testmain.go     old gotest file, left from Makefiles
     30 	test.out         old test log, left from Makefiles
     31 	build.out        old test log, left from Makefiles
     32 	*.[568ao]        object files, left from Makefiles
     33 
     34 	DIR(.exe)        from go build
     35 	DIR.test(.exe)   from go test -c
     36 	MAINFILE(.exe)   from go build MAINFILE.go
     37 	*.so             from SWIG
     38 
     39 In the list, DIR represents the final path element of the
     40 directory, and MAINFILE is the base name of any Go source
     41 file in the directory that is not included when building
     42 the package.
     43 
     44 The -i flag causes clean to remove the corresponding installed
     45 archive or binary (what 'go install' would create).
     46 
     47 The -n flag causes clean to print the remove commands it would execute,
     48 but not run them.
     49 
     50 The -r flag causes clean to be applied recursively to all the
     51 dependencies of the packages named by the import paths.
     52 
     53 The -x flag causes clean to print remove commands as it executes them.
     54 
     55 For more about build flags, see 'go help build'.
     56 
     57 For more about specifying packages, see 'go help packages'.
     58 	`,
     59 }
     60 
     61 var cleanI bool // clean -i flag
     62 var cleanR bool // clean -r flag
     63 
     64 func init() {
     65 	// break init cycle
     66 	cmdClean.Run = runClean
     67 
     68 	cmdClean.Flag.BoolVar(&cleanI, "i", false, "")
     69 	cmdClean.Flag.BoolVar(&cleanR, "r", false, "")
     70 	// -n and -x are important enough to be
     71 	// mentioned explicitly in the docs but they
     72 	// are part of the build flags.
     73 
     74 	addBuildFlags(cmdClean)
     75 }
     76 
     77 func runClean(cmd *Command, args []string) {
     78 	for _, pkg := range packagesAndErrors(args) {
     79 		clean(pkg)
     80 	}
     81 }
     82 
     83 var cleaned = map[*Package]bool{}
     84 
     85 // TODO: These are dregs left by Makefile-based builds.
     86 // Eventually, can stop deleting these.
     87 var cleanDir = map[string]bool{
     88 	"_test": true,
     89 	"_obj":  true,
     90 }
     91 
     92 var cleanFile = map[string]bool{
     93 	"_testmain.go": true,
     94 	"test.out":     true,
     95 	"build.out":    true,
     96 	"a.out":        true,
     97 }
     98 
     99 var cleanExt = map[string]bool{
    100 	".5":  true,
    101 	".6":  true,
    102 	".8":  true,
    103 	".a":  true,
    104 	".o":  true,
    105 	".so": true,
    106 }
    107 
    108 func clean(p *Package) {
    109 	if cleaned[p] {
    110 		return
    111 	}
    112 	cleaned[p] = true
    113 
    114 	if p.Dir == "" {
    115 		errorf("can't load package: %v", p.Error)
    116 		return
    117 	}
    118 	dirs, err := ioutil.ReadDir(p.Dir)
    119 	if err != nil {
    120 		errorf("go clean %s: %v", p.Dir, err)
    121 		return
    122 	}
    123 
    124 	var b builder
    125 	b.print = fmt.Print
    126 
    127 	packageFile := map[string]bool{}
    128 	if p.Name != "main" {
    129 		// Record which files are not in package main.
    130 		// The others are.
    131 		keep := func(list []string) {
    132 			for _, f := range list {
    133 				packageFile[f] = true
    134 			}
    135 		}
    136 		keep(p.GoFiles)
    137 		keep(p.CgoFiles)
    138 		keep(p.TestGoFiles)
    139 		keep(p.XTestGoFiles)
    140 	}
    141 
    142 	_, elem := filepath.Split(p.Dir)
    143 	var allRemove []string
    144 
    145 	// Remove dir-named executable only if this is package main.
    146 	if p.Name == "main" {
    147 		allRemove = append(allRemove,
    148 			elem,
    149 			elem+".exe",
    150 		)
    151 	}
    152 
    153 	// Remove package test executables.
    154 	allRemove = append(allRemove,
    155 		elem+".test",
    156 		elem+".test.exe",
    157 	)
    158 
    159 	// Remove a potential executable for each .go file in the directory that
    160 	// is not part of the directory's package.
    161 	for _, dir := range dirs {
    162 		name := dir.Name()
    163 		if packageFile[name] {
    164 			continue
    165 		}
    166 		if !dir.IsDir() && strings.HasSuffix(name, ".go") {
    167 			// TODO(adg,rsc): check that this .go file is actually
    168 			// in "package main", and therefore capable of building
    169 			// to an executable file.
    170 			base := name[:len(name)-len(".go")]
    171 			allRemove = append(allRemove, base, base+".exe")
    172 		}
    173 	}
    174 
    175 	if buildN || buildX {
    176 		b.showcmd(p.Dir, "rm -f %s", strings.Join(allRemove, " "))
    177 	}
    178 
    179 	toRemove := map[string]bool{}
    180 	for _, name := range allRemove {
    181 		toRemove[name] = true
    182 	}
    183 	for _, dir := range dirs {
    184 		name := dir.Name()
    185 		if dir.IsDir() {
    186 			// TODO: Remove once Makefiles are forgotten.
    187 			if cleanDir[name] {
    188 				if buildN || buildX {
    189 					b.showcmd(p.Dir, "rm -r %s", name)
    190 					if buildN {
    191 						continue
    192 					}
    193 				}
    194 				if err := os.RemoveAll(filepath.Join(p.Dir, name)); err != nil {
    195 					errorf("go clean: %v", err)
    196 				}
    197 			}
    198 			continue
    199 		}
    200 
    201 		if buildN {
    202 			continue
    203 		}
    204 
    205 		if cleanFile[name] || cleanExt[filepath.Ext(name)] || toRemove[name] {
    206 			removeFile(filepath.Join(p.Dir, name))
    207 		}
    208 	}
    209 
    210 	if cleanI && p.target != "" {
    211 		if buildN || buildX {
    212 			b.showcmd("", "rm -f %s", p.target)
    213 		}
    214 		if !buildN {
    215 			removeFile(p.target)
    216 		}
    217 	}
    218 
    219 	if cleanR {
    220 		for _, p1 := range p.imports {
    221 			clean(p1)
    222 		}
    223 	}
    224 }
    225 
    226 // removeFile tries to remove file f, if error other than file doesn't exist
    227 // occurs, it will report the error.
    228 func removeFile(f string) {
    229 	err := os.Remove(f)
    230 	if err == nil || os.IsNotExist(err) {
    231 		return
    232 	}
    233 	// Windows does not allow deletion of a binary file while it is executing.
    234 	if toolIsWindows {
    235 		// Remove lingering ~ file from last attempt.
    236 		if _, err2 := os.Stat(f + "~"); err2 == nil {
    237 			os.Remove(f + "~")
    238 		}
    239 		// Try to move it out of the way. If the move fails,
    240 		// which is likely, we'll try again the
    241 		// next time we do an install of this binary.
    242 		if err2 := os.Rename(f, f+"~"); err2 == nil {
    243 			os.Remove(f + "~")
    244 			return
    245 		}
    246 	}
    247 	errorf("go clean: %v", err)
    248 }
    249