]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: fix get with the new 'work' pattern
authorMichael Matloob <matloob@golang.org>
Fri, 23 May 2025 17:19:44 +0000 (13:19 -0400)
committerMichael Matloob <matloob@google.com>
Tue, 27 May 2025 20:01:16 +0000 (13:01 -0700)
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>
src/cmd/go/internal/modget/get.go
src/cmd/go/testdata/script/mod_get_nopkgs.txt
src/cmd/go/testdata/script/mod_get_work.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt [moved from src/cmd/go/testdata/script/mod_get_work_incomplete.txt with 98% similarity]

index 6867bdaa36f74f00a3d9c82a29ce22e3e4410ebd..31e9244e2dce54e045343af3ef89f0a4967e45c1 100644 (file)
@@ -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".
 //
index 14176a7dc8786d0538c10ea5ec61b1fe1adefdba..e2bfdf30a8d86005556f0efcff252063eb4b65b2 100644 (file)
@@ -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 (file)
index 0000000..39c7ea6
--- /dev/null
@@ -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"
similarity index 98%
rename from src/cmd/go/testdata/script/mod_get_work_incomplete.txt
rename to src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt
index ada2ae50f1a6b2a9628dbdc1452010cda4d971f9..89340ffb5735f1cf7450313ed9b105633627c189 100644 (file)
@@ -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