From 0e1b14bc2e13aed697854e3859f73ba4dba9fb22 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Fri, 23 May 2025 13:19:44 -0400 Subject: 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 LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt --- src/cmd/go/internal/modget/get.go | 35 ++ src/cmd/go/testdata/script/mod_get_nopkgs.txt | 8 + src/cmd/go/testdata/script/mod_get_work.txt | 46 +++ .../go/testdata/script/mod_get_work_incomplete.txt | 378 -------------------- .../script/mod_get_workspace_incomplete.txt | 385 +++++++++++++++++++++ 5 files changed, 474 insertions(+), 378 deletions(-) create mode 100644 src/cmd/go/testdata/script/mod_get_work.txt delete mode 100644 src/cmd/go/testdata/script/mod_get_work_incomplete.txt create mode 100644 src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt 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_work_incomplete.txt deleted file mode 100644 index ada2ae50f1..0000000000 --- a/src/cmd/go/testdata/script/mod_get_work_incomplete.txt +++ /dev/null @@ -1,378 +0,0 @@ -# Enter the first set of test cases. In this test case, package -# example.com/m has an import of example.com/n, which is also -# in the workspace, but is not required by example.com/m, and does not exist -# upstream. It also has an import of rsc.io/quote, which -# is also not required by example.com/m but does exist upstream. get should resolve -# rsc.io/quote and not try to resolve example.com/n. -cd m -cp go.mod go.mod.orig - -# Test go get with an incomplete module using a local query. -cp go.mod.orig go.mod -go get -cmp go.mod go.mod.want -cmp go.sum go.sum.want - -# Test go get with an incomplete module using a wildcard query. -cp go.mod.orig go.mod -rm go.sum -go get ./... -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 -go get rsc.io/quote -cmp go.mod go.mod.want.path_query # query wasn't resolved through import, so don't know if it's direct -cmp go.sum go.sum.want - -# Test go get with a path query that is to a workspace module but that can't be resolved. -# Normally, when we encounter an unresolved import of a workspace module, it's -# ignored, but a path query of the module was asked for explicitly and isn't ignored. -cp go.mod.orig go.mod -rm go.sum -! go get example.com/n -# The following error is returned because module example.com does exist in the proxy we use -# to run these tests, and because its is a prefix of example.com/n, it is a candidate to -# satisfy the import. -stderr 'module example.com@upgrade found \(v1\.0\.0\), but does not contain package example.com/n' - -# Test go get with an incomplete module using an "all" query. -cp go.mod.orig go.mod -rm go.sum -go get all -cmp go.mod go.mod.want.all # all loads a different graph so the requirements get bumped up -cmp go.sum go.sum.want.all - -# Test go get with an incomplete module using a tool query -# The hastool directory has its own go.work file than includes example.com/n and hastool. -cd ../hastool -go get tool -cmp go.mod go.mod.want - -# Test that missing imports from loading the workspace are reported. -# In this example, there is a workspace with the -# example.com/missingworkspaceimport and example.com/withmissing modules. -# missingworkspaceimport imports withmissing, and withmissing in turn -# imports rsc.io/quote, but doesn't have a requirement on it. -# The get operation won't resolve rsc.io/quote because it doesn't -# appear in the missingworkspaceimport's module graph, and the -# workspace will fail to load in checkPackageProblems because of the missing import. -cd ../missingworkspaceimport -! go get ./... -stderr 'cannot find module providing package rsc.io/quote' - -# Test that missing imports are not reported if they're not in the package -# graph. This test case is the same as the above except that there's no -# import from the missingworkspaceimport package to the one that -# imports the unresolved rsc.io/quote dependency. The example.com/missingworkspaceimport -# package imports example.com/withmissing/other so it still depends on the example.com/missing -# module, but not on the withmissing package itself. The example.com/withmissing -# module still has an import on the rsc.io/quote package, but the package -# with the import doesn't appear in the loaded package graph. -cd ../missingworkspaceimport_disconnected -go get ./... - -# Test that deprecations are reported using the workspace. -# First, the control case: without the workspace, the deprecated module -# is an indirect dependency of example.com/withdeprecation/indirect, -# so we shouldn't get a deprecation warning. -cd ../withdeprecation/indirect -cp go.mod go.mod.orig -env GOWORK=off -go get ./... -! stderr 'is deprecated' -cmp go.mod go.mod.want -# Now, in the workspace, we should get a deprecation warning, because -# the deprecated module is a direct dependency of example.com/withdeprecation/direct, which -# is a workspace module. -cp go.mod.orig go.mod -env GOWORK= -go get ./... -stderr 'go: module example.com/deprecated/b is deprecated: in example.com/deprecated/b@v1.9.0' -cmp go.mod go.mod.want - -# Test that retractions are reported using the workspace. -# First, the control case. Even though a workspace module depends on -# a retracted version, because we didn't ask for it on the command line, -# we didn't resolve that retracted module to satisfy an import, -# or need it to build a requested package, we don't produce the warning. -cd ../../withretraction/doesnotrequireretracted -cp go.mod go.mod.orig -go get rsc.io/quote -! stderr 'retracted' -# If we do request a non-retracted version of the module but the workspace -# is off, we also won't see the retraction warning because the retracted -# module isn't selected in the graph. -cp go.mod.orig go.mod -env GOWORK=off -go get example.com/retract@v1.0.0-good -! stderr 'retracted' -# Now, with the workspace on, because example.com/retract@v1.0.0-unused -# is a higher version, it will be selected and the retraction will -# be reported. -cp go.mod.orig go.mod -env GOWORK= -go get example.com/retract@v1.0.0-good -stderr 'retracted' -# Finally, with the workspace on, if the other workspace depends on -# example.com/retract@v1.0.0-bad rather than 'v1.0.0-unused', because -# 'v1.0.0-bad' is considered a lower version than 'v1.0.0-good', 'v1.0.0-good' -# will be selected and the deprecation will not be reported. -cp go.mod.orig go.mod -cd ../requiresretracted -go get example.com/retract@v1.0.0-bad # set the verison to 'v1.0.0-bad' -stderr 'retracted' -cd ../doesnotrequireretracted -go get example.com/retract@v1.0.0-good -! stderr 'retracted' - --- go.work -- -go 1.25 - -use ( - m - n -) --- q/go.mod -- -module example.com/q - -go 1.25 --- q/q.go -- -package q - -import "rsc.io/quote" - -func Q() { - quote.Hello() -} --- m/go.mod -- -module example.com/m - -go 1.25 --- m/go.mod.want -- -module example.com/m - -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 -) --- m/go.mod.want.path_query -- -module example.com/m - -go 1.25 - -require ( - golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect - rsc.io/quote v1.5.2 // indirect - rsc.io/sampler v1.3.0 // indirect -) --- m/go.sum.want -- -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0= -rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= -rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= --- m/go.mod.want.all -- -module example.com/m - -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 -) --- m/go.sum.want.all -- -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:ivTorhoiROmZ1mcs15mO2czVF0uy0tnezXpBVNzgrmA= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0= -rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -rsc.io/sampler v1.99.99 h1:iMG9lbEG/8MdeR4lgL+Q8IcwbLNw7ijW7fTiK8Miqts= -rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= --- m/m.go -- -package m - -import ( - "example.com/n" - "rsc.io/quote" -) - -func M() { - n.Hello() - quote.Hello() -} --- n/go.mod -- -module example.com/n - -go 1.25 --- n/n.go -- -package n - -func Hello() { -} --- hastool/go.work -- -go 1.25 - -use ( - . - ../n -) --- hastool/go.mod -- -module example.com/hastool - -go 1.25 - -tool rsc.io/fortune --- hastool/go.mod.want -- -module example.com/hastool - -go 1.25 - -tool rsc.io/fortune - -require ( - golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect - rsc.io/fortune v1.0.0 // indirect - rsc.io/quote v1.5.2 // indirect - rsc.io/sampler v1.3.0 // indirect -) --- hastool/p.go -- -package hastool - -import "example.com/n" - -func T() { - n.Hello() -} --- missingworkspaceimport/go.work -- -go 1.25 - -use ( - . - withmissing -) --- missingworkspaceimport/go.mod -- -module example.com/missingworkspaceimport - -go 1.25 --- missingworkspaceimport/m.go -- -package m - -import _ "example.com/withmissing" --- missingworkspaceimport/withmissing/go.mod -- -module example.com/withmissing - -go 1.25 --- missingworkspaceimport/withmissing/w.go -- -package w - -import _ "rsc.io/quote" --- missingworkspaceimport_disconnected/go.work -- -go 1.25 - -use ( - . - withmissing -) --- missingworkspaceimport_disconnected/go.mod -- -module example.com/missingworkspaceimport - -go 1.25 --- missingworkspaceimport_disconnected/m.go -- -package m - -import _ "example.com/withmissing/other" --- missingworkspaceimport_disconnected/withmissing/go.mod -- -module example.com/withmissing - -go 1.25 --- missingworkspaceimport_disconnected/withmissing/w.go -- -package w - -import _ "rsc.io/quote" --- missingworkspaceimport_disconnected/withmissing/other/other.go -- -package other --- withdeprecation/go.work -- -go 1.25 - -use ( - indirect - direct -) - -replace example.com/requiresdeprecatednotworkspace => ./requiresdeprecatednotworkspace --- withdeprecation/indirect/go.mod -- -module example.com/withdeprecation/indirect - -go 1.25 - -replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace --- withdeprecation/indirect/go.mod.want -- -module example.com/withdeprecation/indirect - -go 1.25 - -replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace - -require example.com/requiresdeprecatednotworkspace v0.0.0-00010101000000-000000000000 - -require example.com/deprecated/b v1.9.0 // indirect --- withdeprecation/indirect/go.mod.want.direct -- -module example.com/withdeprecation/indirect - -go 1.25 - -replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace - -require example.com/requiresdeprecatednotworkspace v0.0.0-00010101000000-000000000000 - -require example.com/deprecated/b v1.9.0 --- withdeprecation/indirect/a.go -- -package indirect - -import "example.com/requiresdeprecatednotworkspace" --- withdeprecation/direct/go.mod -- -module example.com/withdeprecation/direct - -go 1.25 - -require "example.com/deprecated/b" v1.9.0 --- withdeprecation/direct/import.go -- -package direct - -import "example.com/deprecated/b" --- withdeprecation/requiresdeprecatednotworkspace/go.mod -- -module example.com/requiresdeprecatednotworkspace - -go 1.25 --- withdeprecation/requiresdeprecatednotworkspace/a.go -- -package a - -import "example.com/deprecated/b" --- withretraction/go.work -- -go 1.25 - -use ( - doesnotrequireretracted - requiresretracted -) --- withretraction/doesnotrequireretracted/go.mod -- -module example.com/withretraction/doesnotrequireretracted - -go 1.25 --- withretraction/requiresretracted/go.mod -- -module example.com/withretraction/requiresretracted - -go 1.25 - -require example.com/retract v1.0.0-unused diff --git a/src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt b/src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt new file mode 100644 index 0000000000..89340ffb57 --- /dev/null +++ b/src/cmd/go/testdata/script/mod_get_workspace_incomplete.txt @@ -0,0 +1,385 @@ +# Enter the first set of test cases. In this test case, package +# example.com/m has an import of example.com/n, which is also +# in the workspace, but is not required by example.com/m, and does not exist +# upstream. It also has an import of rsc.io/quote, which +# is also not required by example.com/m but does exist upstream. get should resolve +# rsc.io/quote and not try to resolve example.com/n. +cd m +cp go.mod go.mod.orig + +# Test go get with an incomplete module using a local query. +cp go.mod.orig go.mod +go get +cmp go.mod go.mod.want +cmp go.sum go.sum.want + +# Test go get with an incomplete module using a wildcard query. +cp go.mod.orig go.mod +rm go.sum +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 +go get rsc.io/quote +cmp go.mod go.mod.want.path_query # query wasn't resolved through import, so don't know if it's direct +cmp go.sum go.sum.want + +# Test go get with a path query that is to a workspace module but that can't be resolved. +# Normally, when we encounter an unresolved import of a workspace module, it's +# ignored, but a path query of the module was asked for explicitly and isn't ignored. +cp go.mod.orig go.mod +rm go.sum +! go get example.com/n +# The following error is returned because module example.com does exist in the proxy we use +# to run these tests, and because its is a prefix of example.com/n, it is a candidate to +# satisfy the import. +stderr 'module example.com@upgrade found \(v1\.0\.0\), but does not contain package example.com/n' + +# Test go get with an incomplete module using an "all" query. +cp go.mod.orig go.mod +rm go.sum +go get all +cmp go.mod go.mod.want.all # all loads a different graph so the requirements get bumped up +cmp go.sum go.sum.want.all + +# Test go get with an incomplete module using a tool query +# The hastool directory has its own go.work file than includes example.com/n and hastool. +cd ../hastool +go get tool +cmp go.mod go.mod.want + +# Test that missing imports from loading the workspace are reported. +# In this example, there is a workspace with the +# example.com/missingworkspaceimport and example.com/withmissing modules. +# missingworkspaceimport imports withmissing, and withmissing in turn +# imports rsc.io/quote, but doesn't have a requirement on it. +# The get operation won't resolve rsc.io/quote because it doesn't +# appear in the missingworkspaceimport's module graph, and the +# workspace will fail to load in checkPackageProblems because of the missing import. +cd ../missingworkspaceimport +! go get ./... +stderr 'cannot find module providing package rsc.io/quote' + +# Test that missing imports are not reported if they're not in the package +# graph. This test case is the same as the above except that there's no +# import from the missingworkspaceimport package to the one that +# imports the unresolved rsc.io/quote dependency. The example.com/missingworkspaceimport +# package imports example.com/withmissing/other so it still depends on the example.com/missing +# module, but not on the withmissing package itself. The example.com/withmissing +# module still has an import on the rsc.io/quote package, but the package +# with the import doesn't appear in the loaded package graph. +cd ../missingworkspaceimport_disconnected +go get ./... + +# Test that deprecations are reported using the workspace. +# First, the control case: without the workspace, the deprecated module +# is an indirect dependency of example.com/withdeprecation/indirect, +# so we shouldn't get a deprecation warning. +cd ../withdeprecation/indirect +cp go.mod go.mod.orig +env GOWORK=off +go get ./... +! stderr 'is deprecated' +cmp go.mod go.mod.want +# Now, in the workspace, we should get a deprecation warning, because +# the deprecated module is a direct dependency of example.com/withdeprecation/direct, which +# is a workspace module. +cp go.mod.orig go.mod +env GOWORK= +go get ./... +stderr 'go: module example.com/deprecated/b is deprecated: in example.com/deprecated/b@v1.9.0' +cmp go.mod go.mod.want + +# Test that retractions are reported using the workspace. +# First, the control case. Even though a workspace module depends on +# a retracted version, because we didn't ask for it on the command line, +# we didn't resolve that retracted module to satisfy an import, +# or need it to build a requested package, we don't produce the warning. +cd ../../withretraction/doesnotrequireretracted +cp go.mod go.mod.orig +go get rsc.io/quote +! stderr 'retracted' +# If we do request a non-retracted version of the module but the workspace +# is off, we also won't see the retraction warning because the retracted +# module isn't selected in the graph. +cp go.mod.orig go.mod +env GOWORK=off +go get example.com/retract@v1.0.0-good +! stderr 'retracted' +# Now, with the workspace on, because example.com/retract@v1.0.0-unused +# is a higher version, it will be selected and the retraction will +# be reported. +cp go.mod.orig go.mod +env GOWORK= +go get example.com/retract@v1.0.0-good +stderr 'retracted' +# Finally, with the workspace on, if the other workspace depends on +# example.com/retract@v1.0.0-bad rather than 'v1.0.0-unused', because +# 'v1.0.0-bad' is considered a lower version than 'v1.0.0-good', 'v1.0.0-good' +# will be selected and the deprecation will not be reported. +cp go.mod.orig go.mod +cd ../requiresretracted +go get example.com/retract@v1.0.0-bad # set the verison to 'v1.0.0-bad' +stderr 'retracted' +cd ../doesnotrequireretracted +go get example.com/retract@v1.0.0-good +! stderr 'retracted' + +-- go.work -- +go 1.25 + +use ( + m + n +) +-- q/go.mod -- +module example.com/q + +go 1.25 +-- q/q.go -- +package q + +import "rsc.io/quote" + +func Q() { + quote.Hello() +} +-- m/go.mod -- +module example.com/m + +go 1.25 +-- m/go.mod.want -- +module example.com/m + +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 +) +-- m/go.mod.want.path_query -- +module example.com/m + +go 1.25 + +require ( + golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect + rsc.io/quote v1.5.2 // indirect + rsc.io/sampler v1.3.0 // indirect +) +-- m/go.sum.want -- +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0= +rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= +rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +-- m/go.mod.want.all -- +module example.com/m + +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 +) +-- m/go.sum.want.all -- +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0 h1:ivTorhoiROmZ1mcs15mO2czVF0uy0tnezXpBVNzgrmA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0= +rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/sampler v1.99.99 h1:iMG9lbEG/8MdeR4lgL+Q8IcwbLNw7ijW7fTiK8Miqts= +rsc.io/sampler v1.99.99/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +-- m/m.go -- +package m + +import ( + "example.com/n" + "rsc.io/quote" +) + +func M() { + n.Hello() + quote.Hello() +} +-- n/go.mod -- +module example.com/n + +go 1.25 +-- n/n.go -- +package n + +func Hello() { +} +-- hastool/go.work -- +go 1.25 + +use ( + . + ../n +) +-- hastool/go.mod -- +module example.com/hastool + +go 1.25 + +tool rsc.io/fortune +-- hastool/go.mod.want -- +module example.com/hastool + +go 1.25 + +tool rsc.io/fortune + +require ( + golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect + rsc.io/fortune v1.0.0 // indirect + rsc.io/quote v1.5.2 // indirect + rsc.io/sampler v1.3.0 // indirect +) +-- hastool/p.go -- +package hastool + +import "example.com/n" + +func T() { + n.Hello() +} +-- missingworkspaceimport/go.work -- +go 1.25 + +use ( + . + withmissing +) +-- missingworkspaceimport/go.mod -- +module example.com/missingworkspaceimport + +go 1.25 +-- missingworkspaceimport/m.go -- +package m + +import _ "example.com/withmissing" +-- missingworkspaceimport/withmissing/go.mod -- +module example.com/withmissing + +go 1.25 +-- missingworkspaceimport/withmissing/w.go -- +package w + +import _ "rsc.io/quote" +-- missingworkspaceimport_disconnected/go.work -- +go 1.25 + +use ( + . + withmissing +) +-- missingworkspaceimport_disconnected/go.mod -- +module example.com/missingworkspaceimport + +go 1.25 +-- missingworkspaceimport_disconnected/m.go -- +package m + +import _ "example.com/withmissing/other" +-- missingworkspaceimport_disconnected/withmissing/go.mod -- +module example.com/withmissing + +go 1.25 +-- missingworkspaceimport_disconnected/withmissing/w.go -- +package w + +import _ "rsc.io/quote" +-- missingworkspaceimport_disconnected/withmissing/other/other.go -- +package other +-- withdeprecation/go.work -- +go 1.25 + +use ( + indirect + direct +) + +replace example.com/requiresdeprecatednotworkspace => ./requiresdeprecatednotworkspace +-- withdeprecation/indirect/go.mod -- +module example.com/withdeprecation/indirect + +go 1.25 + +replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace +-- withdeprecation/indirect/go.mod.want -- +module example.com/withdeprecation/indirect + +go 1.25 + +replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace + +require example.com/requiresdeprecatednotworkspace v0.0.0-00010101000000-000000000000 + +require example.com/deprecated/b v1.9.0 // indirect +-- withdeprecation/indirect/go.mod.want.direct -- +module example.com/withdeprecation/indirect + +go 1.25 + +replace example.com/requiresdeprecatednotworkspace => ../requiresdeprecatednotworkspace + +require example.com/requiresdeprecatednotworkspace v0.0.0-00010101000000-000000000000 + +require example.com/deprecated/b v1.9.0 +-- withdeprecation/indirect/a.go -- +package indirect + +import "example.com/requiresdeprecatednotworkspace" +-- withdeprecation/direct/go.mod -- +module example.com/withdeprecation/direct + +go 1.25 + +require "example.com/deprecated/b" v1.9.0 +-- withdeprecation/direct/import.go -- +package direct + +import "example.com/deprecated/b" +-- withdeprecation/requiresdeprecatednotworkspace/go.mod -- +module example.com/requiresdeprecatednotworkspace + +go 1.25 +-- withdeprecation/requiresdeprecatednotworkspace/a.go -- +package a + +import "example.com/deprecated/b" +-- withretraction/go.work -- +go 1.25 + +use ( + doesnotrequireretracted + requiresretracted +) +-- withretraction/doesnotrequireretracted/go.mod -- +module example.com/withretraction/doesnotrequireretracted + +go 1.25 +-- withretraction/requiresretracted/go.mod -- +module example.com/withretraction/requiresretracted + +go 1.25 + +require example.com/retract v1.0.0-unused -- cgit v1.3