aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2023-08-18 15:38:44 -0400
committerGopher Robot <gobot@golang.org>2023-08-30 21:25:08 +0000
commit6385a6fb18cc2c2080d6d8c008c49e1cf20e04a0 (patch)
tree69c0fdbf8d701a43d7a470c434ff18487bb6472f
parent2d07bb86f0a2bab639c51baca4c3cf6d9c4374ad (diff)
downloadgo-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.go38
-rw-r--r--src/cmd/go/testdata/script/goroot_executable.txt8
-rw-r--r--src/cmd/go/testdata/script/goroot_executable_trimpath.txt91
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)
+
+}