]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: update default go directive in mod or work init
authorIan Alexander <jitsu@google.com>
Thu, 13 Nov 2025 21:38:20 +0000 (16:38 -0500)
committerIan Alexander <jitsu@google.com>
Sat, 22 Nov 2025 14:13:24 +0000 (06:13 -0800)
This commit updates the default go directive when initializing a new
module.

The current logic is to use the latest version supported by the
toolchain.  This behavior is simple, predictable, and importantly, it
can work while completely offline (i.e., no internet connection
required).

This commit changes the default version to the following behavior:

* If the current toolchain version is a stable version of Go 1.N.M,
default to go 1.(N-1).0
* If the current toolchain version is a pre-release version of Go
1.N (Release Candidate M) or a development version of Go 1.N, default
to go 1.(N-2).0

This behavior maintains the property of being able to work offline.

Fixes #74748.

Change-Id: I81f62eef29f1dd51060067c8075f61e7bcf57c20
Reviewed-on: https://go-review.googlesource.com/c/go/+/720480
Commit-Queue: Ian Alexander <jitsu@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@golang.org>
Reviewed-by: Michael Matloob <matloob@golang.org>
Reviewed-by: Michael Matloob <matloob@google.com>
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/workcmd/init.go
src/cmd/go/testdata/script/mod_edit.txt
src/cmd/go/testdata/script/mod_init_version.txt [new file with mode: 0644]
src/cmd/go/testdata/script/work.txt
src/cmd/go/testdata/script/work_edit.txt
src/cmd/go/testdata/script/work_init_path.txt
src/cmd/go/testdata/script/work_init_toolchain.txt
src/cmd/go/testdata/script/work_init_version.txt [new file with mode: 0644]
src/cmd/go/testdata/script/work_sync_toolchain.txt
src/cmd/go/testdata/script/work_use_toolchain.txt

index bbdd0e95b5cab4906c9c8cd7a2839ae4bf6f8094..c4ff9656694817a1becff9584ce2c898afb98c17 100644 (file)
@@ -29,6 +29,7 @@ import (
        "cmd/go/internal/lockedfile"
        "cmd/go/internal/modfetch"
        "cmd/go/internal/search"
+       igover "internal/gover"
 
        "golang.org/x/mod/modfile"
        "golang.org/x/mod/module"
@@ -826,7 +827,7 @@ func WriteWorkFile(path string, wf *modfile.WorkFile) error {
        wf.Cleanup()
        out := modfile.Format(wf.Syntax)
 
-       return os.WriteFile(path, out, 0666)
+       return os.WriteFile(path, out, 0o666)
 }
 
 // UpdateWorkGoVersion updates the go line in wf to be at least goVers,
@@ -1200,7 +1201,7 @@ func CreateModFile(loaderstate *State, ctx context.Context, modPath string) {
        modFile := new(modfile.File)
        modFile.AddModuleStmt(modPath)
        loaderstate.MainModules = makeMainModules(loaderstate, []module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, nil)
-       addGoStmt(modFile, modFile.Module.Mod, gover.Local()) // Add the go directive before converted module requirements.
+       addGoStmt(modFile, modFile.Module.Mod, DefaultModInitGoVersion()) // Add the go directive before converted module requirements.
 
        rs := requirementsFromModFiles(loaderstate, ctx, nil, []*modfile.File{modFile}, nil)
        rs, err := updateRoots(loaderstate, ctx, rs.direct, rs, nil, nil, false)
@@ -1811,9 +1812,7 @@ Run 'go help mod init' for more information.
        return "", fmt.Errorf(msg, dir, reason)
 }
 
-var (
-       importCommentRE = lazyregexp.New(`(?m)^package[ \t]+[^ \t\r\n/]+[ \t]+//[ \t]+import[ \t]+(\"[^"]+\")[ \t]*\r?\n`)
-)
+var importCommentRE = lazyregexp.New(`(?m)^package[ \t]+[^ \t\r\n/]+[ \t]+//[ \t]+import[ \t]+(\"[^"]+\")[ \t]*\r?\n`)
 
 func findImportComment(file string) string {
        data, err := os.ReadFile(file)
@@ -2252,3 +2251,29 @@ func CheckGodebug(verb, k, v string) error {
        }
        return fmt.Errorf("unknown %s %q", verb, k)
 }
+
+// DefaultModInitGoVersion returns the appropriate go version to include in a
+// newly initialized module or work file.
+//
+// If the current toolchain version is a stable version of Go 1.N.M, default to
+// go 1.(N-1).0
+//
+// If the current toolchain version is a pre-release version of Go 1.N (Release
+// Candidate M) or a development version of Go 1.N, default to go 1.(N-2).0
+func DefaultModInitGoVersion() string {
+       v := gover.Local()
+       if isPrereleaseOrDevelVersion(v) {
+               v = gover.Prev(gover.Prev(v))
+       } else {
+               v = gover.Prev(v)
+       }
+       if strings.Count(v, ".") < 2 {
+               v += ".0"
+       }
+       return v
+}
+
+func isPrereleaseOrDevelVersion(s string) bool {
+       v := igover.Parse(s)
+       return v.Kind != "" || v.Patch == ""
+}
index 896740f08035021fd9f5047f5714b86fffe00c19..eff7a8c763968d39f924edd5094dd920611ca00b 100644 (file)
@@ -12,7 +12,6 @@ import (
 
        "cmd/go/internal/base"
        "cmd/go/internal/fsys"
-       "cmd/go/internal/gover"
        "cmd/go/internal/modload"
 
        "golang.org/x/mod/modfile"
@@ -58,10 +57,9 @@ func runInit(ctx context.Context, cmd *base.Command, args []string) {
                base.Fatalf("go: %s already exists", gowork)
        }
 
-       goV := gover.Local() // Use current Go version by default
        wf := new(modfile.WorkFile)
        wf.Syntax = new(modfile.FileSyntax)
-       wf.AddGoStmt(goV)
+       wf.AddGoStmt(modload.DefaultModInitGoVersion())
        workUse(ctx, moduleLoaderState, gowork, wf, args)
        modload.WriteWorkFile(gowork, wf)
 }
index 6b7dd2c2c5e21cdfbb2017627c68235810ea45df..16d46610a26b7dbd520eaf591edfc7d43a880038 100644 (file)
@@ -1,5 +1,8 @@
 env GO111MODULE=on
 
+# Set go version so that we can test produced mod files for equality.
+env TESTGO_VERSION=go1.26.0
+
 # Test that go mod edits and related mod flags work.
 # Also test that they can use a dummy name that isn't resolvable. golang.org/issue/24100
 
@@ -10,16 +13,16 @@ stderr 'cannot determine module path'
 
 go mod init x.x/y/z
 stderr 'creating new go.mod: module x.x/y/z'
-cmpenv go.mod $WORK/go.mod.init
+cmp go.mod $WORK/go.mod.init
 
 ! go mod init
-cmpenv go.mod $WORK/go.mod.init
+cmp go.mod $WORK/go.mod.init
 
 # go mod edits
 go mod edit -droprequire=x.1 -require=x.1@v1.0.0 -require=x.2@v1.1.0 -droprequire=x.2 -exclude='x.1 @ v1.2.0' -exclude=x.1@v1.2.1 -exclude=x.1@v2.0.0+incompatible -replace=x.1@v1.3.0=y.1@v1.4.0 -replace='x.1@v1.4.0 = ../z' -retract=v1.6.0 -retract=[v1.1.0,v1.2.0] -retract=[v1.3.0,v1.4.0] -retract=v1.0.0
-cmpenv go.mod $WORK/go.mod.edit1
+cmp go.mod $WORK/go.mod.edit1
 go mod edit -droprequire=x.1 -dropexclude=x.1@v1.2.1 -dropexclude=x.1@v2.0.0+incompatible -dropreplace=x.1@v1.3.0 -require=x.3@v1.99.0 -dropretract=v1.0.0 -dropretract=[v1.1.0,v1.2.0]
-cmpenv go.mod $WORK/go.mod.edit2
+cmp go.mod $WORK/go.mod.edit2
 
 # -exclude and -retract reject invalid versions.
 ! go mod edit -exclude=example.com/m@bad
@@ -36,11 +39,11 @@ stderr '^go: -exclude=example.com/m/v2@v1\.0\.0: version "v1\.0\.0" invalid: sho
 ! go mod edit -exclude=gopkg.in/example.v1@v2.0.0
 stderr '^go: -exclude=gopkg\.in/example\.v1@v2\.0\.0: version "v2\.0\.0" invalid: should be v1, not v2$'
 
-cmpenv go.mod $WORK/go.mod.edit2
+cmp go.mod $WORK/go.mod.edit2
 
 # go mod edit -json
 go mod edit -json
-cmpenv stdout $WORK/go.mod.json
+cmp stdout $WORK/go.mod.json
 
 # go mod edit -json (retractions with rationales)
 go mod edit -json $WORK/go.mod.retractrationale
@@ -56,66 +59,66 @@ cmp stdout $WORK/go.mod.empty.json
 
 # go mod edit -replace
 go mod edit -replace=x.1@v1.3.0=y.1/v2@v2.3.5 -replace=x.1@v1.4.0=y.1/v2@v2.3.5
-cmpenv go.mod $WORK/go.mod.edit3
+cmp go.mod $WORK/go.mod.edit3
 go mod edit -replace=x.1=y.1/v2@v2.3.6
-cmpenv go.mod $WORK/go.mod.edit4
+cmp go.mod $WORK/go.mod.edit4
 go mod edit -dropreplace=x.1
-cmpenv go.mod $WORK/go.mod.edit5
+cmp go.mod $WORK/go.mod.edit5
 go mod edit -replace=x.1=../y.1/@v2
-cmpenv go.mod $WORK/go.mod.edit6
+cmp go.mod $WORK/go.mod.edit6
 ! go mod edit -replace=x.1=y.1/@v2
 stderr '^go: -replace=x.1=y.1/@v2: invalid new path: malformed import path "y.1/": trailing slash$'
 
 # go mod edit -fmt
 cp $WORK/go.mod.badfmt go.mod
 go mod edit -fmt -print # -print should avoid writing file
-cmpenv stdout $WORK/go.mod.goodfmt
+cmp stdout $WORK/go.mod.goodfmt
 cmp go.mod $WORK/go.mod.badfmt
 go mod edit -fmt # without -print, should write file (and nothing to stdout)
 ! stdout .
-cmpenv go.mod $WORK/go.mod.goodfmt
+cmp go.mod $WORK/go.mod.goodfmt
 
 # go mod edit -module
 cd $WORK/m
 go mod init a.a/b/c
 go mod edit -module x.x/y/z
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 
 # golang.org/issue/30513: don't require go-gettable module paths.
 cd $WORK/local
 go mod init foo
 go mod edit -module local-only -require=other-local@v1.0.0 -replace other-local@v1.0.0=./other
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 
 # go mod edit -godebug
 cd $WORK/g
 cp go.mod.start go.mod
 go mod edit -godebug key=value
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -dropgodebug key2
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -dropgodebug key
-cmpenv go.mod go.mod.start
+cmp go.mod go.mod.start
 
 # go mod edit -tool
 cd $WORK/h
 cp go.mod.start go.mod
 go mod edit -tool example.com/tool
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -droptool example.com/tool2
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -droptool example.com/tool
-cmpenv go.mod go.mod.start
+cmp go.mod go.mod.start
 
 # go mod edit -ignore
 cd $WORK/i
 cp go.mod.start go.mod
 go mod edit -ignore example.com/ignore
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -dropignore example.com/ignore2
-cmpenv go.mod go.mod.edit
+cmp go.mod go.mod.edit
 go mod edit -dropignore example.com/ignore
-cmpenv go.mod go.mod.start
+cmp go.mod go.mod.start
 
 -- x.go --
 package x
@@ -126,11 +129,11 @@ package w
 -- $WORK/go.mod.init --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 -- $WORK/go.mod.edit1 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 require x.1 v1.0.0
 
@@ -154,7 +157,7 @@ retract (
 -- $WORK/go.mod.edit2 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 exclude x.1 v1.2.0
 
@@ -171,7 +174,7 @@ require x.3 v1.99.0
        "Module": {
                "Path": "x.x/y/z"
        },
-       "Go": "$goversion",
+       "Go": "1.25.0",
        "Require": [
                {
                        "Path": "x.3",
@@ -211,7 +214,7 @@ require x.3 v1.99.0
 -- $WORK/go.mod.edit3 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 exclude x.1 v1.2.0
 
@@ -229,7 +232,7 @@ require x.3 v1.99.0
 -- $WORK/go.mod.edit4 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 exclude x.1 v1.2.0
 
@@ -244,7 +247,7 @@ require x.3 v1.99.0
 -- $WORK/go.mod.edit5 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 exclude x.1 v1.2.0
 
@@ -257,7 +260,7 @@ require x.3 v1.99.0
 -- $WORK/go.mod.edit6 --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 
 exclude x.1 v1.2.0
 
@@ -272,7 +275,7 @@ replace x.1 => ../y.1/@v2
 -- $WORK/local/go.mod.edit --
 module local-only
 
-go $goversion
+go 1.25.0
 
 require other-local v1.0.0
 
@@ -304,7 +307,7 @@ retract [v1.8.1, v1.8.2]
 -- $WORK/m/go.mod.edit --
 module x.x/y/z
 
-go $goversion
+go 1.25.0
 -- $WORK/go.mod.retractrationale --
 module x.x/y/z
 
@@ -405,4 +408,4 @@ module g
 
 go 1.24
 
-ignore example.com/ignore
\ No newline at end of file
+ignore example.com/ignore
diff --git a/src/cmd/go/testdata/script/mod_init_version.txt b/src/cmd/go/testdata/script/mod_init_version.txt
new file mode 100644 (file)
index 0000000..5b909bc
--- /dev/null
@@ -0,0 +1,47 @@
+env TESTGO_VERSION=go1.28-devel
+go mod init example.com
+cmp go.mod go.mod.want-1.26.0
+rm go.mod
+env TESTGO_VERSION=go1.26.0
+go mod init example.com
+cmp go.mod go.mod.want-1.25.0
+rm go.mod
+env TESTGO_VERSION=go1.22.2
+go mod init example.com
+cmp go.mod go.mod.want-1.21.0
+rm go.mod
+env TESTGO_VERSION=go1.25.0-xyzzy
+go mod init example.com
+cmp go.mod go.mod.want-1.24.0
+rm go.mod
+env TESTGO_VERSION=go1.23rc3
+go mod init example.com
+cmp go.mod go.mod.want-1.21.0
+rm go.mod
+env TESTGO_VERSION=go1.18beta2
+go mod init example.com
+cmp go.mod go.mod.want-1.16.0
+-- go.mod.want-1.26.0 --
+module example.com
+
+go 1.26.0
+-- go.mod.want-1.25.0 --
+module example.com
+
+go 1.25.0
+-- go.mod.want-1.24.0 --
+module example.com
+
+go 1.24.0
+-- go.mod.want-1.22.0 --
+module example.com
+
+go 1.22.0
+-- go.mod.want-1.21.0 --
+module example.com
+
+go 1.21.0
+-- go.mod.want-1.16.0 --
+module example.com
+
+go 1.16.0
index 31597928689552abaf79af4604caf4942c5f76cd..a2d3e85d0371c6d3e6116e01132da195531ffdf1 100644 (file)
@@ -1,4 +1,5 @@
 [short] skip 'runs go run'
+env TESTGO_VERSION=go1.26.0
 
 ! go work init doesnotexist
 stderr 'go: directory doesnotexist does not exist'
@@ -74,7 +75,7 @@ use (
        ../src/a
 )
 -- go.work.want --
-go $goversion
+go 1.25.0
 
 use (
        ./a
index 021346653fb6e30cb286dd8c0dd2d78fb27d4c26..2642d5b4c5a24902f56a49216863ed45bcf5fe2e 100644 (file)
@@ -1,4 +1,5 @@
 # Test editing go.work files.
+env TESTGO_VERSION=go1.26.0
 
 go work init m
 cmpenv go.work go.work.want_initial
@@ -54,11 +55,11 @@ module m
 
 go 1.18
 -- go.work.want_initial --
-go $goversion
+go 1.25.0
 
 use ./m
 -- go.work.want_use_n --
-go $goversion
+go 1.25.0
 
 use (
        ./m
index 0a2d3729fca4bf33e77a39ba86049c521970b563..72a55fc3d4eed7ca4b0f8114ff642787d3bafb46 100644 (file)
@@ -2,7 +2,7 @@
 # 'go work init . .. foo/bar' should produce a go.work file
 # with the same paths as 'go work init; go work use -r ..',
 # and it should have 'use .' rather than 'use ./.' inside.
-
+env TESTGO_VERSION=go1.23
 cd dir
 
 go work init . .. foo/bar
@@ -12,19 +12,19 @@ go work init
 go work use -r ..
 cmp go.work go.work.init
 
-cmpenv go.work $WORK/go.work.want
+cmp go.work $WORK/go.work.want
 
 -- go.mod --
 module example
 go 1.18
 -- dir/go.mod --
 module example
-go 1.18
+go 1.21.0
 -- dir/foo/bar/go.mod --
 module example
-go 1.18
+go 1.21.0
 -- $WORK/go.work.want --
-go $goversion
+go 1.21.0
 
 use (
        .
index 900ea2cf2fc4317b92631f0c2947f03bd45b59e1..012d22bea3ab582aeb41ab32d83ef8745b4fa68c 100644 (file)
@@ -8,13 +8,13 @@ go mod edit -C m1_22_0 -go=1.22.0 -toolchain=go1.99.0
 
 # work init writes the current Go version to the go line
 go work init
-grep '^go 1.50$' go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
-# work init with older modules should leave go 1.50 in the go.work.
+# work init with older modules should leave go 1.48.0 in the go.work.
 rm go.work
 go work init ./m1_22_0
-grep '^go 1.50$' go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
 # work init with newer modules should bump go,
@@ -31,5 +31,5 @@ env GOTOOLCHAIN=auto
 go work init ./m1_22_0
 stderr '^go: m1_22_0'${/}'go.mod requires go >= 1.22.0; switching to go1.22.9$'
 cat go.work
-grep '^go 1.22.9$' go.work
+grep '^go 1.22.0$' go.work
 ! grep toolchain go.work
diff --git a/src/cmd/go/testdata/script/work_init_version.txt b/src/cmd/go/testdata/script/work_init_version.txt
new file mode 100644 (file)
index 0000000..7472ded
--- /dev/null
@@ -0,0 +1,35 @@
+env TESTGO_VERSION=go1.28-devel
+go work init
+cmp go.work go.work.want-1.26.0
+rm go.work
+env TESTGO_VERSION=go1.26.0
+go work init
+cmp go.work go.work.want-1.25.0
+rm go.work
+env TESTGO_VERSION=go1.22.2
+go work init
+cmp go.work go.work.want-1.21.0
+rm go.work
+env TESTGO_VERSION=go1.25.0-xyzzy
+go work init
+cmp go.work go.work.want-1.24.0
+rm go.work
+env TESTGO_VERSION=go1.24rc3
+go work init
+cmp go.work go.work.want-1.22.0
+rm go.work
+env TESTGO_VERSION=go1.18beta2
+go work init
+cmp go.work go.work.want-1.16.0
+-- go.work.want-1.26.0 --
+go 1.26.0
+-- go.work.want-1.25.0 --
+go 1.25.0
+-- go.work.want-1.24.0 --
+go 1.24.0
+-- go.work.want-1.22.0 --
+go 1.22.0
+-- go.work.want-1.21.0 --
+go 1.21.0
+-- go.work.want-1.16.0 --
+go 1.16.0
index 989d6bb792148adac2172dc9307e652e8cd4b652..28b45556cdadd878d35a56316b81d71ba0404347 100644 (file)
@@ -11,13 +11,14 @@ go mod init -C m1_24_rc0
 go mod edit -C m1_24_rc0 -go=1.24rc0 -toolchain=go1.99.2
 
 go work init ./m1_22_0 ./m1_22_1
-grep '^go 1.50$' go.work
+cat go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
-# work sync with older modules should leave go 1.50 in the go.work.
+# work sync with older modules should leave go 1.48.0 in the go.work.
 go work sync
 cat go.work
-grep '^go 1.50$' go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
 # work sync with newer modules should update go 1.21 -> 1.22.1 and toolchain -> go1.22.9 in go.work
index d81e4a4c3ef13e8352abe347accfca44036e5576..8ab2a9901305f9fd92884c54a4eef4b575ffec79 100644 (file)
@@ -11,12 +11,12 @@ go mod init -C m1_24_rc0
 go mod edit -C m1_24_rc0 -go=1.24rc0 -toolchain=go1.99.2
 
 go work init
-grep '^go 1.50$' go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
-# work use with older modules should leave go 1.50 in the go.work.
+# work use with older modules should leave go 1.48.0 in the go.work.
 go work use ./m1_22_0
-grep '^go 1.50$' go.work
+grep '^go 1.48.0$' go.work
 ! grep toolchain go.work
 
 # work use with newer modules should bump go and toolchain,