1
0
mirror of https://github.com/golang/go synced 2024-11-17 14:04:48 -07:00

cmd/go: make 'go get' match patterns against packages, not modules

This is a follow-up to CL 174099, fixing an important TODO.
The 'go help modget' documentation will be clarified in anotehr CL,
pending further discussion.

When invoked without -m, 'go get' will no longer match arguments
containing "..." against module paths. If a module's path matches a
pattern but no packages within the module match the pattern, the
module should not be upgraded. For example, if
golang.org/x/tools/playground and golang.org/x/tools are separate
modules, and only golang.org/x/tools is in the build list,
'go get golang.org/x/tools/playground/...' should add
golang.org/x/tools/playground to the build list and leave
golang.org/x/tools alone.

Updates #26902

Change-Id: I2bd18c7950db1aa7bd8527210c1baf2c7d174375
Reviewed-on: https://go-review.googlesource.com/c/go/+/176578
Run-TryBot: Jay Conrod <jayconrod@google.com>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
This commit is contained in:
Jay Conrod 2019-05-09 15:42:55 -04:00
parent ed7a92bab4
commit 71be83e8ca
6 changed files with 90 additions and 41 deletions

View File

@ -336,37 +336,32 @@ func runGet(cmd *base.Command, args []string) {
} }
case strings.Contains(path, "..."): case strings.Contains(path, "..."):
// Find modules in the build list matching the pattern, if any. // If we're using -m, look up modules in the build list that match
match := search.MatchPattern(path) // the pattern. Report an error if no modules match.
matched := false if *getM {
for _, m := range modload.BuildList() { match := search.MatchPattern(path)
// TODO: If we have matching packages already in the build list and we matched := false
// know which module(s) they are in, then we should not upgrade the for _, m := range modload.BuildList() {
// modules that do *not* contain those packages, even if the module path if match(m.Path) || str.HasPathPrefix(path, m.Path) {
// is a prefix of the pattern. queries = append(queries, &query{querySpec: querySpec{path: m.Path, vers: vers, prevM: m, forceModulePath: true}, arg: arg})
// matched = true
// For example, if we have modules golang.org/x/tools and }
// golang.org/x/tools/playground, and all of the packages matching }
// golang.org/x/tools/playground... are in the if !matched {
// golang.org/x/tools/playground module, then we should not *also* try base.Errorf("go get %s: pattern matches no modules in build list", arg)
// to upgrade golang.org/x/tools if the user says 'go get continue
// golang.org/x/tools/playground...@latest'.
if match(m.Path) || str.HasPathPrefix(path, m.Path) {
queries = append(queries, &query{querySpec: querySpec{path: m.Path, vers: vers, prevM: m, forceModulePath: true}, arg: arg})
matched = true
} }
}
// If matched, we're done.
// If we're using -m, report an error.
// Otherwise, look up a module containing packages that match the pattern.
if matched {
break break
} }
if *getM {
base.Errorf("go get %s: pattern matches no modules in build list", arg) // If we're not using -m, wait until we load packages to look up modules.
continue // We don't know yet whether any modules in the build list provide
} // packages matching the pattern. For example, suppose
queries = append(queries, &query{querySpec: querySpec{path: path, vers: vers}, arg: arg}) // golang.org/x/tools and golang.org/x/tools/playground are separate
// modules, and only golang.org/x/tools is in the build list. If the
// user runs 'go get golang.org/x/tools/playground/...', we should
// add a requirement for golang.org/x/tools/playground. We should not
// upgrade golang.org/x/tools.
case path == "all": case path == "all":
// This is the package pattern "all" not the module pattern "all", // This is the package pattern "all" not the module pattern "all",
@ -463,8 +458,16 @@ func runGet(cmd *base.Command, args []string) {
var matches []*search.Match var matches []*search.Match
var install []string var install []string
for { for {
var queries []*query
var seenPkgs map[string]bool var seenPkgs map[string]bool
seenQuery := make(map[querySpec]bool)
var queries []*query
addQuery := func(q *query) {
if !seenQuery[q.querySpec] {
seenQuery[q.querySpec] = true
queries = append(queries, q)
}
}
if len(pkgPatterns) > 0 { if len(pkgPatterns) > 0 {
// Don't load packages if pkgPatterns is empty. Both // Don't load packages if pkgPatterns is empty. Both
// modload.ImportPathsQuiet and ModulePackages convert an empty list // modload.ImportPathsQuiet and ModulePackages convert an empty list
@ -474,16 +477,21 @@ func runGet(cmd *base.Command, args []string) {
} else { } else {
matches = modload.ImportPathsQuiet(pkgPatterns) matches = modload.ImportPathsQuiet(pkgPatterns)
} }
seenQuery := make(map[querySpec]bool)
seenPkgs = make(map[string]bool) seenPkgs = make(map[string]bool)
install = make([]string, 0, len(pkgPatterns)) install = make([]string, 0, len(pkgPatterns))
for i, match := range matches { for i, match := range matches {
if len(match.Pkgs) == 0 { arg := pkgGets[i]
// We'll print a warning at the end of the outer loop to avoid
// repeating warnings on multiple iterations. if !*getM && len(match.Pkgs) == 0 {
// If the pattern did not match any packages, look up a new module.
// If the pattern doesn't match anything on the last iteration,
// we'll print a warning after the outer loop.
if !search.IsRelativePath(arg.path) && !match.Literal && arg.path != "all" {
addQuery(&query{querySpec: querySpec{path: arg.path, vers: arg.vers}, arg: arg.raw})
}
continue continue
} }
arg := pkgGets[i]
install = append(install, arg.path) install = append(install, arg.path)
allStd := true allStd := true
for _, pkg := range match.Pkgs { for _, pkg := range match.Pkgs {
@ -501,11 +509,7 @@ func runGet(cmd *base.Command, args []string) {
continue continue
} }
allStd = false allStd = false
spec := querySpec{path: m.Path, vers: arg.vers} addQuery(&query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
if !seenQuery[spec] {
seenQuery[spec] = true
queries = append(queries, &query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
}
} }
if allStd { if allStd {
if *getM { if *getM {

View File

@ -0,0 +1,12 @@
Written by hand.
Test case for nested modules without an explicit relationship.
This is nested below the top-level module.
-- .mod --
module example.com/nest/sub
-- .info --
{"Version": "v1.0.0"}
-- go.mod --
module example.com/nest/sub
-- y/y.go --
package y

View File

@ -0,0 +1,12 @@
Written by hand.
Test case for nested modules without an explicit relationship.
This is the top-level module.
-- .mod --
module example.com/nest
-- .info --
{"Version": "v1.0.0"}
-- go.mod --
module example.com/nest
-- sub/x/x.go --
package x

View File

@ -0,0 +1,12 @@
Written by hand.
Test case for nested modules without an explicit relationship.
This is the top-level module.
-- .mod --
module example.com/nest
-- .info --
{"Version": "v1.1.0"}
-- go.mod --
module example.com/nest
-- sub/x/x.go --
package x

View File

@ -16,9 +16,10 @@ stderr '^go get rsc.io@v0.1.0: can.t get a specific version of the main module$'
! go get -d rsc.io/x@v0.1.0 ! go get -d rsc.io/x@v0.1.0
stderr '^go get rsc.io/x@v0.1.0: can.t query specific version for package rsc.io/x in the main module \(rsc.io\)$' stderr '^go get rsc.io/x@v0.1.0: can.t query specific version for package rsc.io/x in the main module \(rsc.io\)$'
# TODO: upgrading a package pattern not contained in the main module should not # Upgrading a package pattern not contained in the main module should not
# attempt to upgrade the main module. # attempt to upgrade the main module.
! go get rsc.io/quote/...@v1.5.1 go get rsc.io/quote/...@v1.5.1
grep 'rsc.io/quote v1.5.1' go.mod
-- go.mod.orig -- -- go.mod.orig --
module rsc.io module rsc.io

View File

@ -22,6 +22,14 @@ stderr 'go get rsc.io/quote/x...: module rsc.io/quote@latest \(v1.5.2\) found, b
stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@latest \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x/...' stderr 'go get rsc.io/quote/x/...: module rsc.io/quote@latest \(v1.5.2\) found, but does not contain packages matching rsc.io/quote/x/...'
! grep 'require rsc.io/quote' go.mod ! grep 'require rsc.io/quote' go.mod
# If a pattern matches no packages within a module, the module should not
# be upgraded, even if the module path matches the pattern.
cp go.mod.orig go.mod
go mod edit -require example.com/nest@v1.0.0
go get example.com/nest/sub/y...
grep 'example.com/nest/sub v1.0.0' go.mod
grep 'example.com/nest v1.0.0' go.mod
-- go.mod.orig -- -- go.mod.orig --
module m module m