Skip to content

Commit f394cd7

Browse files
Bryan C. Millsgopherbot
Bryan C. Mills
authored andcommitted
[release-branch.go1.19] cmd/go: retain extra roots to disambiguate imports in 'go mod tidy'
We don't normally keep explicit requirements for test dependencies of packages loaded from other modules when the required version is already the selected version in the module graph. However, in some cases we may need to keep an explicit requirement in order to make use of lazy module loading to disambiguate an otherwise-ambiguous import. Note that there is no Go version guard for this change: in the cases where the behavior of 'go mod tidy' has changed, previous versions of Go would produce go.mod files that break successive calls to 'go mod tidy'. Given that, I suspect that any existing user in the wild affected by this bug either already has a workaround in place using redundant import statements (in which case the change does not affect them) or is running 'go mod tidy -e' to force past the error (in which case a change in behavior to a non-error should not be surprising). Updates #60313. Fixes #60351. Change-Id: Idf294f72cbe3904b871290d79e4493595a0c7bfc Reviewed-on: https://go-review.googlesource.com/c/go/+/496635 Auto-Submit: Bryan Mills <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Reviewed-by: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]> (cherry picked from commit 2ed6a54) Reviewed-on: https://go-review.googlesource.com/c/go/+/499636 TryBot-Bypass: Bryan Mills <[email protected]> Auto-Submit: Dmitri Shuralyov <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 606a5a6 commit f394cd7

File tree

2 files changed

+129
-6
lines changed

2 files changed

+129
-6
lines changed

src/cmd/go/internal/modload/buildlist.go

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"cmd/go/internal/mvs"
1111
"cmd/go/internal/par"
1212
"context"
13+
"errors"
1314
"fmt"
1415
"os"
1516
"reflect"
@@ -689,8 +690,8 @@ func updateWorkspaceRoots(ctx context.Context, rs *Requirements, add []module.Ve
689690
// roots) until the set of roots has converged.
690691
func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
691692
var (
692-
roots []module.Version
693-
pathIncluded = map[string]bool{mainModule.Path: true}
693+
roots []module.Version
694+
pathIsRoot = map[string]bool{mainModule.Path: true}
694695
)
695696
// We start by adding roots for every package in "all".
696697
//
@@ -710,9 +711,9 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[
710711
if !pkg.flags.has(pkgInAll) {
711712
continue
712713
}
713-
if pkg.fromExternalModule() && !pathIncluded[pkg.mod.Path] {
714+
if pkg.fromExternalModule() && !pathIsRoot[pkg.mod.Path] {
714715
roots = append(roots, pkg.mod)
715-
pathIncluded[pkg.mod.Path] = true
716+
pathIsRoot[pkg.mod.Path] = true
716717
}
717718
queue = append(queue, pkg)
718719
queued[pkg] = true
@@ -744,11 +745,12 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[
744745
queue = append(queue, pkg.test)
745746
queued[pkg.test] = true
746747
}
747-
if !pathIncluded[m.Path] {
748+
749+
if !pathIsRoot[m.Path] {
748750
if s := mg.Selected(m.Path); cmpVersion(s, m.Version) < 0 {
749751
roots = append(roots, m)
752+
pathIsRoot[m.Path] = true
750753
}
751-
pathIncluded[m.Path] = true
752754
}
753755
}
754756

@@ -758,10 +760,62 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[
758760
}
759761
}
760762

763+
roots = tidy.rootModules
761764
_, err := tidy.Graph(ctx)
762765
if err != nil {
763766
return nil, err
764767
}
768+
769+
// We try to avoid adding explicit requirements for test-only dependencies of
770+
// packages in external modules. However, if we drop the explicit
771+
// requirements, that may change an import from unambiguous (due to lazy
772+
// module loading) to ambiguous (because lazy module loading no longer
773+
// disambiguates it). For any package that has become ambiguous, we try
774+
// to fix it by promoting its module to an explicit root.
775+
// (See https://go.dev/issue/60313.)
776+
q := par.NewQueue(runtime.GOMAXPROCS(0))
777+
for {
778+
var disambiguateRoot sync.Map
779+
for _, pkg := range pkgs {
780+
if pkg.mod.Path == "" || pathIsRoot[pkg.mod.Path] {
781+
// Lazy module loading will cause pkg.mod to be checked before any other modules
782+
// that are only indirectly required. It is as unambiguous as possible.
783+
continue
784+
}
785+
pkg := pkg
786+
q.Add(func() {
787+
skipModFile := true
788+
_, _, _, _, err := importFromModules(ctx, pkg.path, tidy, nil, skipModFile)
789+
if aie := (*AmbiguousImportError)(nil); errors.As(err, &aie) {
790+
disambiguateRoot.Store(pkg.mod, true)
791+
}
792+
})
793+
}
794+
<-q.Idle()
795+
796+
disambiguateRoot.Range(func(k, _ any) bool {
797+
m := k.(module.Version)
798+
roots = append(roots, m)
799+
pathIsRoot[m.Path] = true
800+
return true
801+
})
802+
803+
if len(roots) > len(tidy.rootModules) {
804+
module.Sort(roots)
805+
tidy = newRequirements(pruned, roots, tidy.direct)
806+
_, err = tidy.Graph(ctx)
807+
if err != nil {
808+
return nil, err
809+
}
810+
// Adding these roots may have pulled additional modules into the module
811+
// graph, causing additional packages to become ambiguous. Keep iterating
812+
// until we reach a fixed point.
813+
continue
814+
}
815+
816+
break
817+
}
818+
765819
return tidy, nil
766820
}
767821

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Regression test for https://go.dev/issue/60313: 'go mod tidy' did not preserve
2+
# dependencies needed to prevent 'ambiguous import' errors in external test
3+
# dependencies.
4+
5+
cp go.mod go.mod.orig
6+
go mod tidy
7+
cmp go.mod go.mod.orig
8+
9+
-- go.mod --
10+
module example
11+
12+
go 1.19
13+
14+
require (
15+
example.net/a v0.1.0
16+
example.net/b v0.1.0
17+
)
18+
19+
require example.net/outer/inner v0.1.0 // indirect
20+
21+
replace (
22+
example.net/a v0.1.0 => ./a
23+
example.net/b v0.1.0 => ./b
24+
example.net/outer v0.1.0 => ./outer
25+
example.net/outer/inner v0.1.0 => ./inner
26+
)
27+
-- example.go --
28+
package example
29+
30+
import (
31+
_ "example.net/a"
32+
_ "example.net/b"
33+
)
34+
-- a/go.mod --
35+
module example.net/a
36+
37+
go 1.19
38+
39+
require example.net/outer/inner v0.1.0
40+
-- a/a.go --
41+
package a
42+
-- a/a_test.go --
43+
package a_test
44+
45+
import _ "example.net/outer/inner"
46+
-- b/go.mod --
47+
module example.net/b
48+
49+
go 1.19
50+
51+
require example.net/outer v0.1.0
52+
-- b/b.go --
53+
package b
54+
-- b/b_test.go --
55+
package b_test
56+
57+
import _ "example.net/outer/inner"
58+
-- inner/go.mod --
59+
module example.net/outer/inner
60+
61+
go 1.19
62+
-- inner/inner.go --
63+
package inner
64+
-- outer/go.mod --
65+
module example.net/outer
66+
67+
go 1.19
68+
-- outer/inner/inner.go --
69+
package inner

0 commit comments

Comments
 (0)