]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: use os.Executable to find GOROOT
authorDavid Crawshaw <crawshaw@golang.org>
Wed, 3 May 2017 18:46:28 +0000 (14:46 -0400)
committerDavid Crawshaw <crawshaw@golang.org>
Thu, 4 May 2017 13:23:23 +0000 (13:23 +0000)
Before this change, building a GOROOT using make.bash, and then
moving the entire to a new path confused the go tool. Correct
operation of the go tool under these conditions required either
running make.bash again (not always possible if the new location
was owned by a different system user) or setting the GOROOT
environment variable. Setting GOROOT is unfortunate and
discouraged, as it makes it too easy to use the go tool from
one GOROOT and the compiler from another GOROOT.

With this change, the go tool finds its GOROOT relative to its
own location, using os.Executable. It checks it is in a GOROOT
by searching for the GOROOT/pkg/tool directory, to avoid two
plausible situations:

ln -s $GOROOT/bin/go /usr/local/bin/go

and

PATH=$HOME/bin:$PATH
GOPATH=$HOME
ln -s $GOROOT/bin/go $HOME/bin/go

Additionally, if the current executable path is not in a GOROOT,
the tool will follow any symlinks for the executable and check
to see if its original path is a GOROOT.

Fixes #18678

Change-Id: I151d7d449d213164f98193cc176b616849e6332c
Reviewed-on: https://go-review.googlesource.com/42533
Run-TryBot: David Crawshaw <crawshaw@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/cmd/go/go_test.go
src/cmd/go/internal/cfg/cfg.go

index 0b1fe70221e71ef80c17d117e7ab9f165af3f60b..95579b7b225d53d2ecbd78c19063eb10183c337b 100644 (file)
@@ -73,6 +73,11 @@ func init() {
        }
 }
 
+// testGOROOT is the GOROOT to use when running testgo, a cmd/go binary
+// build from this process's current GOROOT, but run from a different
+// (temp) directory.
+var testGOROOT string
+
 // The TestMain function creates a go command for testing purposes and
 // deletes it after the tests have been run.
 func TestMain(m *testing.M) {
@@ -87,6 +92,13 @@ func TestMain(m *testing.M) {
                        os.Exit(2)
                }
 
+               out, err = exec.Command("go", "env", "GOROOT").CombinedOutput()
+               if err != nil {
+                       fmt.Fprintf(os.Stderr, "could not find testing GOROOT: %v\n%s", err, out)
+                       os.Exit(2)
+               }
+               testGOROOT = strings.TrimSpace(string(out))
+
                if out, err := exec.Command("./testgo"+exeSuffix, "env", "CGO_ENABLED").Output(); err != nil {
                        fmt.Fprintf(os.Stderr, "running testgo failed: %v\n", err)
                        canRun = false
@@ -253,6 +265,13 @@ func (tg *testgoData) unsetenv(name string) {
        }
 }
 
+func (tg *testgoData) goTool() string {
+       if tg.wd == "" {
+               return "./testgo" + exeSuffix
+       }
+       return filepath.Join(tg.wd, "testgo"+exeSuffix)
+}
+
 // doRun runs the test go command, recording stdout and stderr and
 // returning exit status.
 func (tg *testgoData) doRun(args []string) error {
@@ -266,13 +285,20 @@ func (tg *testgoData) doRun(args []string) error {
                        }
                }
        }
