Skip to content

Commit 30cbdf1

Browse files
committed
feat: add GoInstallWithGoVersion for Go-version-aware tool caching
1 parent 539a443 commit 30cbdf1

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

sgtool/build.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"regexp"
10+
"runtime"
1011
"strings"
1112

1213
"go.einride.tech/sage/sg"
@@ -45,6 +46,33 @@ func GoInstall(ctx context.Context, pkg, version string) (string, error) {
4546
return symlink, nil
4647
}
4748

49+
// GoInstallWithGoVersion is like GoInstall but includes the Go version in the
50+
// cache key. Use this for tools whose behavior depends on the Go runtime version,
51+
// such as go-licenses which uses build.Default.GOROOT to detect stdlib packages.
52+
func GoInstallWithGoVersion(ctx context.Context, pkg, version string) (string, error) {
53+
executable := sg.FromToolsDir("go", pkg, version, runtime.Version(), filepath.Base(trimVersionSuffix(pkg)))
54+
// Check if executable already exists
55+
if _, err := os.Stat(executable); err == nil {
56+
symlink, err := CreateSymlink(executable)
57+
if err != nil {
58+
return "", err
59+
}
60+
return symlink, nil
61+
}
62+
pkgVersion := fmt.Sprintf("%s@%s", pkg, version)
63+
sg.Logger(ctx).Printf("building %s...", pkgVersion)
64+
cmd := sg.Command(ctx, "go", "install", pkgVersion)
65+
cmd.Env = append(cmd.Env, "GOBIN="+filepath.Dir(executable))
66+
if err := cmd.Run(); err != nil {
67+
return "", err
68+
}
69+
symlink, err := CreateSymlink(executable)
70+
if err != nil {
71+
return "", err
72+
}
73+
return symlink, nil
74+
}
75+
4876
// GoInstallWithModfile builds and installs a go binary given the package and a path
4977
// to the local go.mod file.
5078
func GoInstallWithModfile(ctx context.Context, pkg, file string) (string, error) {

tools/sggolicenses/tools.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
// Package sggolicenses provides sage integration for the go-licenses tool.
2+
//
3+
// Troubleshooting: If go-licenses fails with errors like "Package X does not
4+
// have module info", this is a known incompatibility between go-licenses and
5+
// Go toolchain version mismatches (https://github.com/google/go-licenses/pull/329).
6+
// The go-licenses binary uses build.Default.GOROOT at runtime to detect stdlib
7+
// packages — if your local Go version differs from the toolchain directive in
8+
// go.mod, the GOROOT path won't match and stdlib packages are misidentified.
9+
//
10+
// To fix this, update your local Go installation to match the version in go.mod,
11+
// or set GOTOOLCHAIN=go<version>+auto (e.g. GOTOOLCHAIN=go1.25.7+auto) to
12+
// ensure the correct toolchain is used consistently.
113
package sggolicenses
214

315
import (
@@ -93,7 +105,10 @@ func Check(ctx context.Context, disallowedTypes ...string) error {
93105
return nil
94106
}
95107

108+
// PrepareCommand installs go-licenses using Go-version-aware caching.
109+
// go-licenses uses build.Default.GOROOT at runtime to detect stdlib packages,
110+
// so the binary must be rebuilt when the Go version changes.
96111
func PrepareCommand(ctx context.Context) error {
97-
_, err := sgtool.GoInstall(ctx, "github.com/google/go-licenses/v2", version)
112+
_, err := sgtool.GoInstallWithGoVersion(ctx, "github.com/google/go-licenses/v2", version)
98113
return err
99114
}

tools/sggovulncheck/tools.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,17 @@ func RunAll(ctx context.Context, modulePaths ...string) error {
6767
return nil
6868
}
6969

70+
// PrepareCommand installs govulncheck using Go-version-aware caching.
71+
// govulncheck uses go/packages to load and analyze dependencies. When the
72+
// binary is built with an older Go version than what is currently installed,
73+
// package loading can fail with errors like:
74+
//
75+
// file requires newer Go version go1.N (application built with go1.M)
76+
//
77+
// This happens because dependencies may have //go:build go1.N constraints
78+
// that are incompatible with the Go version embedded in a stale binary.
79+
// Rebuilding when the Go version changes ensures compatibility.
7080
func PrepareCommand(ctx context.Context) error {
71-
_, err := sgtool.GoInstall(ctx, "golang.org/x/vuln/cmd/govulncheck", version)
81+
_, err := sgtool.GoInstallWithGoVersion(ctx, "golang.org/x/vuln/cmd/govulncheck", version)
7282
return err
7383
}

0 commit comments

Comments
 (0)