This libgo patch changes the go tool to remove the work directory on a usage error. We ensure that cmd/go consistently calls base.Exit rather than os.Exit, so that we don't incorrectly leave the work directory around on exit. This is tested by modifying the testsuite to run all the tests with TMPDIR set to a temporary directory, and then check that no files are left behind in that temporary directory. A couple of tests were adjusted to make this approach work. This is for GCC PR 89406. Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu. Committed to mainline.
Ian
Index: gcc/go/gofrontend/MERGE =================================================================== --- gcc/go/gofrontend/MERGE (revision 269079) +++ gcc/go/gofrontend/MERGE (working copy) @@ -1,4 +1,4 @@ -43e458ab704e04cdf347f3e74e0b0eff3de00a3d +4fbd06dc7b1b8fb665293399a2b6d5326435512f The first line of this file holds the git revision number of the last merge done from the gofrontend repository. Index: libgo/go/cmd/go/go_test.go =================================================================== --- libgo/go/cmd/go/go_test.go (revision 269079) +++ libgo/go/cmd/go/go_test.go (working copy) @@ -146,7 +146,18 @@ func TestMain(m *testing.M) { select {} } - dir, err := ioutil.TempDir(os.Getenv("GOTMPDIR"), "cmd-go-test-") + // Run with a temporary TMPDIR to check that the tests don't + // leave anything behind. + topTmpdir, err := ioutil.TempDir("", "cmd-go-test-") + if err != nil { + log.Fatal(err) + } + if !*testWork { + defer removeAll(topTmpdir) + } + os.Setenv(tempEnvName(), topTmpdir) + + dir, err := ioutil.TempDir(topTmpdir, "tmpdir") if err != nil { log.Fatal(err) } @@ -258,6 +269,23 @@ func TestMain(m *testing.M) { removeAll(testTmpDir) // os.Exit won't run defer } + if !*testWork { + // There shouldn't be anything left in topTmpdir. + dirf, err := os.Open(topTmpdir) + if err != nil { + log.Fatal(err) + } + names, err := dirf.Readdirnames(0) + if err != nil { + log.Fatal(err) + } + if len(names) > 0 { + log.Fatalf("unexpected files left in tmpdir: %v", names) + } + + removeAll(topTmpdir) + } + os.Exit(r) } @@ -5020,7 +5048,8 @@ func TestExecBuildX(t *testing.T) { obj := tg.path("main") tg.run("build", "-x", "-o", obj, src) sh := tg.path("test.sh") - err := ioutil.WriteFile(sh, []byte("set -e\n"+tg.getStderr()), 0666) + cmds := tg.getStderr() + err := ioutil.WriteFile(sh, []byte("set -e\n"+cmds), 0666) if err != nil { t.Fatal(err) } @@ -5051,6 +5080,12 @@ func TestExecBuildX(t *testing.T) { if string(out) != "hello" { t.Fatalf("got %q; want %q", out, "hello") } + + matches := regexp.MustCompile(`^WORK=(.*)\n`).FindStringSubmatch(cmds) + if len(matches) == 0 { + t.Fatal("no WORK directory") + } + tg.must(os.RemoveAll(matches[1])) } func TestParallelNumber(t *testing.T) { Index: libgo/go/cmd/go/internal/base/base.go =================================================================== --- libgo/go/cmd/go/internal/base/base.go (revision 269079) +++ libgo/go/cmd/go/internal/base/base.go (working copy) @@ -82,7 +82,8 @@ func (c *Command) Name() string { func (c *Command) Usage() { fmt.Fprintf(os.Stderr, "usage: %s\n", c.UsageLine) fmt.Fprintf(os.Stderr, "Run 'go help %s' for details.\n", c.LongName()) - os.Exit(2) + SetExitStatus(2) + Exit() } // Runnable reports whether the command can be run; otherwise Index: libgo/go/cmd/go/internal/cmdflag/flag.go =================================================================== --- libgo/go/cmd/go/internal/cmdflag/flag.go (revision 269079) +++ libgo/go/cmd/go/internal/cmdflag/flag.go (working copy) @@ -66,7 +66,8 @@ func SyntaxError(cmd, msg string) { } else { fmt.Fprintf(os.Stderr, `run "go help %s" for more information`+"\n", cmd) } - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } // AddKnownFlags registers the flags in defns with base.AddKnownFlag. Index: libgo/go/cmd/go/internal/help/help.go =================================================================== --- libgo/go/cmd/go/internal/help/help.go (revision 269079) +++ libgo/go/cmd/go/internal/help/help.go (working copy) @@ -63,7 +63,8 @@ Args: helpSuccess = " " + strings.Join(args[:i], " ") } fmt.Fprintf(os.Stderr, "go help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess) - os.Exit(2) // failed at 'go help cmd' + base.SetExitStatus(2) // failed at 'go help cmd' + base.Exit() } if len(cmd.Commands) > 0 { @@ -167,7 +168,8 @@ func tmpl(w io.Writer, text string, data if ew.err != nil { // I/O error writing. Ignore write on closed pipe. if strings.Contains(ew.err.Error(), "pipe") { - os.Exit(1) + base.SetExitStatus(1) + base.Exit() } base.Fatalf("writing output: %v", ew.err) } Index: libgo/go/cmd/go/internal/vet/vetflag.go =================================================================== --- libgo/go/cmd/go/internal/vet/vetflag.go (revision 269079) +++ libgo/go/cmd/go/internal/vet/vetflag.go (working copy) @@ -76,7 +76,8 @@ func vetFlags(usage func(), args []strin vetcmd.Stdout = out if err := vetcmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "go vet: can't execute %s -flags: %v\n", tool, err) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } var analysisFlags []struct { Name string @@ -85,7 +86,8 @@ func vetFlags(usage func(), args []strin } if err := json.Unmarshal(out.Bytes(), &analysisFlags); err != nil { fmt.Fprintf(os.Stderr, "go vet: can't unmarshal JSON from %s -flags: %v", tool, err) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } // Add vet's flags to vetflagDefn. @@ -134,7 +136,8 @@ func vetFlags(usage func(), args []strin if f == nil { fmt.Fprintf(os.Stderr, "vet: flag %q not defined\n", args[i]) fmt.Fprintf(os.Stderr, "Run \"go help vet\" for more information\n") - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } if f.Value != nil { if err := f.Value.Set(value); err != nil { @@ -182,5 +185,6 @@ func usage() { } fmt.Fprintf(os.Stderr, "Run '%s -help' for the vet tool's flags.\n", cmd) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } Index: libgo/go/cmd/go/internal/work/action.go =================================================================== --- libgo/go/cmd/go/internal/work/action.go (revision 269079) +++ libgo/go/cmd/go/internal/work/action.go (working copy) @@ -249,12 +249,14 @@ func (b *Builder) Init() { if _, ok := cfg.OSArchSupportsCgo[cfg.Goos+"/"+cfg.Goarch]; !ok && cfg.BuildContext.Compiler == "gc" { fmt.Fprintf(os.Stderr, "cmd/go: unsupported GOOS/GOARCH pair %s/%s\n", cfg.Goos, cfg.Goarch) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } for _, tag := range cfg.BuildContext.BuildTags { if strings.Contains(tag, ",") { fmt.Fprintf(os.Stderr, "cmd/go: -tags space-separated list contains comma\n") - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } } } Index: libgo/go/cmd/go/internal/work/exec.go =================================================================== --- libgo/go/cmd/go/internal/work/exec.go (revision 269079) +++ libgo/go/cmd/go/internal/work/exec.go (working copy) @@ -2334,7 +2334,7 @@ func (b *Builder) gccSupportsFlag(compil // version of GCC, so some systems have frozen on it. // Now we pass an empty file on stdin, which should work at least for // GCC and clang. - cmdArgs := str.StringList(compiler, flag, "-c", "-x", "c", "-") + cmdArgs := str.StringList(compiler, flag, "-c", "-x", "c", "-", "-o", os.DevNull) if cfg.BuildN || cfg.BuildX { b.Showcmd(b.WorkDir, "%s || true", joinUnambiguously(cmdArgs)) if cfg.BuildN { Index: libgo/go/cmd/go/internal/work/gccgo.go =================================================================== --- libgo/go/cmd/go/internal/work/gccgo.go (revision 269079) +++ libgo/go/cmd/go/internal/work/gccgo.go (working copy) @@ -56,7 +56,8 @@ func checkGccgoBin() { return } fmt.Fprintf(os.Stderr, "cmd/go: gccgo: %s\n", gccgoErr) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } func (tools gccgoToolchain) gc(b *Builder, a *Action, archive string, importcfg []byte, symabis string, asmhdr bool, gofiles []string) (ofile string, output []byte, err error) { Index: libgo/go/cmd/go/internal/work/init.go =================================================================== --- libgo/go/cmd/go/internal/work/init.go (revision 269079) +++ libgo/go/cmd/go/internal/work/init.go (working copy) @@ -29,7 +29,8 @@ func BuildInit() { p, err := filepath.Abs(cfg.BuildPkgdir) if err != nil { fmt.Fprintf(os.Stderr, "go %s: evaluating -pkgdir: %v\n", flag.Args()[0], err) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } cfg.BuildPkgdir = p } @@ -41,16 +42,19 @@ func instrumentInit() { } if cfg.BuildRace && cfg.BuildMSan { fmt.Fprintf(os.Stderr, "go %s: may not use -race and -msan simultaneously\n", flag.Args()[0]) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } if cfg.BuildMSan && !sys.MSanSupported(cfg.Goos, cfg.Goarch) { fmt.Fprintf(os.Stderr, "-msan is not supported on %s/%s\n", cfg.Goos, cfg.Goarch) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } if cfg.BuildRace { if !sys.RaceDetectorSupported(cfg.Goos, cfg.Goarch) { fmt.Fprintf(os.Stderr, "go %s: -race is only supported on linux/amd64, linux/ppc64le, linux/arm64, freebsd/amd64, netbsd/amd64, darwin/amd64 and windows/amd64\n", flag.Args()[0]) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } } mode := "race" @@ -61,7 +65,8 @@ func instrumentInit() { if !cfg.BuildContext.CgoEnabled { fmt.Fprintf(os.Stderr, "go %s: %s requires cgo; enable cgo by setting CGO_ENABLED=1\n", flag.Args()[0], modeFlag) - os.Exit(2) + base.SetExitStatus(2) + base.Exit() } forcedGcflags = append(forcedGcflags, modeFlag) forcedLdflags = append(forcedLdflags, modeFlag) Index: libgo/go/cmd/go/script_test.go =================================================================== --- libgo/go/cmd/go/script_test.go (revision 269079) +++ libgo/go/cmd/go/script_test.go (working copy) @@ -400,6 +400,7 @@ func (ts *testScript) cmdCc(neg bool, ar var b work.Builder b.Init() ts.cmdExec(neg, append(b.GccCmd(".", ""), args...)) + os.RemoveAll(b.WorkDir) } // cd changes to a different directory.