github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/core/utils.go (about)

     1  package core
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/rand"
     7  	"crypto/sha1"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"os/exec"
    13  	"path"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strings"
    17  	"sync"
    18  	"syscall"
    19  	"time"
    20  
    21  	"cli"
    22  	"fs"
    23  )
    24  
    25  // RepoRoot is the root of the Please repository
    26  var RepoRoot string
    27  
    28  // initialWorkingDir is the directory we began in. Early on we chdir() to the repo root but for
    29  // some things we need to remember this.
    30  var initialWorkingDir string
    31  
    32  // initialPackage is the initial subdir of the working directory, ie. what package did we start in.
    33  // This is similar but not identical to initialWorkingDir.
    34  var initialPackage string
    35  
    36  // usingBazelWorkspace is true if we detected a Bazel WORKSPACE file to find our repo root.
    37  var usingBazelWorkspace bool
    38  
    39  // DirPermissions are the default permission bits we apply to directories.
    40  const DirPermissions = os.ModeDir | 0775
    41  
    42  // FindRepoRoot returns the root directory of the current repo and sets the initial working dir.
    43  // It returns true if the repo root was found.
    44  func FindRepoRoot() bool {
    45  	initialWorkingDir, _ = os.Getwd()
    46  	RepoRoot, initialPackage = getRepoRoot(ConfigFileName, false)
    47  	return RepoRoot != ""
    48  }
    49  
    50  // MustFindRepoRoot returns the root directory of the current repo and sets the initial working dir.
    51  // It dies on failure, although will fall back to looking for a Bazel WORKSPACE file first.
    52  func MustFindRepoRoot() string {
    53  	if RepoRoot != "" {
    54  		return RepoRoot
    55  	}
    56  	if !FindRepoRoot() {
    57  		RepoRoot, initialPackage = getRepoRoot("WORKSPACE", true)
    58  		log.Warning("No .plzconfig file found to define the repo root.")
    59  		log.Warning("Falling back to Bazel WORKSPACE at %s", path.Join(RepoRoot, "WORKSPACE"))
    60  		usingBazelWorkspace = true
    61  	}
    62  	return RepoRoot
    63  }
    64  
    65  // InitialPackage returns a label corresponding to the initial package we started in.
    66  func InitialPackage() []BuildLabel {
    67  	// It's possible to start off in directories that aren't legal package names, because
    68  	// our package naming is stricter than directory naming requirements.
    69  	// In that case move up until we find somewhere we can run from.
    70  	dir := initialPackage
    71  	for dir != "." {
    72  		if label, err := TryNewBuildLabel(dir, "test"); err == nil {
    73  			label.Name = "..."
    74  			return []BuildLabel{label}
    75  		}
    76  		dir = filepath.Dir(dir)
    77  	}
    78  	return WholeGraph
    79  }
    80  
    81  // getRepoRoot returns the root directory of the current repo and the initial package.
    82  func getRepoRoot(filename string, die bool) (string, string) {
    83  	dir, err := os.Getwd()
    84  	if err != nil {
    85  		log.Fatalf("Couldn't determine working directory: %s", err)
    86  	}
    87  	// Walk up directories looking for a .plzconfig file, which we use to identify the root.
    88  	initial := dir
    89  	for dir != "" {
    90  		if PathExists(path.Join(dir, filename)) {
    91  			return dir, strings.TrimLeft(initial[len(dir):], "/")
    92  		}
    93  		dir, _ = path.Split(dir)
    94  		dir = strings.TrimRight(dir, "/")
    95  	}
    96  	if die {
    97  		log.Fatalf("Couldn't locate the repo root. Are you sure you're inside a plz repo?")
    98  	}
    99  	return "", ""
   100  }
   101  
   102  // StartedAtRepoRoot returns true if the build was initiated from the repo root.
   103  // Used to provide slightly nicer output in some places.
   104  func StartedAtRepoRoot() bool {
   105  	return RepoRoot == initialWorkingDir
   106  }
   107  
   108  // RecursiveCopyFile copies either a single file or a directory.
   109  // If 'link' is true then we'll hardlink files instead of copying them.
   110  // If 'fallback' is true then we'll fall back to a copy if linking fails.
   111  func RecursiveCopyFile(from string, to string, mode os.FileMode, link, fallback bool) error {
   112  	if info, err := os.Stat(from); err == nil && info.IsDir() {
   113  		return fs.WalkMode(from, func(name string, isDir bool, fileMode os.FileMode) error {
   114  			dest := path.Join(to, name[len(from):])
   115  			if isDir {
   116  				return os.MkdirAll(dest, DirPermissions)
   117  			}
   118  			return fs.CopyOrLinkFile(name, dest, mode, link, fallback)
   119  		})
   120  	}
   121  	return fs.CopyOrLinkFile(from, to, mode, link, fallback)
   122  }
   123  
   124  // safeBuffer is an io.Writer that ensures that only one thread writes to it at a time.
   125  // This is important because we potentially have both stdout and stderr writing to the same
   126  // buffer, and os.exec only guarantees goroutine-safety if both are the same writer, which in
   127  // our case they're not (but are both ultimately causing writes to the same buffer)
   128  type safeBuffer struct {
   129  	sync.Mutex
   130  	buf bytes.Buffer
   131  }
   132  
   133  func (sb *safeBuffer) Write(b []byte) (int, error) {
   134  	sb.Lock()
   135  	defer sb.Unlock()
   136  	return sb.buf.Write(b)
   137  }
   138  
   139  func (sb *safeBuffer) Bytes() []byte {
   140  	return sb.buf.Bytes()
   141  }
   142  
   143  func (sb *safeBuffer) String() string {
   144  	return sb.buf.String()
   145  }
   146  
   147  // logProgress logs a message once a minute until the given context has expired.
   148  // Used to provide some notion of progress while waiting for external commands.
   149  func logProgress(ctx context.Context, target *BuildTarget) {
   150  	t := time.NewTicker(1 * time.Minute)
   151  	defer t.Stop()
   152  	for i := 1; i < 1000000; i++ {
   153  		select {
   154  		case <-ctx.Done():
   155  			return
   156  		case <-t.C:
   157  			if i == 1 {
   158  				log.Notice("%s still running after 1 minute %s", target.Label, progressMessage(target))
   159  			} else {
   160  				log.Notice("%s still running after %d minutes %s", target.Label, i, progressMessage(target))
   161  			}
   162  		}
   163  	}
   164  }
   165  
   166  // progressMessage displays a progress message for a target, if it tracks progress.
   167  func progressMessage(target *BuildTarget) string {
   168  	if target.ShowProgress {
   169  		return fmt.Sprintf("(%0.1f%% done)", target.Progress)
   170  	}
   171  	return ""
   172  }
   173  
   174  // ExecWithTimeout runs an external command with a timeout.
   175  // If the command times out the returned error will be a context.DeadlineExceeded error.
   176  // If showOutput is true then output will be printed to stderr as well as returned.
   177  // It returns the stdout only, combined stdout and stderr and any error that occurred.
   178  func ExecWithTimeout(target *BuildTarget, dir string, env []string, timeout time.Duration, defaultTimeout cli.Duration, showOutput, attachStdStreams bool, argv []string) ([]byte, []byte, error) {
   179  	if timeout == 0 {
   180  		if defaultTimeout == 0 {
   181  			timeout = 10 * time.Minute
   182  		} else {
   183  			timeout = time.Duration(defaultTimeout)
   184  		}
   185  	}
   186  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   187  	defer cancel()
   188  	cmd := ExecCommand(argv[0], argv[1:]...)
   189  	cmd.Dir = dir
   190  	cmd.Env = env
   191  
   192  	var out bytes.Buffer
   193  	var outerr safeBuffer
   194  	if showOutput {
   195  		cmd.Stdout = io.MultiWriter(os.Stderr, &out, &outerr)
   196  		cmd.Stderr = io.MultiWriter(os.Stderr, &outerr)
   197  	} else {
   198  		cmd.Stdout = io.MultiWriter(&out, &outerr)
   199  		cmd.Stderr = &outerr
   200  	}
   201  	if target != nil && target.ShowProgress {
   202  		cmd.Stdout = newProgressWriter(target, cmd.Stdout)
   203  		cmd.Stderr = newProgressWriter(target, cmd.Stderr)
   204  	}
   205  	if attachStdStreams {
   206  		cmd.Stdin = os.Stdin
   207  		cmd.Stdout = os.Stdout
   208  		cmd.Stderr = os.Stderr
   209  	}
   210  	if target != nil {
   211  		go logProgress(ctx, target)
   212  	}
   213  	// Start the command, wait for the timeout & then kill it.
   214  	// We deliberately don't use CommandContext because it will only send SIGKILL which
   215  	// child processes can't handle themselves.
   216  	err := cmd.Start()
   217  	if err != nil {
   218  		return nil, nil, err
   219  	}
   220  	ch := make(chan error)
   221  	go runCommand(cmd, ch)
   222  	select {
   223  	case err = <-ch:
   224  		// Do nothing.
   225  	case <-time.After(timeout):
   226  		// Send a relatively gentle signal that it can catch.
   227  		if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
   228  			log.Notice("Failed to kill subprocess: %s", err)
   229  		}
   230  		time.Sleep(10 * time.Millisecond)
   231  		// Send a more forceful signal.
   232  		cmd.Process.Kill()
   233  		err = fmt.Errorf("Timeout exceeded: %s", outerr.String())
   234  	}
   235  	return out.Bytes(), outerr.Bytes(), err
   236  }
   237  
   238  // runCommand runs a command and signals on the given channel when it's done.
   239  func runCommand(cmd *exec.Cmd, ch chan error) {
   240  	ch <- cmd.Wait()
   241  }
   242  
   243  // ExecWithTimeoutShell runs an external command within a Bash shell.
   244  // Other arguments are as ExecWithTimeout.
   245  // Note that the command is deliberately a single string.
   246  func ExecWithTimeoutShell(state *BuildState, target *BuildTarget, dir string, env []string, timeout time.Duration, defaultTimeout cli.Duration, showOutput bool, cmd string, sandbox bool) ([]byte, []byte, error) {
   247  	return ExecWithTimeoutShellStdStreams(state, target, dir, env, timeout, defaultTimeout, showOutput, cmd, sandbox, false)
   248  }
   249  
   250  // ExecWithTimeoutShellStdStreams is as ExecWithTimeoutShell but optionally attaches stdin to the subprocess.
   251  func ExecWithTimeoutShellStdStreams(state *BuildState, target *BuildTarget, dir string, env []string, timeout time.Duration, defaultTimeout cli.Duration, showOutput bool, cmd string, sandbox, attachStdStreams bool) ([]byte, []byte, error) {
   252  	c := append([]string{"bash", "-u", "-o", "pipefail", "-c"}, cmd)
   253  	// Runtime check is a little ugly, but we know this only works on Linux right now.
   254  	if sandbox && runtime.GOOS == "linux" {
   255  		tool, err := LookPath(state.Config.Build.PleaseSandboxTool, state.Config.Build.Path)
   256  		if err != nil {
   257  			return nil, nil, err
   258  		}
   259  		c = append([]string{tool}, c...)
   260  	}
   261  	return ExecWithTimeout(target, dir, env, timeout, defaultTimeout, showOutput, attachStdStreams, c)
   262  }
   263  
   264  // ExecWithTimeoutSimple runs an external command with a timeout.
   265  // It's a simpler version of ExecWithTimeout that gives less control.
   266  func ExecWithTimeoutSimple(timeout cli.Duration, cmd ...string) ([]byte, error) {
   267  	_, out, err := ExecWithTimeout(nil, "", nil, time.Duration(timeout), timeout, false, false, cmd)
   268  	return out, err
   269  }
   270  
   271  // A SourcePair represents a source file with its source and temporary locations.
   272  // This isn't typically used much by callers; it's just useful to have a single type for channels.
   273  type SourcePair struct{ Src, Tmp string }
   274  
   275  // IterSources returns all the sources for a function, allowing for sources that are other rules
   276  // and rules that require transitive dependencies.
   277  // Yielded values are pairs of the original source location and its temporary location for this rule.
   278  func IterSources(graph *BuildGraph, target *BuildTarget) <-chan SourcePair {
   279  	ch := make(chan SourcePair)
   280  	done := map[BuildLabel]bool{}
   281  	donePaths := map[string]bool{}
   282  	tmpDir := target.TmpDir()
   283  	var inner func(dependency *BuildTarget)
   284  	inner = func(dependency *BuildTarget) {
   285  		sources := dependency.AllSources()
   286  		if target == dependency {
   287  			// This is the current build rule, so link its sources.
   288  			for _, source := range sources {
   289  				for _, providedSource := range recursivelyProvideSource(graph, target, source) {
   290  					fullPaths := providedSource.FullPaths(graph)
   291  					for i, sourcePath := range providedSource.Paths(graph) {
   292  						tmpPath := path.Join(tmpDir, sourcePath)
   293  						ch <- SourcePair{fullPaths[i], tmpPath}
   294  						donePaths[tmpPath] = true
   295  					}
   296  				}
   297  			}
   298  		} else {
   299  			// This is a dependency of the rule, so link its outputs.
   300  			outDir := dependency.OutDir()
   301  			for _, dep := range dependency.Outputs() {
   302  				depPath := path.Join(outDir, dep)
   303  				pkgName := dependency.Subrepo.MakeRelativeName(dependency.Label.PackageName)
   304  				tmpPath := path.Join(tmpDir, pkgName, dep)
   305  				if !donePaths[tmpPath] {
   306  					ch <- SourcePair{depPath, tmpPath}
   307  					donePaths[tmpPath] = true
   308  				}
   309  			}
   310  			// Mark any label-type outputs as done.
   311  			for _, out := range dependency.DeclaredOutputs() {
   312  				if LooksLikeABuildLabel(out) {
   313  					label := ParseBuildLabel(out, target.Label.PackageName)
   314  					done[label] = true
   315  				}
   316  			}
   317  		}
   318  		// All the sources of this rule now count as done.
   319  		for _, source := range sources {
   320  			if label := source.Label(); label != nil && dependency.IsSourceOnlyDep(*label) {
   321  				done[*label] = true
   322  			}
   323  		}
   324  
   325  		done[dependency.Label] = true
   326  		if target == dependency || (target.NeedsTransitiveDependencies && !dependency.OutputIsComplete) {
   327  			for _, dep := range dependency.BuildDependencies() {
   328  				for _, dep2 := range recursivelyProvideFor(graph, target, dependency, dep.Label) {
   329  					if !done[dep2] && !dependency.IsTool(dep2) {
   330  						inner(graph.TargetOrDie(dep2))
   331  					}
   332  				}
   333  			}
   334  		} else {
   335  			for _, dep := range dependency.ExportedDependencies() {
   336  				for _, dep2 := range recursivelyProvideFor(graph, target, dependency, dep) {
   337  					if !done[dep2] {
   338  						inner(graph.TargetOrDie(dep2))
   339  					}
   340  				}
   341  			}
   342  		}
   343  	}
   344  	go func() {
   345  		inner(target)
   346  		close(ch)
   347  	}()
   348  	return ch
   349  }
   350  
   351  // recursivelyProvideFor recursively applies ProvideFor to a target.
   352  func recursivelyProvideFor(graph *BuildGraph, target, dependency *BuildTarget, dep BuildLabel) []BuildLabel {
   353  	depTarget := graph.TargetOrDie(dep)
   354  	ret := depTarget.ProvideFor(dependency)
   355  	if len(ret) == 1 && ret[0] == dep {
   356  		// Dependency doesn't have a require/provide directly on this guy, up to the top-level
   357  		// target. We have to check the dep first to keep things consistent with what targets
   358  		// have actually been built.
   359  		ret = depTarget.ProvideFor(target)
   360  		if len(ret) == 1 && ret[0] == dep {
   361  			return ret
   362  		}
   363  	}
   364  	ret2 := make([]BuildLabel, 0, len(ret))
   365  	for _, r := range ret {
   366  		if r == dep {
   367  			ret2 = append(ret2, r) // Providing itself, don't recurse
   368  		} else {
   369  			ret2 = append(ret2, recursivelyProvideFor(graph, target, dependency, r)...)
   370  		}
   371  	}
   372  	return ret2
   373  }
   374  
   375  // recursivelyProvideSource is similar to recursivelyProvideFor but operates on a BuildInput.
   376  func recursivelyProvideSource(graph *BuildGraph, target *BuildTarget, src BuildInput) []BuildInput {
   377  	if label := src.nonOutputLabel(); label != nil {
   378  		dep := graph.TargetOrDie(*label)
   379  		provided := recursivelyProvideFor(graph, target, target, dep.Label)
   380  		ret := make([]BuildInput, len(provided))
   381  		for i, p := range provided {
   382  			ret[i] = p
   383  		}
   384  		return ret
   385  	}
   386  	return []BuildInput{src}
   387  }
   388  
   389  // IterRuntimeFiles yields all the runtime files for a rule (outputs & data files), similar to above.
   390  func IterRuntimeFiles(graph *BuildGraph, target *BuildTarget, absoluteOuts bool) <-chan SourcePair {
   391  	done := map[string]bool{}
   392  	ch := make(chan SourcePair)
   393  
   394  	makeOut := func(out string) string {
   395  		if absoluteOuts {
   396  			return path.Join(RepoRoot, target.TestDir(), out)
   397  		}
   398  		return out
   399  	}
   400  
   401  	pushOut := func(src, out string) {
   402  		out = makeOut(out)
   403  		if !done[out] {
   404  			ch <- SourcePair{src, out}
   405  			done[out] = true
   406  		}
   407  	}
   408  
   409  	var inner func(*BuildTarget)
   410  	inner = func(target *BuildTarget) {
   411  		outDir := target.OutDir()
   412  		for _, out := range target.Outputs() {
   413  			pushOut(path.Join(outDir, out), out)
   414  		}
   415  		for _, data := range target.Data {
   416  			var subrepo *Subrepo
   417  			label := data.Label()
   418  			if label != nil {
   419  				subrepo = graph.TargetOrDie(*label).Subrepo
   420  			}
   421  			fullPaths := data.FullPaths(graph)
   422  			for i, dataPath := range data.Paths(graph) {
   423  				pushOut(fullPaths[i], subrepo.MakeRelativeName(dataPath))
   424  			}
   425  			if label != nil {
   426  				for _, dep := range graph.TargetOrDie(*label).ExportedDependencies() {
   427  					inner(graph.TargetOrDie(dep))
   428  				}
   429  			}
   430  		}
   431  		for _, dep := range target.ExportedDependencies() {
   432  			inner(graph.TargetOrDie(dep))
   433  		}
   434  	}
   435  	go func() {
   436  		inner(target)
   437  		close(ch)
   438  	}()
   439  	return ch
   440  }
   441  
   442  // IterInputPaths yields all the transitive input files for a rule (sources & data files), similar to above (again).
   443  func IterInputPaths(graph *BuildGraph, target *BuildTarget) <-chan string {
   444  	// Use a couple of maps to protect us from dep-graph loops and to stop parsing the same target
   445  	// multiple times. We also only want to push files to the channel that it has not already seen.
   446  	donePaths := map[string]bool{}
   447  	doneTargets := map[*BuildTarget]bool{}
   448  	ch := make(chan string)
   449  	var inner func(*BuildTarget)
   450  	inner = func(target *BuildTarget) {
   451  		if !doneTargets[target] {
   452  			// First yield all the sources of the target only ever pushing declared paths to
   453  			// the channel to prevent us outputting any intermediate files.
   454  			for _, source := range target.AllSources() {
   455  				// If the label is nil add any input paths contained here.
   456  				if label := source.nonOutputLabel(); label == nil {
   457  					for _, sourcePath := range source.FullPaths(graph) {
   458  						if !donePaths[sourcePath] {
   459  							ch <- sourcePath
   460  							donePaths[sourcePath] = true
   461  						}
   462  					}
   463  					// Otherwise we should recurse for this build label (and gather its sources)
   464  				} else {
   465  					inner(graph.TargetOrDie(*label))
   466  				}
   467  			}
   468  
   469  			// Now yield all the data deps of this rule.
   470  			for _, data := range target.Data {
   471  				// If the label is nil add any input paths contained here.
   472  				if label := data.Label(); label == nil {
   473  					for _, sourcePath := range data.FullPaths(graph) {
   474  						if !donePaths[sourcePath] {
   475  							ch <- sourcePath
   476  							donePaths[sourcePath] = true
   477  						}
   478  					}
   479  					// Otherwise we should recurse for this build label (and gather its sources)
   480  				} else {
   481  					inner(graph.TargetOrDie(*label))
   482  				}
   483  			}
   484  
   485  			// Finally recurse for all the deps of this rule.
   486  			for _, dep := range target.Dependencies() {
   487  				inner(dep)
   488  			}
   489  			doneTargets[target] = true
   490  		}
   491  	}
   492  	go func() {
   493  		inner(target)
   494  		close(ch)
   495  	}()
   496  	return ch
   497  }
   498  
   499  // PrepareSource symlinks a single source file for a build rule.
   500  func PrepareSource(sourcePath string, tmpPath string) error {
   501  	dir := path.Dir(tmpPath)
   502  	if !PathExists(dir) {
   503  		if err := os.MkdirAll(dir, DirPermissions); err != nil {
   504  			return err
   505  		}
   506  	}
   507  	if !PathExists(sourcePath) {
   508  		return fmt.Errorf("Source file %s doesn't exist", sourcePath)
   509  	}
   510  	return RecursiveCopyFile(sourcePath, tmpPath, 0, true, true)
   511  }
   512  
   513  // PrepareSourcePair prepares a source file for a build.
   514  func PrepareSourcePair(pair SourcePair) error {
   515  	if path.IsAbs(pair.Src) {
   516  		return PrepareSource(pair.Src, pair.Tmp)
   517  	}
   518  	return PrepareSource(path.Join(RepoRoot, pair.Src), pair.Tmp)
   519  }
   520  
   521  // CollapseHash combines our usual four-part hash into one by XOR'ing them together.
   522  // This helps keep things short in places where sometimes we get complaints about filenames being
   523  // too long (this is most noticeable on e.g. Ubuntu with an encrypted home directory, but
   524  // not an entire encrypted disk) and where we don't especially care about breaking out the
   525  // individual parts of hashes, which is important for many parts of the system.
   526  func CollapseHash(key []byte) []byte {
   527  	short := [sha1.Size]byte{}
   528  	// We store the rule hash twice, if it's repeated we must make sure not to xor it
   529  	// against itself.
   530  	if bytes.Equal(key[0:sha1.Size], key[sha1.Size:2*sha1.Size]) {
   531  		for i := 0; i < sha1.Size; i++ {
   532  			short[i] = key[i] ^ key[i+2*sha1.Size] ^ key[i+3*sha1.Size]
   533  		}
   534  	} else {
   535  		for i := 0; i < sha1.Size; i++ {
   536  			short[i] = key[i] ^ key[i+sha1.Size] ^ key[i+2*sha1.Size] ^ key[i+3*sha1.Size]
   537  		}
   538  	}
   539  	return short[:]
   540  }
   541  
   542  // LookPath does roughly the same as exec.LookPath, i.e. looks for the named file on the path.
   543  // The main difference is that it looks based on our config which isn't necessarily the same
   544  // as the external environment variable.
   545  func LookPath(filename string, paths []string) (string, error) {
   546  	for _, p := range paths {
   547  		for _, p2 := range strings.Split(p, ":") {
   548  			p3 := path.Join(p2, filename)
   549  			if _, err := os.Stat(p3); err == nil {
   550  				return p3, nil
   551  			}
   552  		}
   553  	}
   554  	return "", fmt.Errorf("%s not found in PATH %s", filename, strings.Join(paths, ":"))
   555  }
   556  
   557  // AsyncDeleteDir deletes a directory asynchronously.
   558  // First it renames the directory to something temporary and then forks to delete it.
   559  // The rename is done synchronously but the actual deletion is async (after fork) so
   560  // you don't have to wait for large directories to be removed.
   561  // Conversely there is obviously no guarantee about at what point it will actually cease to
   562  // be on disk any more.
   563  func AsyncDeleteDir(dir string) error {
   564  	rm, err := exec.LookPath("rm")
   565  	if err != nil {
   566  		return err
   567  	} else if !PathExists(dir) {
   568  		return nil // not an error, just don't need to do anything.
   569  	}
   570  	newDir, err := moveDir(dir)
   571  	if err != nil {
   572  		return err
   573  	}
   574  	// Note that we can't fork() directly and continue running Go code, but ForkExec() works okay.
   575  	// Hence why we're using rm rather than fork() + os.RemoveAll.
   576  	_, err = syscall.ForkExec(rm, []string{rm, "-rf", newDir}, nil)
   577  	return err
   578  }
   579  
   580  // moveDir moves a directory to a new location and returns that new location.
   581  func moveDir(dir string) (string, error) {
   582  	b := make([]byte, 16)
   583  	rand.Read(b)
   584  	name := path.Join(path.Dir(dir), ".plz_clean_"+hex.EncodeToString(b))
   585  	log.Notice("Moving %s to %s", dir, name)
   586  	return name, os.Rename(dir, name)
   587  }
   588  
   589  // PathExists is an alias to fs.PathExists.
   590  // TODO(peterebden): Remove and migrate everything over.
   591  func PathExists(filename string) bool {
   592  	return fs.PathExists(filename)
   593  }