Commit d6c12ec0 authored by Bryan C. Mills's avatar Bryan C. Mills

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/152739Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
Reviewed-by: 's avatarJay Conrod <jayconrod@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent bae1e70a
......@@ -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 {
......
......@@ -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.'
......
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment