-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
x/tools/go/packages invokes the "go" command, but often behaves differently depending on the underlying Go version. As a consumer of the package, I regularly test my usage against several versions of Go, because these tools behave differently depending on the underlying Go version.
At one point, I believed that setting packages.Config.Env with the correct GOROOT and PATH=$GOROOT/bin:$PATH would make up for this, but because exec.Command uses exec.LookPath (which is hard-coded to the system os.Getenv("PATH"), see #20081, #59753, #60601) and packages.Load does nothing to mitigate that, the Go version used to load packages is not the correct one.
Here's an example usage of packages.Load:
package main
import (
"fmt"
"go/build"
"log"
"os"
"runtime"
"golang.org/x/tools/go/packages"
)
func GOROOT() string {
if g := build.Default.GOROOT; g != "" {
return g
}
return runtime.GOROOT()
}
func main() {
fmt.Println("GOROOT:", GOROOT())
fmt.Println("PATH:", os.Getenv("PATH"))
cfg := &packages.Config{
Mode: packages.LoadSyntax | packages.LoadImports,
Env: []string{
"GOPACKAGESDRIVER=off",
// Packages driver needs a GOROOT to look up cmd/test2json.
fmt.Sprintf("GOROOT=%s", GOROOT()),
// Where to find the Go binary itself.
fmt.Sprintf("PATH=%s/bin", GOROOT()),
// Package driver needs HOME (or GOCACHE) for build cache.
fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
},
Dir: "",
}
pkgs, err := packages.Load(cfg, "cmd/test2json")
if err != nil {
log.Fatal(err)
}
for _, pkg := range pkgs {
fmt.Printf("%#v\n", pkg)
}
}Say you are on a system where the admin has installed some older Go, but you've used golang.org/dl/go1.20 to download a newer version. Or it's a newer version of Go, but you want to ensure that your packages.Load code works against every Go version 1.18 and up.
$ go1.20 build -o pkgdriver
$ GOROOT=$HOME/sdk/go1.20 strace -fe trace=execve -e signal=SIGIO ./pkgdriver 2>&1 | grep -v "exited with" | grep -v "strace: Process"
[pid 675271] execve("/usr/lib/google-golang/bin/go", ["go", "list", "-e", "-f", "{{context.ReleaseTags}}", "--", "unsafe"], 0xc00007e340 /* 6 vars */) = 0
[pid 675272] execve("/usr/lib/google-golang/bin/go", ["go", "list", "-f", "{{context.GOARCH}} {{context.Com"..., "--", "unsafe"], 0xc00009c150 /* 5 vars */) = 0
[pid 675282] execve("/usr/lib/google-golang/bin/go", ["go", "list", "-e", "-json=Name,ImportPath,Error,Dir,"..., "-compiled=true", "-test=false", "-export=true", "-deps=tr
ue", "-find=false", "-pgo=off", "--", "cmd/test2json"], 0xc00002ea50 /* 5 vars */) = 0
[pid 675291] execve("/usr/local/google/home/chrisko/sdk/go1.20/pkg/tool/linux_amd64/compile", ["/usr/local/google/home/chrisko/s"..., "-V=full"], 0xc0001d9d40 /* 25 vars *
/) = 0
[pid 675292] execve("/usr/local/google/home/chrisko/sdk/go1.20/pkg/tool/linux_amd64/compile", ["/usr/local/google/home/chrisko/s"..., "-V=full"], 0xc00013cc30 /* 25 vars *
/ <unfinished ...>
...
Despite best efforts, the system "go" (which might be older) is used to execute the list driver, but $GOROOT/bin/go is then used to compile more information about the package by calling compile/asm functions. This is inconsistent.
Note that if instead of compiling the command and then running it, you run it with go1.20 run, the command seems to mitigate this itself by placing GOROOT/bin in the PATH prior to execution:
$ go1.20 run .
PATH: /usr/local/google/home/chrisko/sdk/go1.20/bin:<snip>