aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Matloob <matloob@golang.org>2025-05-23 13:19:44 -0400
committerMichael Matloob <matloob@google.com>2025-05-27 13:01:16 -0700
commit0e1b14bc2e13aed697854e3859f73ba4dba9fb22 (patch)
tree0759fc15258c6e6a83cee0dbb839a64a5d9721d6
parent09f1546cba2998a8d89f4506c16ba3bff115071d (diff)
downloadgo-0e1b14bc2e13aed697854e3859f73ba4dba9fb22.tar.xz
cmd/go: fix get with the new 'work' pattern
Before this change, go get didn't have support for the work pattern. The work pattern is new in Go 1.25 and evaluates to the packages in the work (also called main) modules. 'go get work' would cause a panic because 'work' would be incorrectly considered a path pattern and then queryPath would would try to query a metapackage pattern (resulting in the internal error panic). This change properly supports the work pattern in go get. It's pretty simple: First, we need to seprate the work pattern from the other patterns. Then in performWorkQueries, which maps queries to the modules that satisfy them, we return the single main module because by definition the work pattern is the set of packages in the work modules, and go get always runs in single module mode. (The exception is when the work module contains no packages, in which case we report a warning, and return no candidates because nothing is needed to resolve nothing). The rest of the work is already done by loading the packages matching the query and finding missing imports in the call to findAndUpgradeImports in runGet. Change-Id: I3c4610878b3d930a1d106cc59d9a0be194d966cd Reviewed-on: https://go-review.googlesource.com/c/go/+/675895 Reviewed-by: Michael Matloob <matloob@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
-rw-r--r--src/cmd/go/internal/modget/get.go35
-rw-r--r--src/cmd/go/testdata/script/mod_get_nopkgs.txt8
-rw-r--r--src/cmd/go/testdata/script/mod_get_work.txt46
-rw-r--r--src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt (renamed from src/cmd/go/testdata/script/mod_get_work_incomplete.txt)7
4 files changed, 96 insertions, 0 deletions
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 6867bdaa36..31e9244e2d 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -337,6 +337,7 @@ func runGet(ctx context.Context, cmd *base.Command, args []string) {
r.performLocalQueries(ctx)
r.performPathQueries(ctx)
r.performToolQueries(ctx)
+ r.performWorkQueries(ctx)
for {
r.performWildcardQueries(ctx)
@@ -513,6 +514,7 @@ type resolver struct {
pathQueries []*query // package path literal queries in original order
wildcardQueries []*query // path wildcard queries in original order
patternAllQueries []*query // queries with the pattern "all"
+ workQueries []*query // queries with the pattern "work"
toolQueries []*query // queries with the pattern "tool"
// Indexed "none" queries. These are also included in the slices above;
@@ -578,6 +580,8 @@ func newResolver(ctx context.Context, queries []*query) *resolver {
for _, q := range queries {
if q.pattern == "all" {
r.patternAllQueries = append(r.patternAllQueries, q)
+ } else if q.pattern == "work" {
+ r.workQueries = append(r.workQueries, q)
} else if q.pattern == "tool" {
r.toolQueries = append(r.toolQueries, q)
} else if q.patternIsLocal {
@@ -1070,6 +1074,37 @@ func (r *resolver) performToolQueries(ctx context.Context) {
}
}
+// performWorkQueries populates the candidates for each query whose pattern is "work".
+// The candidate module to resolve the work pattern is exactly the single main module.
+func (r *resolver) performWorkQueries(ctx context.Context) {
+ for _, q := range r.workQueries {
+ q.pathOnce(q.pattern, func() pathSet {
+ // TODO(matloob): Maybe export MainModules.mustGetSingleMainModule and call that.
+ // There are a few other places outside the modload package where we expect
+ // a single main module.
+ if len(modload.MainModules.Versions()) != 1 {
+ panic("internal error: number of main modules is not exactly one in resolution phase of go get")
+ }
+ mainModule := modload.MainModules.Versions()[0]
+
+ // We know what the result is going to be, assuming the main module is not
+ // empty, (it's the main module itself) but first check to see that there
+ // are packages in the main module, so that if there aren't any, we can
+ // return the expected warning that the pattern matched no packages.
+ match := modload.MatchInModule(ctx, q.pattern, mainModule, imports.AnyTags())
+ if len(match.Errs) > 0 {
+ return pathSet{err: match.Errs[0]}
+ }
+ if len(match.Pkgs) == 0 {
+ search.WarnUnmatched([]*search.Match{match})
+ return pathSet{} // There are no packages in the main module, so the main module isn't needed to resolve them.
+ }
+
+ return pathSet{pkgMods: []module.Version{mainModule}}
+ })
+ }
+}
+
// performPatternAllQueries populates the candidates for each query whose
// pattern is "all".
//
diff --git a/src/cmd/go/testdata/script/mod_get_nopkgs.txt b/src/cmd/go/testdata/script/mod_get_nopkgs.txt
index 14176a7dc8..e2bfdf30a8 100644
--- a/src/cmd/go/testdata/script/mod_get_nopkgs.txt
+++ b/src/cmd/go/testdata/script/mod_get_nopkgs.txt
@@ -29,6 +29,10 @@ stderr '^go: example\.net/emptysubdir/subdir/\.\.\.: module example\.net/emptysu
! go get builtin/... # in GOROOT/src, but contains no packages
stderr '^go: builtin/...: malformed module path "builtin": missing dot in first path element$'
+cd ../subdirmod
+go get work
+stderr -count=1 'matched no packages'
+
-- go.mod --
module example.net/emptysubdir
@@ -38,3 +42,7 @@ go 1.16
package emptysubdir
-- subdir/README.txt --
This module intentionally does not contain any p
+-- subdirmod/go.mod --
+module example.net/emptysubdir/subdirmod
+
+go 1.16
diff --git a/src/cmd/go/testdata/script/mod_get_work.txt b/src/cmd/go/testdata/script/mod_get_work.txt
new file mode 100644
index 0000000000..39c7ea6beb
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_get_work.txt
@@ -0,0 +1,46 @@
+# Test go get with the work pattern.
+
+# go get work gets dependencies to satisfy missing imports in the
+# main modules' package graph. Before the 'work' pattern existed, users
+# would have to run './...' in the root of the work (main) module.
+cp go.mod go.mod.orig
+go get work
+cmp go.mod go.mod.want
+
+# 'go get work' and 'go get all' behave very differently. Because
+# 'all' evaluates to work packages but also to their dependencies,
+# 'go get all' will run the 'get' logic on all the dependency module
+# packages, bumping all their modules to the latest versions.
+cp go.mod.orig go.mod
+go get all
+cmp go.mod go.mod.all.want
+-- go.mod --
+module example.com/a
+
+go 1.25
+-- go.mod.want --
+module example.com/a
+
+go 1.25
+
+require rsc.io/quote v1.5.2
+
+require (
+ golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
+ rsc.io/sampler v1.3.0 // indirect
+)
+-- go.mod.all.want --
+module example.com/a
+
+go 1.25
+
+require rsc.io/quote v1.5.2
+
+require (
+ golang.org/x/text v0.3.0 // indirect
+ rsc.io/sampler v1.99.99 // indirect
+)
+-- a.go --
+package a
+
+import _ "rsc.io/quote"
diff --git a/src/cmd/go/testdata/script/mod_get_work_incomplete.txt b/src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt
index ada2ae50f1..89340ffb57 100644
--- a/src/cmd/go/testdata/script/mod_get_work_incomplete.txt
+++ b/src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt
@@ -20,6 +20,13 @@ go get ./...
cmp go.mod go.mod.want
cmp go.sum go.sum.want
+# Test go get with an incomplete module using a "work" query.
+cp go.mod.orig go.mod
+rm go.sum
+go get work
+cmp go.mod go.mod.want
+cmp go.sum go.sum.want
+
# Test go get with an incomplete module using a path query that can be resolved.
cp go.mod.orig go.mod
rm go.sum