Skip to content

Commit 2b8b34a

Browse files
author
Bryan C. Mills
committed
cmd/go: query each path only once in 'go get'
If we don't know whether a path is a module path or a package path, previously we would first try a module query for it, then fall back to a package query. If we are using a sequence of proxies with fallback (as will be the default in Go 1.13), and the path is not actually a module path, that initial module query will fail against the first proxy, then immediately fall back to the next proxy in the sequence — even if the query could have been satisfied by some other (prefix) module available from the first proxy. Instead, we now query the requested path as only one kind of path. If we query it as a package path but it turns out to only exist as a module, we can detect that as a PackageNotInModuleError with an appropriate module path — we do not need to issue a second query to classify it. Fixes #31785 Change-Id: I581d44279196e41d1fed27ec25489e75d62654e3 Reviewed-on: https://go-review.googlesource.com/c/go/+/189517 Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 1dc0110 commit 2b8b34a

File tree

6 files changed

+66
-42
lines changed

6 files changed

+66
-42
lines changed

src/cmd/go/internal/modfetch/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func lookup(proxy, path string) (r Repo, err error) {
240240

241241
var (
242242
errModVendor = errors.New("module lookup disabled by -mod=vendor")
243-
errProxyOff = errors.New("module lookup disabled by GOPROXY=off")
243+
errProxyOff = notExistError("module lookup disabled by GOPROXY=off")
244244
errNoproxy error = notExistError("disabled by GOPRIVATE/GONOPROXY")
245245
errUseProxy error = notExistError("path does not match GOPRIVATE/GONOPROXY")
246246
)

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

+27-16
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ func runQueries(cache map[querySpec]*query, queries []*query, modOnly map[string
735735
return byPath
736736
}
737737

738-
// getQuery evaluates the given package path, version pair
738+
// getQuery evaluates the given (package or module) path and version
739739
// to determine the underlying module version being requested.
740740
// If forceModulePath is set, getQuery must interpret path
741741
// as a module path.
@@ -753,40 +753,51 @@ func getQuery(path, vers string, prevM module.Version, forceModulePath bool) (mo
753753
base.Fatalf("go get: internal error: prevM may be set if and only if forceModulePath is set")
754754
}
755755

756-
if forceModulePath || !strings.Contains(path, "...") {
756+
// If the query must be a module path, try only that module path.
757+
if forceModulePath {
757758
if path == modload.Target.Path {
758759
if vers != "latest" {
759760
return module.Version{}, fmt.Errorf("can't get a specific version of the main module")
760761
}
761762
}
762763

763-
// If the path doesn't contain a wildcard, try interpreting it as a module path.
764764
info, err := modload.Query(path, vers, prevM.Version, modload.Allowed)
765765
if err == nil {
766766
return module.Version{Path: path, Version: info.Version}, nil
767767
}
768768

769-
// If the query fails, and the path must be a real module, report the query error.
770-
if forceModulePath {
771-
// If the query was "upgrade" or "patch" and the current version has been
772-
// replaced, check to see whether the error was for that same version:
773-
// if so, the version was probably replaced because it is invalid,
774-
// and we should keep that replacement without complaining.
775-
if vers == "upgrade" || vers == "patch" {
776-
var vErr *module.InvalidVersionError
777-
if errors.As(err, &vErr) && vErr.Version == prevM.Version && modload.Replacement(prevM).Path != "" {
778-
return prevM, nil
779-
}
769+
// If the query was "upgrade" or "patch" and the current version has been
770+
// replaced, check to see whether the error was for that same version:
771+
// if so, the version was probably replaced because it is invalid,
772+
// and we should keep that replacement without complaining.
773+
if vers == "upgrade" || vers == "patch" {
774+
var vErr *module.InvalidVersionError
775+
if errors.As(err, &vErr) && vErr.Version == prevM.Version && modload.Replacement(prevM).Path != "" {
776+
return prevM, nil
780777
}
781-
return module.Version{}, err
782778
}
779+
780+
return module.Version{}, err
783781
}
784782

785-
// Otherwise, try a package path or pattern.
783+
// If the query may be either a package or a module, try it as a package path.
784+
// If it turns out to only exist as a module, we can detect the resulting
785+
// PackageNotInModuleError and avoid a second round-trip through (potentially)
786+
// all of the configured proxies.
786787
results, err := modload.QueryPattern(path, vers, modload.Allowed)
787788
if err != nil {
789+
// If the path doesn't contain a wildcard, check whether it was actually a
790+
// module path instead. If so, return that.
791+
if !strings.Contains(path, "...") {
792+
var modErr *modload.PackageNotInModuleError
793+
if errors.As(err, &modErr) && modErr.Mod.Path == path {
794+
return modErr.Mod, nil
795+
}
796+
}
797+
788798
return module.Version{}, err
789799
}
800+
790801
return results[0].Mod, nil
791802
}
792803

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

+24-23
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,10 @@ func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]Q
380380
}
381381
r.Packages = match(r.Mod, root, isLocal)
382382
if len(r.Packages) == 0 {
383-
return r, &packageNotInModuleError{
384-
mod: r.Mod,
385-
query: query,
386-
pattern: pattern,
383+
return r, &PackageNotInModuleError{
384+
Mod: r.Mod,
385+
Query: query,
386+
Pattern: pattern,
387387
}
388388
}
389389
return r, nil
@@ -446,30 +446,31 @@ func queryPrefixModules(candidateModules []string, queryModule func(path string)
446446
wg.Wait()
447447

448448
// Classify the results. In case of failure, identify the error that the user
449-
// is most likely to find helpful.
449+
// is most likely to find helpful: the most useful class of error at the
450+
// longest matching path.
450451
var (
452+
noPackage *PackageNotInModuleError
451453
noVersion *NoMatchingVersionError
452-
noPackage *packageNotInModuleError
453454
notExistErr error
454455
)
455456
for _, r := range results {
456457
switch rErr := r.err.(type) {
457458
case nil:
458459
found = append(found, r.QueryResult)
460+
case *PackageNotInModuleError:
461+
if noPackage == nil {
462+
noPackage = rErr
463+
}
459464
case *NoMatchingVersionError:
460465
if noVersion == nil {
461466
noVersion = rErr
462467
}
463-
case *packageNotInModuleError:
464-
if noPackage == nil {
465-
noPackage = rErr
466-
}
467468
default:
468469
if errors.Is(rErr, os.ErrNotExist) {
469470
if notExistErr == nil {
470471
notExistErr = rErr
471472
}
472-
} else {
473+
} else if err == nil {
473474
err = r.err
474475
}
475476
}
@@ -515,31 +516,31 @@ func (e *NoMatchingVersionError) Error() string {
515516
return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
516517
}
517518

518-
// A packageNotInModuleError indicates that QueryPattern found a candidate
519+
// A PackageNotInModuleError indicates that QueryPattern found a candidate
519520
// module at the requested version, but that module did not contain any packages
520521
// matching the requested pattern.
521522
//
522-
// NOTE: packageNotInModuleError MUST NOT implement Is(os.ErrNotExist).
523+
// NOTE: PackageNotInModuleError MUST NOT implement Is(os.ErrNotExist).
523524
//
524525
// If the module came from a proxy, that proxy had to return a successful status
525526
// code for the versions it knows about, and thus did not have the opportunity
526527
// to return a non-400 status code to suppress fallback.
527-
type packageNotInModuleError struct {
528-
mod module.Version
529-
query string
530-
pattern string
528+
type PackageNotInModuleError struct {
529+
Mod module.Version
530+
Query string
531+
Pattern string
531532
}
532533

533-
func (e *packageNotInModuleError) Error() string {
534+
func (e *PackageNotInModuleError) Error() string {
534535
found := ""
535-
if e.query != e.mod.Version {
536-
found = fmt.Sprintf(" (%s)", e.mod.Version)
536+
if e.Query != e.Mod.Version {
537+
found = fmt.Sprintf(" (%s)", e.Mod.Version)
537538
}
538539

539-
if strings.Contains(e.pattern, "...") {
540-
return fmt.Sprintf("module %s@%s%s found, but does not contain packages matching %s", e.mod.Path, e.query, found, e.pattern)
540+
if strings.Contains(e.Pattern, "...") {
541+
return fmt.Sprintf("module %s@%s%s found, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern)
541542
}
542-
return fmt.Sprintf("module %s@%s%s found, but does not contain package %s", e.mod.Path, e.query, found, e.pattern)
543+
return fmt.Sprintf("module %s@%s%s found, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern)
543544
}
544545

545546
// ModuleHasRootPackage returns whether module m contains a package m.Path.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
env GO111MODULE=on
2+
3+
[!net] skip
4+
5+
env GOPROXY=https://proxy.golang.org,direct
6+
env GOSUMDB=off
7+
8+
go get -x -v -d golang.org/x/tools/cmd/goimports
9+
stderr '# get https://proxy.golang.org/golang.org/x/tools/@latest'
10+
! stderr '# get https://golang.org'

src/cmd/go/testdata/script/mod_get_newcycle.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
env GO111MODULE=on
22

33
# Download modules to avoid stderr chatter
4+
go mod download [email protected]
45
go mod download example.com/newcycle/[email protected]
56
go mod download example.com/newcycle/[email protected]
67
go mod download example.com/newcycle/[email protected]
@@ -10,5 +11,6 @@ go mod init m
1011
cmp stderr stderr-expected
1112

1213
-- stderr-expected --
14+
go: finding example.com/newcycle v1.0.0
1315
go get: inconsistent versions:
1416
example.com/newcycle/[email protected] requires example.com/newcycle/[email protected] (not example.com/newcycle/[email protected])

src/cmd/go/testdata/script/mod_sumdb.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ env dbname=localhost.localdev/sumdb
99
cp go.mod.orig go.mod
1010
env GOSUMDB=$sumdb' '$proxy/sumdb-wrong
1111
! go get -d rsc.io/quote
12-
stderr 'verifying rsc.io/[email protected]/go.mod: checksum mismatch'
13-
stderr 'downloaded: h1:LzX7'
12+
stderr 'verifying rsc.io/[email protected]: checksum mismatch'
13+
stderr 'downloaded: h1:3fEy'
1414
stderr 'localhost.localdev/sumdb: h1:wrong'
1515
stderr 'SECURITY ERROR\nThis download does NOT match the one reported by the checksum server.'
1616
! go get -d rsc.io/sampler

0 commit comments

Comments
 (0)