cmd/go: update default go directive in mod or work init

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>
This commit is contained in:
Ian Alexander
2025-11-13 16:38:20 -05:00
parent 3c26aef8fb
commit 337f7b1f5d
11 changed files with 171 additions and 60 deletions

View 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 == ""
}

View 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)
}

View 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
ignore example.com/ignore

View File

@@ -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

View 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

View 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

View 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 (
.

View 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

View File

@@ -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

View 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

View 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,