cmd/go: simplify go.mod to have at most two require sections

This change simplifies go.mod files to have at most two require blocks
(one for direct and one for indirect dependencies) when the go version
is 1.27 or higher.

Fixes #56471.

Change-Id: I71bb41511575d02697945eb0fab787391018e652
Reviewed-on: https://go-review.googlesource.com/c/go/+/739061
Reviewed-by: Michael Matloob <matloob@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
This commit is contained in:
Ian Alexander 2026-01-24 21:23:43 -05:00
parent f3f3d0859a
commit 21e3cdefc3
6 changed files with 217 additions and 12 deletions

View file

@ -6,7 +6,7 @@ require (
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83
golang.org/x/arch v0.23.1-0.20260109160903-657d90bd6695
golang.org/x/build v0.0.0-20260122183339-3ba88df37c64
golang.org/x/mod v0.35.0
golang.org/x/mod v0.36.1-0.20260513122029-343ee60345a1
golang.org/x/sync v0.20.0
golang.org/x/sys v0.44.0
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa

View file

@ -10,8 +10,8 @@ golang.org/x/arch v0.23.1-0.20260109160903-657d90bd6695 h1:q45HsUyFzBjBk4mHGgUew
golang.org/x/arch v0.23.1-0.20260109160903-657d90bd6695/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/build v0.0.0-20260122183339-3ba88df37c64 h1:BNhBATNmH/VtzGolB+ksQPPvn6ZyffiR8TmKenqNo+A=
golang.org/x/build v0.0.0-20260122183339-3ba88df37c64/go.mod h1:3QmSbNil8ZWqC94m80Glej1v8b92gYzPIQPTtSa0c+4=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/mod v0.36.1-0.20260513122029-343ee60345a1 h1:C0TwvxhsI0bHc1TbK4QEa5PCMrHiST7y/lpX4MVW3KM=
golang.org/x/mod v0.36.1-0.20260513122029-343ee60345a1/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=

View file

@ -1989,8 +1989,10 @@ func UpdateGoModFromReqs(ld *Loader, ctx context.Context, opts WriteOpts) (befor
// Update require blocks.
if gover.Compare(goVersion, gover.SeparateIndirectVersion) < 0 {
modFile.SetRequire(list)
} else {
} else if gover.Compare(goVersion, gover.SimplifyRequireVersion) < 0 {
modFile.SetRequireSeparateIndirect(list)
} else {
modFile.SetRequireAtMostTwo(list)
}
modFile.Cleanup()
after, err = modFile.Format()

View file

@ -0,0 +1,155 @@
# If go.mod has go 1.27 or higher, multiple require blocks should be
# consolidated. Even those with comments.
cp go.mod.127 go.mod
go mod tidy
cmp go.mod go.mod.127tidy
# If go.mod has go 1.26, blocks with comments should be preserved.
cp go.mod.126 go.mod
go mod tidy
cmp go.mod go.mod.126tidy
-- go.mod.127 --
module example.com/m
go 1.27
require example.com/a v1.0.0
// Block comment
require (
example.com/b v1.0.0
)
// Another block comment
require (
example.com/d v1.0.0 // an inline comment
example.com/c v1.0.0 // indirect
)
// A third block comment
require (
example.com/e v1.0.0 // indirect
)
replace (
example.com/a v1.0.0 => ./a
example.com/b v1.0.0 => ./b
example.com/c v1.0.0 => ./c
example.com/d v1.0.0 => ./d
example.com/e v1.0.0 => ./e
)
-- go.mod.127tidy --
module example.com/m
go 1.27
// Block comment
//
// Another block comment
require (
example.com/a v1.0.0
example.com/b v1.0.0
example.com/d v1.0.0 // an inline comment
)
// A third block comment
require (
example.com/c v1.0.0 // indirect
example.com/e v1.0.0 // indirect
)
replace (
example.com/a v1.0.0 => ./a
example.com/b v1.0.0 => ./b
example.com/c v1.0.0 => ./c
example.com/d v1.0.0 => ./d
example.com/e v1.0.0 => ./e
)
-- go.mod.126 --
module example.com/m
go 1.26
require example.com/a v1.0.0
// Block comment
require (
example.com/b v1.0.0
example.com/d v1.0.0
)
require example.com/c v1.0.0 // indirect
replace (
example.com/a v1.0.0 => ./a
example.com/b v1.0.0 => ./b
example.com/c v1.0.0 => ./c
example.com/d v1.0.0 => ./d
example.com/e v1.0.0 => ./e
)
-- go.mod.126tidy --
module example.com/m
go 1.26
require example.com/a v1.0.0
// Block comment
require (
example.com/b v1.0.0
example.com/d v1.0.0
)
require (
example.com/c v1.0.0 // indirect
example.com/e v1.0.0 // indirect
)
replace (
example.com/a v1.0.0 => ./a
example.com/b v1.0.0 => ./b
example.com/c v1.0.0 => ./c
example.com/d v1.0.0 => ./d
example.com/e v1.0.0 => ./e
)
-- m.go --
package m
import _ "example.com/a"
import _ "example.com/b"
import _ "example.com/d"
-- a/go.mod --
module example.com/a
go 1.26
require example.com/c v1.0.0
require example.com/e v1.0.0
-- a/a.go --
package a
import _ "example.com/c"
import _ "example.com/e"
-- b/go.mod --
module example.com/b
go 1.26
-- b/b.go --
package b
-- c/go.mod --
module example.com/c
go 1.26
-- c/c.go --
package c
-- d/go.mod --
module example.com/d
go 1.26
-- d/d.go --
package d
-- e/go.mod --
module example.com/e
go 1.26
-- e/e.go --
package e

View file

@ -327,6 +327,7 @@ func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parse
}
var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`)
var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`)
// Toolchains must be named beginning with `go1`,
@ -1272,6 +1273,17 @@ func (f *File) SetRequire(req []*Require) {
// SetRequireSeparateIndirect will split it into a direct-only and indirect-only
// block. This aids in the transition to separate blocks.
func (f *File) SetRequireSeparateIndirect(req []*Require) {
f.setRequireSeparateIndirect(req, false)
}
// SetRequireAtMostTwo is like SetRequireSeparateIndirect but it aggressively
// consolidates all requirements into at most two blocks (one direct, one indirect).
// It ignores existing blocks and comments when deciding where to place requirements.
func (f *File) SetRequireAtMostTwo(req []*Require) {
f.setRequireSeparateIndirect(req, true)
}
func (f *File) setRequireSeparateIndirect(req []*Require, simplify bool) {
// hasComments returns whether a line or block has comments
// other than "indirect".
hasComments := func(c Comments) bool {
@ -1304,6 +1316,17 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
}
// Examine existing require lines and blocks.
need := make(map[string]*Require)
for _, r := range req {
need[r.Mod.Path] = r
}
lineIndirect := make(map[*Line]bool)
for _, r := range f.Require {
if n := need[r.Mod.Path]; n != nil {
lineIndirect[r.Syntax] = n.Indirect
}
}
var (
// We may insert new requirements into the last uncommented
// direct-only and indirect-only blocks. We may also move requirements
@ -1321,7 +1344,9 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
// Track the block each requirement belongs to (if any) so we can
// move them later.
lineToBlock = make(map[*Line]*LineBlock)
lineToBlock = make(map[*Line]*LineBlock)
directBlockComments []Comment
indirectBlockComments []Comment
)
for i, stmt := range f.Syntax.Stmt {
switch stmt := stmt.(type) {
@ -1364,6 +1389,24 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
if allIndirect {
lastIndirectIndex = i
}
if simplify {
anyDirect := false
for _, line := range stmt.Line {
if ind, ok := lineIndirect[line]; ok && !ind {
anyDirect = true
break
}
}
target := &directBlockComments
if !anyDirect && len(stmt.Line) > 0 {
target = &indirectBlockComments
}
if len(*target) > 0 && len(stmt.Comments.Before) > 0 {
*target = append(*target, Comment{Token: "//"})
}
*target = append(*target, stmt.Comments.Before...)
stmt.Comments.Before = nil
}
}
}
@ -1422,6 +1465,15 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
lastIndirectBlock = ensureBlock(lastIndirectIndex)
}
if simplify {
if len(directBlockComments) > 0 {
lastDirectBlock.Comments.Before = append(lastDirectBlock.Comments.Before, directBlockComments...)
}
if len(indirectBlockComments) > 0 {
lastIndirectBlock.Comments.Before = append(lastIndirectBlock.Comments.Before, indirectBlockComments...)
}
}
// Delete requirements we don't want anymore.
// Update versions and indirect comments on requirements we want to keep.
// If a requirement is in last{Direct,Indirect}Block with the wrong
@ -1430,10 +1482,6 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
// correct block.
//
// Some blocks may be empty after this. Cleanup will remove them.
need := make(map[string]*Require)
for _, r := range req {
need[r.Mod.Path] = r
}
have := make(map[string]*Require)
for _, r := range f.Require {
path := r.Mod.Path
@ -1446,10 +1494,10 @@ func (f *File) SetRequireSeparateIndirect(req []*Require) {
r.setVersion(need[path].Mod.Version)
r.setIndirect(need[path].Indirect)
if need[path].Indirect &&
(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
(simplify || oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) {
moveReq(r, lastIndirectBlock)
} else if !need[path].Indirect &&
(oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
(simplify || oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) {
moveReq(r, lastDirectBlock)
}
}

View file

@ -28,7 +28,7 @@ golang.org/x/arch/x86/x86asm
# golang.org/x/build v0.0.0-20260122183339-3ba88df37c64
## explicit; go 1.24.9
golang.org/x/build/relnote
# golang.org/x/mod v0.35.0
# golang.org/x/mod v0.36.1-0.20260513122029-343ee60345a1
## explicit; go 1.25.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile