diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 79edf8053a7b09fa8114ee08bcebb894335ad183..cd764468813613a81ca5eeae23630f0681727767 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -17,6 +17,7 @@ import ( "path/filepath" "regexp" "sort" + "strconv" "strings" "sync" "time" @@ -261,8 +262,12 @@ func xinit() { os.Unsetenv("GOFLAGS") os.Setenv("GOWORK", "off") + // Create the go.mod for building toolchain2 and toolchain3. Toolchain1 and go_bootstrap are built with + // a separate go.mod (with a lower required go version to allow all allowed bootstrap toolchain versions) + // in bootstrapBuildTools. + modVer := goModVersion() workdir = xworkdir() - if err := os.WriteFile(pathf("%s/go.mod", workdir), []byte("module bootstrap"), 0666); err != nil { + if err := os.WriteFile(pathf("%s/go.mod", workdir), []byte("module bootstrap\n\ngo "+modVer+"\n"), 0666); err != nil { fatalf("cannot write stub go.mod: %s", err) } xatexit(rmworkdir) @@ -441,6 +446,33 @@ func findgoversion() string { return version } +// goModVersion returns the go version declared in src/go.mod. This is the +// go version to use in the go.mod building go_bootstrap, toolchain2, and toolchain3. +// (toolchain1 must be built with requiredBootstrapVersion(goModVersion)) +func goModVersion() string { + goMod := readfile(pathf("%s/src/go.mod", goroot)) + m := regexp.MustCompile(`(?m)^go (1.\d+)$`).FindStringSubmatch(goMod) + if m == nil { + fatalf("std go.mod does not contain go 1.X") + } + return m[1] +} + +func requiredBootstrapVersion(v string) string { + minorstr, ok := strings.CutPrefix(v, "1.") + if !ok { + fatalf("go version %q in go.mod does not start with %q", v, "1.") + } + minor, err := strconv.Atoi(minorstr) + if err != nil { + fatalf("invalid go version minor component %q: %v", minorstr, err) + } + // Per go.dev/doc/install/source, for N >= 22, Go version 1.N will require a Go 1.M compiler, + // where M is N-2 rounded down to an even number. Example: Go 1.24 and 1.25 require Go 1.22. + requiredMinor := minor - 2 - minor%2 + return "1." + strconv.Itoa(requiredMinor) +} + // isGitRepo reports whether the working directory is inside a Git repository. func isGitRepo() bool { // NB: simply checking the exit code of `git rev-parse --git-dir` would diff --git a/src/cmd/dist/build_test.go b/src/cmd/dist/build_test.go index 158ac2678d404122ab54f746239c7b7af219fbe8..36bf54c305a3c21b18a282c4790e986382509da7 100644 --- a/src/cmd/dist/build_test.go +++ b/src/cmd/dist/build_test.go @@ -24,3 +24,20 @@ func TestMustLinkExternal(t *testing.T) { } } } + +func TestRequiredBootstrapVersion(t *testing.T) { + testCases := map[string]string{ + "1.22": "1.20", + "1.23": "1.20", + "1.24": "1.22", + "1.25": "1.22", + "1.26": "1.24", + "1.27": "1.24", + } + + for v, want := range testCases { + if got := requiredBootstrapVersion(v); got != want { + t.Errorf("requiredBootstrapVersion(%v): got %v, want %v", v, got, want) + } + } +} diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 9ca8fc539c43d448893e31f24f63a5e8bf45b67d..0b9e489200b130c7624bb2647c2239fa8e0969fc 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -151,7 +151,8 @@ func bootstrapBuildTools() { xmkdirall(base) // Copy source code into $GOROOT/pkg/bootstrap and rewrite import paths. - writefile("module bootstrap\ngo 1.20\n", pathf("%s/%s", base, "go.mod"), 0) + minBootstrapVers := requiredBootstrapVersion(goModVersion()) // require the minimum required go version to build this go version in the go.mod file + writefile("module bootstrap\ngo "+minBootstrapVers+"\n", pathf("%s/%s", base, "go.mod"), 0) for _, dir := range bootstrapDirs { recurse := strings.HasSuffix(dir, "/...") dir = strings.TrimSuffix(dir, "/...")