-       tg.t.Logf("running testgo %v", args)
-       var prog string
-       if tg.wd == "" {
-               prog = "./testgo" + exeSuffix
-       } else {
-               prog = filepath.Join(tg.wd, "testgo"+exeSuffix)
+
+       hasGoroot := false
+       for _, v := range tg.env {
+               if strings.HasPrefix(v, "GOROOT=") {
+                       hasGoroot = true
+                       break
+               }
+       }
+       prog := tg.goTool()
+       if !hasGoroot {
+               tg.setenv("GOROOT", testGOROOT)
        }
+
+       tg.t.Logf("running testgo %v", args)
        cmd := exec.Command(prog, args...)
        tg.stdout.Reset()
        tg.stderr.Reset()
@@ -3897,3 +3923,93 @@ func TestBuildTagsNoComma(t *testing.T) {
        tg.runFail("build", "-tags", "tag1,tag2", "math")
        tg.grepBoth("space-separated list contains comma", "-tags with a comma-separated list didn't error")
 }
+
+func copyFile(src, dst string, perm os.FileMode) error {
+       sf, err := os.Open(src)
+       if err != nil {
+               return err
+       }
+       defer sf.Close()
+
+       df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
+       if err != nil {
+               return err
+       }
+
+       _, err = io.Copy(df, sf)
+       err2 := df.Close()
+       if err != nil {
+               return err
+       }
+       return err2
+}
+
+func TestExecutableGOROOT(t *testing.T) {
+       if runtime.GOOS == "openbsd" {
+               t.Skipf("test case does not work on %s, missing os.Executable", runtime.GOOS)
+       }
+
+       tg := testgo(t)
+       defer tg.cleanup()
+
+       tg.makeTempdir()
+       tg.tempDir("newgoroot/bin")
+       newGoTool := tg.path("newgoroot/bin/go" + exeSuffix)
+       err := copyFile(tg.goTool(), newGoTool, 0775)
+       if err != nil {
+               t.Fatalf("error copying go tool %v", err)
+       }
+
+       goroot := func(goTool string) string {
+               cmd := exec.Command(goTool, "env", "GOROOT")
+               cmd.Env = os.Environ()
+               for i, val := range cmd.Env {
+                       if strings.HasPrefix(val, "GOROOT=") {
+                               cmd.Env = append(cmd.Env[:i], cmd.Env[i+1:]...)
+                               break
+                       }
+               }
+               out, err := cmd.CombinedOutput()
+               if err != nil {
+                       t.Fatalf("copied go tool failed %v: %s", err, out)
+                       return ""
+               }
+               return strings.TrimSpace(string(out))
+       }
+
+       // macOS uses a symlink for /tmp.
+       resolvedTestGOROOT, err := filepath.EvalSymlinks(testGOROOT)
+       if err != nil {
+               t.Fatalf("could not eval testgoroot symlinks: %v", err)
+       }
+
+       // Missing GOROOT/pkg/tool, the go tool should fall back to
+       // its default path.
+       if got, want := goroot(newGoTool), resolvedTestGOROOT; got != want {
+               t.Fatalf("%s env GOROOT = %q, want %q", newGoTool, got, want)
+       }
+
+       // Now the executable's path looks like a GOROOT.
+       tg.tempDir("newgoroot/pkg/tool")
+       if got, want := goroot(newGoTool), tg.path("newgoroot"); got != want {
+               t.Fatalf("%s env GOROOT = %q with pkg/tool, want %q", newGoTool, got, want)
+       }
+
+       switch runtime.GOOS {
+       case "plan9", "windows":
+               t.Skipf("skipping symlink test on %s", runtime.GOOS)
+       }
+
+       tg.tempDir("notgoroot/bin")
+       symGoTool := tg.path("notgoroot/bin/go" + exeSuffix)
+       tg.must(os.Symlink(newGoTool, symGoTool))
+
+       resolvedNewGOROOT, err := filepath.EvalSymlinks(tg.path("newgoroot"))
+       if err != nil {
+               t.Fatalf("could not eval newgoroot symlinks: %v", err)
+       }
+
+       if got, want := goroot(symGoTool), resolvedNewGOROOT; got != want {
+               t.Fatalf("%s env GOROOT = %q, want %q", symGoTool, got, want)
+       }
+}
index 6850fde2be6160296059e113be2b8c9b45d006e8..ea4cea7c66f10e2f0220eaf6e007d3f59ecca03e 100644 (file)
@@ -64,9 +64,44 @@ var (
 )
 
 var (
-       GOROOT    = filepath.Clean(runtime.GOROOT())
+       GOROOT    = findGOROOT()
        GOBIN     = os.Getenv("GOBIN")
        GOROOTbin = filepath.Join(GOROOT, "bin")
        GOROOTpkg = filepath.Join(GOROOT, "pkg")
        GOROOTsrc = filepath.Join(GOROOT, "src")
 )
+
+func findGOROOT() string {
+       if env := os.Getenv("GOROOT"); env != "" {
+               return filepath.Clean(env)
+       }
+       exe, err := os.Executable()
+       if err == nil {
+               exe, err = filepath.Abs(exe)
+               if err == nil {
+                       if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
+                               return dir
+                       }
+                       exe, err = filepath.EvalSymlinks(exe)
+                       if err == nil {
+                               if dir := filepath.Join(exe, "../.."); isGOROOT(dir) {
+                                       return dir
+                               }
+                       }
+               }
+       }
+       return filepath.Clean(runtime.GOROOT())
+}
+
+// isGOROOT reports whether path looks like a GOROOT.
+//
+// It does this by looking for the path/pkg/tool directory,
+// which is necessary for useful operation of the cmd/go tool,
+// and is not typically present in a GOPATH.
+func isGOROOT(path string) bool {
+       stat, err := os.Stat(filepath.Join(path, "pkg", "tool"))
+       if err != nil {
+               return false
+       }
+       return stat.IsDir()
+}