diff options
author | Bryan C. Mills <bcmills@google.com> | 2023-08-18 15:38:44 -0400 |
---|---|---|
committer | Gopher Robot <gobot@golang.org> | 2023-08-30 21:25:08 +0000 |
commit | 6385a6fb18cc2c2080d6d8c008c49e1cf20e04a0 (patch) | |
tree | 69c0fdbf8d701a43d7a470c434ff18487bb6472f | |
parent | 2d07bb86f0a2bab639c51baca4c3cf6d9c4374ad (diff) | |
download | go-6385a6fb18cc2c2080d6d8c008c49e1cf20e04a0.tar.gz go-6385a6fb18cc2c2080d6d8c008c49e1cf20e04a0.zip |
[release-branch.go1.21] cmd/go: find GOROOT using os.Executable when installed to GOROOT/bin/GOOS_GOARCH
When running make.bash in a cross-compiled configuration
(for example, GOARCH different from GOHOSTARCH), cmd/go
is installed to GOROOT/bin/GOOS_GOARCH instead of GOROOT/bin.
That means that we need to look for GOROOT in both ../.. and ../../..,
not just the former.
Fixes #62144.
Updates #62119.
Updates #18678.
Change-Id: I283c6a10c46df573ff44da826f870417359226a7
Reviewed-on: https://go-review.googlesource.com/c/go/+/521015
Reviewed-by: Michael Matloob <matloob@golang.org>
Auto-Submit: Bryan Mills <bcmills@google.com>
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
(cherry picked from commit 9e9556d328c53ed0a4d8b36feee949885d648ba8)
Reviewed-on: https://go-review.googlesource.com/c/go/+/521695
Auto-Submit: Dmitri Shuralyov <dmitshur@google.com>
-rw-r--r-- | src/cmd/go/internal/cfg/cfg.go | 38 | ||||
-rw-r--r-- | src/cmd/go/testdata/script/goroot_executable.txt | 8 | ||||
-rw-r--r-- | src/cmd/go/testdata/script/goroot_executable_trimpath.txt | 91 |
3 files changed, 125 insertions, 12 deletions
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index 8a82e5562b..8caa22a93d 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -490,25 +490,43 @@ func findGOROOT(env string) string { // depend on the executable's location. return def } + + // canonical returns a directory path that represents + // the same directory as dir, + // preferring the spelling in def if the two are the same. + canonical := func(dir string) string { + if isSameDir(def, dir) { + return def + } + return dir + } + exe, err := os.Executable() if err == nil { exe, err = filepath.Abs(exe) if err == nil { + // cmd/go may be installed in GOROOT/bin or GOROOT/bin/GOOS_GOARCH, + // depending on whether it was cross-compiled with a different + // GOHOSTOS (see https://go.dev/issue/62119). Try both. if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { - // If def (runtime.GOROOT()) and dir are the same - // directory, prefer the spelling used in def. - if isSameDir(def, dir) { - return def - } - return dir + return canonical(dir) + } + if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { + return canonical(dir) } + + // Depending on what was passed on the command line, it is possible + // that os.Executable is a symlink (like /usr/local/bin/go) referring + // to a binary installed in a real GOROOT elsewhere + // (like /usr/lib/go/bin/go). + // Try to find that GOROOT by resolving the symlinks. exe, err = filepath.EvalSymlinks(exe) if err == nil { if dir := filepath.Join(exe, "../.."); isGOROOT(dir) { - if isSameDir(def, dir) { - return def - } - return dir + return canonical(dir) + } + if dir := filepath.Join(exe, "../../.."); isGOROOT(dir) { + return canonical(dir) } } } diff --git a/src/cmd/go/testdata/script/goroot_executable.txt b/src/cmd/go/testdata/script/goroot_executable.txt index 1a0e23e375..e20dbd87ac 100644 --- a/src/cmd/go/testdata/script/goroot_executable.txt +++ b/src/cmd/go/testdata/script/goroot_executable.txt @@ -1,4 +1,5 @@ [compiler:gccgo] skip +[short] skip 'builds and links another cmd/go' mkdir $WORK/new/bin @@ -9,15 +10,18 @@ mkdir $WORK/new/bin # new cmd/go is built. env GOROOT_FINAL= +# $GOROOT/bin/go is whatever the user has already installed +# (using make.bash or similar). We can't make assumptions about what +# options it may have been built with, such as -trimpath or GOROOT_FINAL. +# Instead, we build a fresh copy of the binary with known settings. go build -o $WORK/new/bin/go$GOEXE cmd/go & -go build -o $WORK/bin/check$GOEXE check.go & +go build -trimpath -o $WORK/bin/check$GOEXE check.go & wait env TESTGOROOT=$GOROOT env GOROOT= # Relocated Executable -# cp $TESTGOROOT/bin/go$GOEXE $WORK/new/bin/go$GOEXE exec $WORK/bin/check$GOEXE $WORK/new/bin/go$GOEXE $TESTGOROOT # Relocated Tree: diff --git a/src/cmd/go/testdata/script/goroot_executable_trimpath.txt b/src/cmd/go/testdata/script/goroot_executable_trimpath.txt new file mode 100644 index 0000000000..dc1e25e606 --- /dev/null +++ b/src/cmd/go/testdata/script/goroot_executable_trimpath.txt @@ -0,0 +1,91 @@ +# Regression test for https://go.dev/issue/62119: +# A 'go' command cross-compiled with a different GOHOSTOS +# should be able to locate its GOROOT using os.Executable. +# +# (This also tests a 'go' command built with -trimpath +# that is not cross-compiled, since we need to build that +# configuration for the test anyway.) + +[short] skip 'builds and links another cmd/go' + +mkdir $WORK/new/bin +mkdir $WORK/new/bin/${GOOS}_${GOARCH} + +# In this test, we are specifically checking the logic for deriving +# the value of GOROOT from os.Executable when runtime.GOROOT is +# trimmed away. +# GOROOT_FINAL changes the default behavior of runtime.GOROOT, +# so we explicitly clear it to remove it as a confounding variable. +env GOROOT_FINAL= + +# $GOROOT/bin/go is whatever the user has already installed +# (using make.bash or similar). We can't make assumptions about what +# options it may have been built with, such as -trimpath or GOROOT_FINAL. +# Instead, we build a fresh copy of the binary with known settings. +go build -trimpath -o $WORK/new/bin/go$GOEXE cmd/go & +go build -trimpath -o $WORK/bin/check$GOEXE check.go & +wait + +env TESTGOROOT=$GOROOT +env GOROOT= + +# Relocated Executable +# Since we built with -trimpath and the binary isn't installed in a +# normal-looking GOROOT, this command should fail. + +! exec $WORK/new/bin/go$GOEXE env GOROOT +stderr '^go: cannot find GOROOT directory: ''go'' binary is trimmed and GOROOT is not set$' + +# Cross-compiled binaries in cmd are installed to a ${GOOS}_${GOARCH} subdirectory, +# so we also want to try a copy there. +# (Note that the script engine's 'exec' engine already works around +# https://go.dev/issue/22315, so we don't have to do that explicitly in the +# 'check' program we use later.) +cp $WORK/new/bin/go$GOEXE $WORK/new/bin/${GOOS}_${GOARCH}/go$GOEXE +! exec $WORK/new/bin/${GOOS}_${GOARCH}/go$GOEXE env GOROOT +stderr '^go: cannot find GOROOT directory: ''go'' binary is trimmed and GOROOT is not set$' + +# Relocated Tree: +# If the binary is sitting in a bin dir next to ../pkg/tool, that counts as a GOROOT, +# so it should find the new tree. +mkdir $WORK/new/pkg/tool +exec $WORK/bin/check$GOEXE $WORK/new/bin/go$GOEXE $WORK/new +exec $WORK/bin/check$GOEXE $WORK/new/bin/${GOOS}_${GOARCH}/go$GOEXE $WORK/new + +-- check.go -- +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func main() { + exe := os.Args[1] + want := os.Args[2] + cmd := exec.Command(exe, "env", "GOROOT") + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Fprintf(os.Stderr, "%s env GOROOT: %v, %s\n", exe, err, out) + os.Exit(1) + } + goroot, err := filepath.EvalSymlinks(strings.TrimSpace(string(out))) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + want, err = filepath.EvalSymlinks(want) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + if !strings.EqualFold(goroot, want) { + fmt.Fprintf(os.Stderr, "go env GOROOT:\nhave %s\nwant %s\n", goroot, want) + os.Exit(1) + } + fmt.Fprintf(os.Stderr, "go env GOROOT: %s\n", goroot) + +} |