Skip to content

Commit 6997671

Browse files
author
Jay Conrod
committed
cmd/go: handle wildcards for unknown modules in "go get"
For example, "go get golang.org/x/tools/cmd/..." will add a requirement for "golang.org/x/tools" to go.mod and will install executables from the "cmd" subdirectory. Fixes #29363 Change-Id: Id53f051710708d7760ffe831d4274fd54533d2b7 Reviewed-on: https://go-review.googlesource.com/c/go/+/171138 Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 75308c9 commit 6997671

File tree

3 files changed

+119
-7
lines changed

3 files changed

+119
-7
lines changed

src/cmd/go/internal/modget/get.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -351,23 +351,29 @@ func runGet(cmd *base.Command, args []string) {
351351
match := search.MatchPattern(path)
352352
matched := false
353353
for _, m := range modload.BuildList() {
354+
// TODO(bcmills): Patterns that don't contain the module path but do
355+
// contain partial package paths will not match here. For example,
356+
// ...html/... would not match html/template or golang.org/x/net/html.
357+
// Related golang.org/issue/26902.
354358
if match(m.Path) || str.HasPathPrefix(path, m.Path) {
355359
tasks = append(tasks, &task{arg: arg, path: m.Path, vers: vers, prevM: m, forceModulePath: true})
356360
matched = true
357361
}
358362
}
359363
// If matched, we're done.
360-
// Otherwise assume pattern is inside a single module
361-
// (golang.org/x/text/unicode/...) and leave for usual lookup.
362-
// Unless we're using -m.
364+
// If we're using -m, report an error.
365+
// Otherwise, look up a module containing packages that match the pattern.
363366
if matched {
364367
continue
365368
}
366369
if *getM {
367370
base.Errorf("go get %s: pattern matches no modules in build list", arg)
368371
continue
369372
}
373+
tasks = append(tasks, &task{arg: arg, path: path, vers: vers})
374+
continue
370375
}
376+
371377
t := &task{arg: arg, path: path, vers: vers}
372378
if vers == "patch" {
373379
if *getM {
@@ -605,7 +611,7 @@ func runGet(cmd *base.Command, args []string) {
605611
if p.Error != nil {
606612
if len(args) == 0 && getU != "" && strings.HasPrefix(p.Error.Err, "no Go files") {
607613
// Upgrading modules: skip the implicitly-requested package at the
608-
// current directory, even if it is not tho module root.
614+
// current directory, even if it is not the module root.
609615
continue
610616
}
611617
if strings.Contains(p.Error.Err, "cannot find module providing") && modload.ModuleInfo(p.ImportPath) != nil {
@@ -644,14 +650,22 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo
644650
}
645651
}
646652

647-
// First choice is always to assume path is a module path.
648-
// If that works out, we're done.
653+
// If the path has a wildcard, search for a module that matches the pattern.
654+
if strings.Contains(path, "...") {
655+
if forceModulePath {
656+
panic("forceModulePath is true for path with wildcard " + path)
657+
}
658+
_, m, _, err := modload.QueryPattern(path, vers, modload.Allowed)
659+
return m, err
660+
}
661+
662+
// Try interpreting the path as a module path.
649663
info, err := modload.Query(path, vers, modload.Allowed)
650664
if err == nil {
651665
return module.Version{Path: path, Version: info.Version}, nil
652666
}
653667

654-
// Even if the query fails, if the path must be a real module, then report the query error.
668+
// If the query fails, and the path must be a real module, report the query error.
655669
if forceModulePath || *getM {
656670
return module.Version{}, err
657671
}

src/cmd/go/internal/modload/query.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"cmd/go/internal/module"
1111
"cmd/go/internal/semver"
1212
"cmd/go/internal/str"
13+
"errors"
1314
"fmt"
1415
pathpkg "path"
1516
"strings"
@@ -254,3 +255,67 @@ func QueryPackage(path, query string, allowed func(module.Version) bool) (module
254255

255256
return module.Version{}, nil, finalErr
256257
}
258+
259+
// QueryPattern looks up a module with at least one package matching the
260+
// given pattern at the given version. It returns a list of matched packages
261+
// and information about the module.
262+
//
263+
// QueryPattern queries modules with package paths up to the first "..."
264+
// in the pattern. For the pattern "example.com/a/b.../c", QueryPattern would
265+
// consider prefixes of "example.com/a". If multiple modules have versions
266+
// that match the query and packages that match the pattern, QueryPattern
267+
// picks the one with the longest module path.
268+
func QueryPattern(pattern string, query string, allowed func(module.Version) bool) ([]string, module.Version, *modfetch.RevInfo, error) {
269+
i := strings.Index(pattern, "...")
270+
if i < 0 {
271+
m, info, err := QueryPackage(pattern, query, allowed)
272+
if err != nil {
273+
return nil, module.Version{}, nil, err
274+
} else {
275+
return []string{pattern}, m, info, nil
276+
}
277+
}
278+
base := pathpkg.Dir(pattern[:i+3])
279+
280+
// Return the most specific error for the longest module path.
281+
const (
282+
errNoModule = 0
283+
errNoVersion = 1
284+
errNoMatch = 2
285+
)
286+
errLevel := errNoModule
287+
finalErr := errors.New("cannot find module matching pattern")
288+
289+
for p := base; p != "." && p != "/"; p = pathpkg.Dir(p) {
290+
info, err := Query(p, query, allowed)
291+
if err != nil {
292+
if _, ok := err.(*codehost.VCSError); ok {
293+
// A VCSError means we know where to find the code,
294+
// we just can't. Abort search.
295+
return nil, module.Version{}, nil, err
296+
}
297+
if errLevel < errNoVersion {
298+
errLevel = errNoVersion
299+
finalErr = err
300+
}
301+
continue
302+
}
303+
m := module.Version{Path: p, Version: info.Version}
304+
// matchPackages also calls fetch but treats errors as fatal, so we
305+
// fetch here first.
306+
_, _, err = fetch(m)
307+
if err != nil {
308+
return nil, module.Version{}, nil, err
309+
}
310+
pkgs := matchPackages(pattern, anyTags, false, []module.Version{m})
311+
if len(pkgs) > 0 {
312+
return pkgs, m, info, nil
313+
}
314+
if errLevel < errNoMatch {
315+
errLevel = errNoMatch
316+
finalErr = fmt.Errorf("no matching packages in module %s@%s", m.Path, m.Version)
317+
}
318+
}
319+
320+
return nil, module.Version{}, nil, finalErr
321+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
env GO111MODULE=on
2+
3+
# If a pattern doesn't match any modules in the build list,
4+
# and -m is used, an error should be reported.
5+
cp go.mod.orig go.mod
6+
! go get -m rsc.io/quote/...
7+
stderr 'pattern matches no modules in build list'
8+
9+
# If a pattern doesn't match any modules in the build list,
10+
# we assume the pattern matches a single module where the
11+
# part of the pattern before "..." is the module path.
12+
cp go.mod.orig go.mod
13+
go get -d rsc.io/quote/...
14+
grep 'require rsc.io/quote' go.mod
15+
16+
cp go.mod.orig go.mod
17+
! go get -d rsc.io/quote/x...
18+
stderr 'go get rsc.io/quote/x...: no matching packages in module rsc.io/[email protected]'
19+
! grep 'require rsc.io/quote' go.mod
20+
21+
! go get -d rsc.io/quote/x/...
22+
stderr 'go get rsc.io/quote/x/...: no matching packages in module rsc.io/[email protected]'
23+
! grep 'require rsc.io/quote' go.mod
24+
25+
-- go.mod.orig --
26+
module m
27+
28+
go 1.13
29+
30+
-- use/use.go --
31+
package use
32+
33+
import _ "rsc.io/quote"

0 commit comments

Comments
 (0)