aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd/go/internal/modload/buildlist.go12
-rw-r--r--src/cmd/go/internal/modload/load.go12
-rw-r--r--src/cmd/go/testdata/script/work_sync_check_multiple_paths_once.txt56
3 files changed, 77 insertions, 3 deletions
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index a17626e3f5..73e881ef3c 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -295,6 +295,10 @@ type ModuleGraph struct {
buildListOnce sync.Once
buildList []module.Version
+
+ // checkPathsOnce ensures checkMultiplePaths runs at most once per graph.
+ // Errors are checked and reported only on the first call.
+ checkPathsOnce sync.Once
}
var readModGraphDebugOnce sync.Once
@@ -797,7 +801,13 @@ func updateWorkspaceRoots(ld *Loader, ctx context.Context, direct map[string]boo
// return an error.
panic("add is not empty")
}
- return newRequirements(ld, workspace, rs.rootModules, direct), nil
+ newRS := newRequirements(ld, workspace, rs.rootModules, direct)
+ // The root modules are unchanged (only the direct imports change),
+ // so the module graph can be reused to avoid rebuilding it from scratch.
+ if cached := rs.graph.Load(); cached != nil {
+ newRS.graphOnce.Do(func() { newRS.graph.Store(cached) })
+ }
+ return newRS, nil
}
// tidyPrunedRoots returns a minimal set of root requirements that maintains the
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 4efecdf8a1..4706d05e91 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -2020,13 +2020,21 @@ func (pld *packageLoader) computePatternAll() (all []string) {
//
// (See https://golang.org/issue/26607 and https://golang.org/issue/34650.)
func (pld *packageLoader) checkMultiplePaths(ld *Loader) {
- mods := pld.requirements.rootModules
if cached := pld.requirements.graph.Load(); cached != nil {
if mg := cached.mg; mg != nil {
- mods = mg.BuildList()
+ // The check depends only on the build list and workspace replace
+ // directives, both fixed for the lifetime of mg, so skip it on
+ // subsequent calls sharing the same graph.
+ mg.checkPathsOnce.Do(func() {
+ checkMultiplePathsUncached(ld, pld, mg.BuildList())
+ })
+ return
}
}
+ checkMultiplePathsUncached(ld, pld, pld.requirements.rootModules)
+}
+func checkMultiplePathsUncached(ld *Loader, pld *packageLoader, mods []module.Version) {
firstPath := map[module.Version]string{}
for _, mod := range mods {
src := resolveReplacement(ld, mod)
diff --git a/src/cmd/go/testdata/script/work_sync_check_multiple_paths_once.txt b/src/cmd/go/testdata/script/work_sync_check_multiple_paths_once.txt
new file mode 100644
index 0000000000..bc2fd1f120
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_sync_check_multiple_paths_once.txt
@@ -0,0 +1,56 @@
+# Test that checkMultiplePaths reports errors exactly once in a workspace with
+# multiple modules, even though it is called once per workspace module.
+#
+# example.com/a and example.com/b are both replaced by ../shared (which
+# canonicalizes to the same path in workspace mode), triggering a "used for
+# two different module paths" error. With three workspace modules the check
+# is called three times; the error should appear in stderr exactly once.
+
+go work sync
+stderr -count=1 'used for two different module paths'
+
+-- go.work --
+go 1.18
+
+use (
+ ./m
+ ./n
+ ./o
+)
+
+-- m/go.mod --
+module example.com/m
+
+go 1.18
+
+require example.com/a v1.0.0
+
+replace example.com/a v1.0.0 => ../shared
+
+-- m/m.go --
+package m
+
+-- n/go.mod --
+module example.com/n
+
+go 1.18
+
+require example.com/b v1.0.0
+
+replace example.com/b v1.0.0 => ../shared
+
+-- n/n.go --
+package n
+
+-- o/go.mod --
+module example.com/o
+
+go 1.18
+
+-- o/o.go --
+package o
+
+-- shared/go.mod --
+module example.com/shared
+
+go 1.18