Repository: go
Issue: x/tools/go/packages: Load does not respect Go version consistently
Labels: NeedsInvestigation,Tools

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:

```go
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>
```