Skip to content

Commit d6c12ec

Browse files
author
Bryan C. Mills
committed
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>
1 parent bae1e70 commit d6c12ec

File tree

3 files changed

+161
-3
lines changed

3 files changed

+161
-3
lines changed

src/cmd/go/internal/modload/import.go

+50-2
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,17 @@ import (
1212
"internal/goroot"
1313
"os"
1414
"path/filepath"
15+
"sort"
1516
"strings"
17+
"time"
1618

1719
"cmd/go/internal/cfg"
20+
"cmd/go/internal/modfetch"
1821
"cmd/go/internal/modfetch/codehost"
1922
"cmd/go/internal/module"
2023
"cmd/go/internal/par"
2124
"cmd/go/internal/search"
25+
"cmd/go/internal/semver"
2226
)
2327

2428
type ImportMissingError struct {
@@ -122,14 +126,58 @@ func Import(path string) (m module.Version, dir string, err error) {
122126
return module.Version{}, "", errors.New(buf.String())
123127
}
124128

125-
// Not on build list.
126-
127129
// Look up module containing the package, for addition to the build list.
128130
// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
129131
if cfg.BuildMod == "readonly" {
130132
return module.Version{}, "", fmt.Errorf("import lookup disabled by -mod=%s", cfg.BuildMod)
131133
}
132134

135+
// Not on build list.
136+
// To avoid spurious remote fetches, next try the latest replacement for each module.
137+
// (golang.org/issue/26241)
138+
if modFile != nil {
139+
latest := map[string]string{} // path -> version
140+
for _, r := range modFile.Replace {
141+
if maybeInModule(path, r.Old.Path) {
142+
latest[r.Old.Path] = semver.Max(r.Old.Version, latest[r.Old.Path])
143+
}
144+
}
145+
146+
mods = make([]module.Version, 0, len(latest))
147+
for p, v := range latest {
148+
// If the replacement didn't specify a version, synthesize a
149+
// pseudo-version with an appropriate major version and a timestamp below
150+
// any real timestamp. That way, if the main module is used from within
151+
// some other module, the user will be able to upgrade the requirement to
152+
// any real version they choose.
153+
if v == "" {
154+
if _, pathMajor, ok := module.SplitPathVersion(p); ok && len(pathMajor) > 0 {
155+
v = modfetch.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
156+
} else {
157+
v = modfetch.PseudoVersion("v0", "", time.Time{}, "000000000000")
158+
}
159+
}
160+
mods = append(mods, module.Version{Path: p, Version: v})
161+
}
162+
163+
// Every module path in mods is a prefix of the import path.
164+
// As in QueryPackage, prefer the longest prefix that satisfies the import.
165+
sort.Slice(mods, func(i, j int) bool {
166+
return len(mods[i].Path) > len(mods[j].Path)
167+
})
168+
for _, m := range mods {
169+
root, isLocal, err := fetch(m)
170+
if err != nil {
171+
// Report fetch error as above.
172+
return module.Version{}, "", err
173+
}
174+
_, ok := dirInModule(path, m.Path, root, isLocal)
175+
if ok {
176+
return m, "", &ImportMissingError{ImportPath: path, Module: m}
177+
}
178+
}
179+
}
180+
133181
m, _, err = QueryPackage(path, "latest", Allowed)
134182
if err != nil {
135183
if _, ok := err.(*codehost.VCSError); ok {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ stderr 'rsc.io/quote/v3@v3.0.0 used for two different module paths \(not-rsc.io/
3030

3131
# Modules that do not (yet) exist upstream can be replaced too.
3232
cp go.mod.orig go.mod
33-
go mod edit -require not-rsc.io/quote/v3@v3.0.0 -replace=not-rsc.io/quote/v3=./local/rsc.io/quote/v3
33+
go mod edit -replace=not-rsc.io/quote/v3@v3.1.0=./local/rsc.io/quote/v3
3434
go build -o a5.exe ./usenewmodule
3535
! stderr 'finding not-rsc.io/quote/v3'
36+
grep 'not-rsc.io/quote/v3 v3.1.0' go.mod
3637
exec ./a5.exe
3738
stdout 'Concurrency is not parallelism.'
3839

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
env GO111MODULE=on
2+
3+
# 'go list -mod=readonly' should not add requirements even if they can be
4+
# resolved locally.
5+
cp go.mod go.mod.orig
6+
! go list -mod=readonly all
7+
cmp go.mod go.mod.orig
8+
9+
# 'go list' should resolve imports using replacements.
10+
go list all
11+
stdout 'example.com/a/b$'
12+
stdout 'example.com/x/v3$'
13+
stdout 'example.com/y/z/w$'
14+
stdout 'example.com/v'
15+
16+
# The selected modules should prefer longer paths,
17+
# but should try shorter paths if needed.
18+
# Modules with a major-version suffix should have a corresponding pseudo-version.
19+
# Replacements that specify a version should use the latest such version.
20+
go list -m all
21+
stdout 'example.com/a/b v0.0.0-00010101000000-000000000000 => ./b'
22+
stdout 'example.com/y v0.0.0-00010101000000-000000000000 => ./y'
23+
stdout 'example.com/x/v3 v3.0.0-00010101000000-000000000000 => ./v3'
24+
stdout 'example.com/v v1.12.0 => ./v12'
25+
26+
-- go.mod --
27+
module example.com/m
28+
29+
replace (
30+
example.com/a => ./a
31+
example.com/a/b => ./b
32+
)
33+
34+
replace (
35+
example.com/x => ./x
36+
example.com/x/v3 => ./v3
37+
)
38+
39+
replace (
40+
example.com/y/z/w => ./w
41+
example.com/y => ./y
42+
)
43+
44+
replace (
45+
example.com/v v1.11.0 => ./v11
46+
example.com/v v1.12.0 => ./v12
47+
example.com/v => ./v
48+
)
49+
50+
-- m.go --
51+
package main
52+
import (
53+
_ "example.com/a/b"
54+
_ "example.com/x/v3"
55+
_ "example.com/y/z/w"
56+
_ "example.com/v"
57+
)
58+
func main() {}
59+
60+
-- a/go.mod --
61+
module a.localhost
62+
-- a/a.go --
63+
package a
64+
-- a/b/b.go--
65+
package b
66+
67+
-- b/go.mod --
68+
module a.localhost/b
69+
-- b/b.go --
70+
package b
71+
72+
-- x/go.mod --
73+
module x.localhost
74+
-- x/x.go --
75+
package x
76+
-- x/v3.go --
77+
package v3
78+
import _ "x.localhost/v3"
79+
80+
-- v3/go.mod --
81+
module x.localhost/v3
82+
-- v3/x.go --
83+
package x
84+
85+
-- w/go.mod --
86+
module w.localhost
87+
-- w/skip/skip.go --
88+
// Package skip is nested below nonexistent package w.
89+
package skip
90+
91+
-- y/go.mod --
92+
module y.localhost
93+
-- y/z/w/w.go --
94+
package w
95+
96+
-- v12/go.mod --
97+
module v.localhost
98+
-- v12/v.go --
99+
package v
100+
101+
-- v11/go.mod --
102+
module v.localhost
103+
-- v11/v.go --
104+
package v
105+
106+
-- v/go.mod --
107+
module v.localhost
108+
-- v/v.go --
109+
package v

0 commit comments

Comments
 (0)