diff --git a/internal/workdir/runtimes/runtime-go/private.go b/internal/workdir/runtimes/runtime-go/private.go index 3986f6c..87e54e3 100644 --- a/internal/workdir/runtimes/runtime-go/private.go +++ b/internal/workdir/runtimes/runtime-go/private.go @@ -14,7 +14,6 @@ import ( "regexp" "strings" - "github.com/kazhuravlev/toolset/internal/fsh" "github.com/kazhuravlev/toolset/internal/prog" "github.com/spf13/afero" "golang.org/x/mod/modfile" @@ -31,7 +30,7 @@ type moduleInfo struct { } // parse will parse source string and try to extract all details about mentioned golang program. -func parse(ctx context.Context, goBin, str string) (*moduleInfo, error) { +func (r *Runtime) parse(ctx context.Context, str string) (*moduleInfo, error) { var mod, version, program string { @@ -64,8 +63,8 @@ func parse(ctx context.Context, goBin, str string) (*moduleInfo, error) { buf := bytes.NewBuffer(nil) { - cmd := exec.CommandContext(ctx, goBin, "env", "GOPRIVATE") - cmd.Env = envAllOverride([][2]string{{"GOTOOLCHAIN", "local"}}) + cmd := exec.CommandContext(ctx, r.goBin, "env", "GOPRIVATE") + cmd.Env = r.goEnv() cmd.Stdout = buf cmd.Stderr = io.Discard if err := cmd.Run(); err != nil { @@ -97,19 +96,19 @@ type fetchedMod struct { // @ => @latest // @latest => @vX.X.X // @vX.X.X => @vX.X.X -func fetchModule(ctx context.Context, fs fsh.FS, goBin, link string) (*moduleInfo, error) { - mod, err := parse(ctx, goBin, link) +func (r *Runtime) fetchModule(ctx context.Context, link string) (*moduleInfo, error) { + mod, err := r.parse(ctx, link) if err != nil { return nil, fmt.Errorf("parse module (%s) string: %w", link, err) } if mod.IsPrivate { - privateMod, err := fetchPrivate(ctx, fs, goBin, *mod) + privateMod, err := r.fetchPrivate(ctx, *mod) if err != nil { return nil, fmt.Errorf("fetch private module: %w", err) } - return parse(ctx, goBin, mod.Mod.Name()+at+privateMod.Mod.Version()) + return r.parse(ctx, mod.Mod.Name()+at+privateMod.Mod.Version()) } link = mod.Mod.Name() @@ -153,7 +152,7 @@ func fetchModule(ctx context.Context, fs fsh.FS, goBin, link string) (*moduleInf return nil, fmt.Errorf("unable to decode module: %w", err) } - mod2, err := parse(ctx, goBin, mod.Mod.Name()+at+fMod.Version) + mod2, err := r.parse(ctx, mod.Mod.Name()+at+fMod.Version) if err != nil { return nil, fmt.Errorf("parse fetched module: %w", err) } @@ -170,16 +169,16 @@ func fetchModule(ctx context.Context, fs fsh.FS, goBin, link string) (*moduleInf // - Add dependency // - Get dep info // - Remove temp dir -func fetchPrivate(ctx context.Context, fSys fsh.FS, goBin string, mod moduleInfo) (*moduleInfo, error) { - tmpDir, err := afero.TempDir(fSys, "", "gomodtemp") +func (r *Runtime) fetchPrivate(ctx context.Context, mod moduleInfo) (*moduleInfo, error) { + tmpDir, err := afero.TempDir(r.fs, "", "gomodtemp") if err != nil { return nil, fmt.Errorf("failed to create temp directory: %v", err) } - defer fSys.RemoveAll(tmpDir) //nolint:errcheck + defer r.fs.RemoveAll(tmpDir) //nolint:errcheck { - cmd := exec.CommandContext(ctx, goBin, "mod", "init", "sample") - cmd.Env = envAllOverride([][2]string{{"GOTOOLCHAIN", "local"}}) + cmd := exec.CommandContext(ctx, r.goBin, "mod", "init", "sample") + cmd.Env = r.goEnv() cmd.Dir = tmpDir cmd.Stdout = io.Discard cmd.Stderr = io.Discard @@ -189,8 +188,8 @@ func fetchPrivate(ctx context.Context, fSys fsh.FS, goBin string, mod moduleInfo } { - cmd := exec.CommandContext(ctx, goBin, "get", mod.Mod.S()) - cmd.Env = envAllOverride([][2]string{{"GOTOOLCHAIN", "local"}}) + cmd := exec.CommandContext(ctx, r.goBin, "get", mod.Mod.S()) + cmd.Env = r.goEnv() cmd.Dir = tmpDir cmd.Stdout = io.Discard cmd.Stderr = io.Discard @@ -200,7 +199,7 @@ func fetchPrivate(ctx context.Context, fSys fsh.FS, goBin string, mod moduleInfo } goModFilename := filepath.Join(tmpDir, "go.mod") - bb, err := afero.ReadFile(fSys, goModFilename) + bb, err := afero.ReadFile(r.fs, goModFilename) if err != nil { return nil, fmt.Errorf("failed to read go.mod: %w", err) } @@ -212,7 +211,7 @@ func fetchPrivate(ctx context.Context, fSys fsh.FS, goBin string, mod moduleInfo for _, require := range modFile.Require { if strings.HasPrefix(mod.Mod.Name(), require.Mod.Path) { - return parse(ctx, goBin, mod.Mod.Name()+at+require.Mod.Version) + return r.parse(ctx, mod.Mod.Name()+at+require.Mod.Version) } } @@ -268,12 +267,9 @@ func envAllOverride(envs [][2]string) []string { "GOARM64", "GOAUTH", "GOBIN", - "GOCACHE", - "GOCACHEPROG", "GODEBUG", "GOENV", "GOEXE", - //"GOEXPERIMENT", "GOFIPS140", "GOFLAGS", "GOGCCFLAGS", @@ -282,15 +278,9 @@ func envAllOverride(envs [][2]string) []string { "GOINSECURE", "GOMOD", "GOMODCACHE", - //"GONOPROXY", - //"GONOSUMDB", "GOOS", "GOPATH", - //"GOPRIVATE", - //"GOPROXY", "GOROOT", - //"GOSUMDB", - //"GOTELEMETRY", "GOTELEMETRYDIR", "GOTMPDIR", "GOTOOLCHAIN", @@ -299,6 +289,16 @@ func envAllOverride(envs [][2]string) []string { "GOVERSION", "GOWORK", "PKG_CONFIG", + + // "GOCACHE", + // "GOCACHEPROG", + // "GOEXPERIMENT", + // "GONOPROXY", + // "GONOSUMDB", + // "GOPRIVATE", + // "GOPROXY", + // "GOSUMDB", + // "GOTELEMETRY", } for _, env := range excluded { diff --git a/internal/workdir/runtimes/runtime-go/private_test.go b/internal/workdir/runtimes/runtime-go/private_test.go index 26d980e..ecbaf7e 100644 --- a/internal/workdir/runtimes/runtime-go/private_test.go +++ b/internal/workdir/runtimes/runtime-go/private_test.go @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/kazhuravlev/optional" "github.com/kazhuravlev/toolset/internal/fsh" "github.com/kazhuravlev/toolset/internal/prog" @@ -13,13 +14,12 @@ import ( ) func Test_parse(t *testing.T) { - goBin, err := exec.LookPath("go") - require.NoError(t, err, "install go") + rt := newTestRuntime(t) f := func(name, in string, exp moduleInfo) { t.Run(name, func(t *testing.T) { ctx := context.Background() - mod, err := parse(ctx, goBin, in) + mod, err := rt.parse(ctx, in) require.NoError(t, err) require.NotEmpty(t, mod) require.Equal(t, exp, *mod) @@ -49,14 +49,12 @@ func Test_parse(t *testing.T) { } func Test_fetchModule(t *testing.T) { - fs := fsh.NewRealFS() - goBin, err := exec.LookPath("go") - require.NoError(t, err, "install go") + rt := newTestRuntime(t) f := func(name, link string, exp moduleInfo) { t.Run(name, func(t *testing.T) { ctx := context.Background() - mod, err := fetchModule(ctx, fs, goBin, link) + mod, err := rt.fetchModule(ctx, link) require.NoError(t, err) require.NotEmpty(t, mod) require.Equal(t, exp, *mod) @@ -85,3 +83,22 @@ func Test_getGoVersion(t *testing.T) { require.NotEmpty(t, goVersion) require.True(t, strings.HasPrefix(goVersion, "1.")) // NOTE(zhuravlev): should looks like 1.23.4 } + +func newTestRuntime(t *testing.T) *Runtime { + t.Helper() + + fs := fsh.NewRealFS() + goBin, err := exec.LookPath("go") + require.NoError(t, err, "install go") + + ctx := context.Background() + goVersion, err := getGoVersion(ctx, goBin) + require.NoError(t, err) + + binDir := t.TempDir() + + rt, err := New(fs, binDir, goBin, goVersion, optional.Empty[string]()) + require.NoError(t, err) + + return rt +} diff --git a/internal/workdir/runtimes/runtime-go/runtime_go.go b/internal/workdir/runtimes/runtime-go/runtime_go.go index 5c88fa7..08515f1 100644 --- a/internal/workdir/runtimes/runtime-go/runtime_go.go +++ b/internal/workdir/runtimes/runtime-go/runtime_go.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/kazhuravlev/optional" "github.com/spf13/afero" "github.com/kazhuravlev/toolset/internal/fsh" @@ -28,15 +29,23 @@ type Runtime struct { isGlobal bool goVersion string // ex: 1.23 binToolDir string + goCacheDir optional.Val[string] } -func New(fs fsh.FS, binToolDir, goBin, goVer string) *Runtime { +func New(fs fsh.FS, binToolDir, goBin, goVer string, goCache optional.Val[string]) (*Runtime, error) { + if goCacheVal, ok := goCache.Get(); ok { + if err := fs.MkdirAll(goCacheVal, fsh.DefaultDirPerm); err != nil { + return nil, fmt.Errorf("create go cache dir (%s): %w", goCacheVal, err) + } + } + return &Runtime{ fs: fs, goBin: goBin, goVersion: goVer, binToolDir: binToolDir, - } + goCacheDir: goCache, + }, nil } // Parse will parse string to normal version. @@ -48,7 +57,7 @@ func (r *Runtime) Parse(ctx context.Context, str string) (string, error) { return "", errors.New("program name not provided") } - goModule, err := fetchModule(ctx, r.fs, r.goBin, str) + goModule, err := r.fetchModule(ctx, str) if err != nil { return "", fmt.Errorf("get go module version: %w", err) } @@ -57,7 +66,7 @@ func (r *Runtime) Parse(ctx context.Context, str string) (string, error) { } func (r *Runtime) GetModule(ctx context.Context, module string) (*structs.ModuleInfo, error) { - mod, err := parse(ctx, r.goBin, module) + mod, err := r.parse(ctx, module) if err != nil { return nil, fmt.Errorf("parse module (%s): %w", module, err) } @@ -86,7 +95,7 @@ func (r *Runtime) Install(ctx context.Context, program string) error { } cmd := exec.CommandContext(ctx, r.goBin, "install", program) - cmd.Env = envAllOverride([][2]string{{"GOTOOLCHAIN", "local"}, {"GOBIN", mod.BinDir}}) + cmd.Env = r.goEnv([2]string{"GOBIN", mod.BinDir}) var stdout bytes.Buffer cmd.Stderr = &stdout @@ -127,13 +136,13 @@ func (r *Runtime) Run(ctx context.Context, program string, args ...string) error } func (r *Runtime) GetLatest(ctx context.Context, moduleReq string) (string, bool, error) { - mod, err := parse(ctx, r.goBin, moduleReq) + mod, err := r.parse(ctx, moduleReq) if err != nil { return "", false, fmt.Errorf("parse module (%s): %w", moduleReq, err) } latestStr := mod.Mod.AsLatest().S() - latestMod, err := fetchModule(ctx, r.fs, r.goBin, latestStr) + latestMod, err := r.fetchModule(ctx, latestStr) if err != nil { return "", false, fmt.Errorf("get go module: %w", err) } @@ -162,6 +171,19 @@ func (r *Runtime) Remove(ctx context.Context, tool structs.Tool) error { return nil } +func (r *Runtime) goEnv(overrides ...[2]string) []string { + base := make([][2]string, 0, len(overrides)+2) + + if val, ok := r.goCacheDir.Get(); ok { + base = append(base, [2]string{"GOCACHE", val}) + } + + base = append(base, [2]string{"GOTOOLCHAIN", "local"}) + base = append(base, overrides...) + + return envAllOverride(base) +} + func (r *Runtime) Version() string { if r.isGlobal { return "go" @@ -190,7 +212,11 @@ func Discover(ctx context.Context, fSys fsh.FS, binToolDir string) ([]*Runtime, return res, fmt.Errorf("get go version: %w", err) } - rt := New(fSys, binToolDir, lp, ver) + rt, err := New(fSys, binToolDir, lp, ver, optional.Empty[string]()) + if err != nil { + return res, fmt.Errorf("init go runtime (%s): %w", ver, err) + } + rt.isGlobal = true res = append(res, rt) } @@ -224,7 +250,14 @@ func Discover(ctx context.Context, fSys fsh.FS, binToolDir string) ([]*Runtime, return res, fmt.Errorf("get go version for (%s): %w", goBin, err) } - res = append(res, New(fSys, binToolDir, goBin, goVer)) + goCache := filepath.Join(binToolDir, e.Name(), "gocache") + + rt, err := New(fSys, binToolDir, goBin, goVer, optional.New(goCache)) + if err != nil { + return res, fmt.Errorf("init go runtime (%s): %w", goVer, err) + } + + res = append(res, rt) } }