diff --git a/internal/build/scaffold.go b/internal/build/scaffold.go index 6c48957c..07342f48 100644 --- a/internal/build/scaffold.go +++ b/internal/build/scaffold.go @@ -9,6 +9,7 @@ import ( "go/format" "go/parser" "go/token" + "go/version" "os" "os/exec" "path/filepath" @@ -555,6 +556,24 @@ func ensureToolchain(path string) error { return nil } lines := strings.Split(string(data), "\n") + // A file may carry its own toolchain line (go mod tidy and go work use + // write the running toolchain). Go refuses a second one, so keep an + // existing line unless it names an older toolchain than ours. + want := strings.TrimSpace(strings.TrimPrefix(toolchainLine, "toolchain")) + for i, line := range lines { + fields := strings.Fields(line) + if len(fields) != 2 || fields[0] != "toolchain" { + continue + } + if version.Compare(fields[1], want) >= 0 { + return nil + } + lines[i] = toolchainLine + if err := os.WriteFile(path, []byte(strings.Join(lines, "\n")), 0o644); err != nil { + return fmt.Errorf("build: write %s: %w", path, err) + } + return nil + } out := make([]string, 0, len(lines)+2) inserted := false for _, line := range lines { diff --git a/internal/build/toolchain_test.go b/internal/build/toolchain_test.go new file mode 100644 index 00000000..d660e98b --- /dev/null +++ b/internal/build/toolchain_test.go @@ -0,0 +1,58 @@ +package build + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestEnsureToolchain(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + { + name: "missing line is added after go", + in: "go 1.27\n\nuse .\n", + want: "go 1.27\n\n" + toolchainLine + "\n\nuse .\n", + }, + { + name: "our line is kept", + in: "go 1.27\n\n" + toolchainLine + "\n", + want: "go 1.27\n\n" + toolchainLine + "\n", + }, + { + name: "newer toolchain is kept, not repeated", + in: "go 1.27\n\ntoolchain go1.99.2\n\nuse .\n", + want: "go 1.27\n\ntoolchain go1.99.2\n\nuse .\n", + }, + { + name: "older toolchain is replaced", + in: "module x\n\ngo 1.25\n\ntoolchain go1.25.1\n", + want: "module x\n\ngo 1.25\n\n" + toolchainLine + "\n", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "go.work") + if err := os.WriteFile(path, []byte(tc.in), 0o644); err != nil { + t.Fatal(err) + } + if err := ensureToolchain(path); err != nil { + t.Fatalf("ensureToolchain: %v", err) + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if string(got) != tc.want { + t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want) + } + if n := strings.Count(string(got), "\ntoolchain "); n != 1 { + t.Fatalf("got %d toolchain lines, want 1", n) + } + }) + } +}