Skip to content

x/tools/go/packages: Load does not respect Go version consistently #62114

@hugelgupf

Description

@hugelgupf

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>

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.ToolsThis label describes issues relating to any tools in the x/tools repository.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions