1
0
mirror of https://github.com/golang/go synced 2024-11-23 06:00:08 -07:00

cmd/go/internal/modload: use replacements to resolve missing imports

If the replacements specify one or more versions, we choose the latest
(for consistency with the QueryPackage path, with resolves the latest
version from upstream).

Otherwise, we synthesize a pseudo-version with a zero timestamp and an
appropriate major version.

Fixes #26241

RELNOTE=yes

Change-Id: I14b4c63858c8714cc3e1b05ac52c33de5a16dea9
Reviewed-on: https://go-review.googlesource.com/c/152739
Reviewed-by: Russ Cox <rsc@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Bryan C. Mills 2018-12-05 09:52:58 -05:00
parent bae1e70ac4
commit d6c12ec0ef
3 changed files with 161 additions and 3 deletions

View File

@ -12,13 +12,17 @@ import (
"internal/goroot"
"os"
"path/filepath"
"sort"
"strings"
"time"
"cmd/go/internal/cfg"
"cmd/go/internal/modfetch"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/module"
"cmd/go/internal/par"
"cmd/go/internal/search"
"cmd/go/internal/semver"
)
type ImportMissingError struct {
@ -122,14 +126,58 @@ func Import(path string) (m module.Version, dir string, err error) {
return module.Version{}, "", errors.New(buf.String())
}
// Not on build list.
// Look up module containing the package, for addition to the build list.
// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
if cfg.BuildMod == "readonly" {
return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
}
// Not on build list.
// To avoid spurious remote fetches, next try the latest replacement for each module.
// (golang.org/issue/26241)
if modFile != nil {
latest := map[string]string{} // path -> version
for _, r := range modFile.Replace {
if maybeInModule(path, r.Old.Path) {
latest[r.Old.Path] = semver.Max(r.Old.Version, latest[r.Old.Path])
}
}
mods = make([]module.Version, 0, len(latest))
for p, v := range latest {
// If the replacement didn't specify a version, synthesize a
// pseudo-version with an appropriate major version and a timestamp below
// any real timestamp. That way, if the main module is used from within
// some other module, the user will be able to upgrade the requirement to
// any real version they choose.
if v == "" {
if _, pathMajor, ok := module.SplitPathVersion(p); ok && len(pathMajor) > 0 {
v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
} else {
v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
}
}
mods = append(mods, module.Version{Path: p, Version: v})
}
// Every module path in mods is a prefix of the import path.
// As in QueryPackage, prefer the longest prefix that satisfies the import.
sort.Slice(mods, func(i, j int) bool {
return len(mods[i].Path) > len(mods[j].Path)
})
for _, m := range mods {
root, isLocal, err := fetch(m)
if err != nil {
// Report fetch error as above.
return module.Version{}, "", err
}
_, ok := dirInModule(path, m.Path, root, isLocal)
if ok {
return m, "", &ImportMissingError{ImportPath: path, Module: m}
}
}
}
m, _, err = QueryPackage(path, "latest", Allowed)
if err != nil {
if _, ok := err.(*codehost.VCSError); ok {

View File

@ -30,9 +30,10 @@ stderr 'rsc.io/quote/v3@v3.0.0 used for two different module paths \(not-rsc.io/
# Modules that do not (yet) exist upstream can be replaced too.
cp go.mod.orig go.mod
go mod edit -require not-rsc.io/quote/v3@v3.0.0 -replace=not-rsc.io/quote/v3=./local/rsc.io/quote/v3
go mod edit -replace=not-rsc.io/quote/v3@v3.1.0=./local/rsc.io/quote/v3
go build -o a5.exe ./usenewmodule
! stderr 'finding not-rsc.io/quote/v3'
grep 'not-rsc.io/quote/v3 v3.1.0' go.mod
exec ./a5.exe
stdout 'Concurrency is not parallelism.'

View File

@ -0,0 +1,109 @@
env GO111MODULE=on
# 'go list -mod=readonly' should not add requirements even if they can be
# resolved locally.
cp go.mod go.mod.orig
! go list -mod=readonly all
cmp go.mod go.mod.orig
# 'go list' should resolve imports using replacements.
go list all
stdout 'example.com/a/b$'
stdout 'example.com/x/v3$'
stdout 'example.com/y/z/w$'
stdout 'example.com/v'
# The selected modules should prefer longer paths,
# but should try shorter paths if needed.
# Modules with a major-version suffix should have a corresponding pseudo-version.
# Replacements that specify a version should use the latest such version.
go list -m all
stdout 'example.com/a/b v0.0.0-00010101000000-000000000000 => ./b'
stdout 'example.com/y v0.0.0-00010101000000-000000000000 => ./y'
stdout 'example.com/x/v3 v3.0.0-00010101000000-000000000000 => ./v3'
stdout 'example.com/v v1.12.0 => ./v12'
-- go.mod --
module example.com/m
replace (
example.com/a => ./a
example.com/a/b => ./b
)
replace (
example.com/x => ./x
example.com/x/v3 => ./v3
)
replace (
example.com/y/z/w => ./w
example.com/y => ./y
)
replace (
example.com/v v1.11.0 => ./v11
example.com/v v1.12.0 => ./v12
example.com/v => ./v
)
-- m.go --
package main
import (
_ "example.com/a/b"
_ "example.com/x/v3"
_ "example.com/y/z/w"
_ "example.com/v"
)
func main() {}
-- a/go.mod --
module a.localhost
-- a/a.go --
package a
-- a/b/b.go--
package b
-- b/go.mod --
module a.localhost/b
-- b/b.go --
package b
-- x/go.mod --
module x.localhost
-- x/x.go --
package x
-- x/v3.go --
package v3
import _ "x.localhost/v3"
-- v3/go.mod --
module x.localhost/v3
-- v3/x.go --
package x
-- w/go.mod --
module w.localhost
-- w/skip/skip.go --
// Package skip is nested below nonexistent package w.
package skip
-- y/go.mod --
module y.localhost
-- y/z/w/w.go --
package w
-- v12/go.mod --
module v.localhost
-- v12/v.go --
package v
-- v11/go.mod --
module v.localhost
-- v11/v.go --
package v
-- v/go.mod --
module v.localhost
-- v/v.go --
package v