Skip to content

Commit 015a5a5

Browse files
author
Bryan C. Mills
committed
cmd/go/internal/modload: rework import resolution
modload.Import previously performed two otherwise-separable tasks: 1. Identify which module in the build list contains the requested package. 2. If no such module exists, search available modules to try to find the missing package. This change splits those two tasks into two separate unexported functions, and reports import-resolution errors by attaching them to the package rather than emitting them directly to stderr. That allows 'list' to report the errors, but 'list -e' to ignore them. With the two tasks now separate, it will be easier to avoid the overhead of resolving missing packages during lazy loading if we discover that some existing dependency needs to be promoted to the top level (potentially altering the main module's selected versions, and thus suppling packages that were previously missing). For #36460 Updates #26909 Change-Id: I32bd853b266d7cd231d1f45f92b0650d95c4bcbd Reviewed-on: https://go-review.googlesource.com/c/go/+/251445 Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 26d27f9 commit 015a5a5

File tree

8 files changed

+158
-80
lines changed

8 files changed

+158
-80
lines changed

src/cmd/go/internal/list/list.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
545545
// Note that -deps is applied after -test,
546546
// so that you only get descriptions of tests for the things named
547547
// explicitly on the command line, not for all dependencies.
548-
pkgs = load.PackageList(pkgs)
548+
pkgs = loadPackageList(pkgs)
549549
}
550550

551551
// Do we need to run a build to gather information?
@@ -580,7 +580,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
580580
if *listTest {
581581
all := pkgs
582582
if !*listDeps {
583-
all = load.PackageList(pkgs)
583+
all = loadPackageList(pkgs)
584584
}
585585
// Update import paths to distinguish the real package p
586586
// from p recompiled for q.test.
@@ -697,6 +697,23 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
697697
}
698698
}
699699

700+
// loadPackageList is like load.PackageList, but prints error messages and exits
701+
// with nonzero status if listE is not set and any package in the expanded list
702+
// has errors.
703+
func loadPackageList(roots []*load.Package) []*load.Package {
704+
pkgs := load.PackageList(roots)
705+
706+
if !*listE {
707+
for _, pkg := range pkgs {
708+
if pkg.Error != nil {
709+
base.Errorf("%v", pkg.Error)
710+
}
711+
}
712+
}
713+
714+
return pkgs
715+
}
716+
700717
// TrackingWriter tracks the last byte written on every write so
701718
// we can avoid printing a newline if one was already written or
702719
// if there is no output at all.

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

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"golang.org/x/mod/semver"
2727
)
2828

29+
var errImportMissing = errors.New("import missing")
30+
2931
type ImportMissingError struct {
3032
Path string
3133
Module module.Version
@@ -48,6 +50,11 @@ func (e *ImportMissingError) Error() string {
4850
}
4951
return "cannot find module providing package " + e.Path
5052
}
53+
54+
if e.newMissingVersion != "" {
55+
return fmt.Sprintf("package %s provided by %s at latest version %s but not at required version %s", e.Path, e.Module.Path, e.Module.Version, e.newMissingVersion)
56+
}
57+
5158
return fmt.Sprintf("missing module for import: %s@%s provides %s", e.Module.Path, e.Module.Version, e.Path)
5259
}
5360

@@ -100,18 +107,20 @@ func (e *AmbiguousImportError) Error() string {
100107

101108
var _ load.ImportPathError = &AmbiguousImportError{}
102109

103-
// Import finds the module and directory in the build list
104-
// containing the package with the given import path.
105-
// The answer must be unique: Import returns an error
106-
// if multiple modules attempt to provide the same package.
107-
// Import can return a module with an empty m.Path, for packages in the standard library.
108-
// Import can return an empty directory string, for fake packages like "C" and "unsafe".
110+
// importFromBuildList finds the module and directory in the build list
111+
// containing the package with the given import path. The answer must be unique:
112+
// importFromBuildList returns an error if multiple modules attempt to provide
113+
// the same package.
114+
//
115+
// importFromBuildList can return a module with an empty m.Path, for packages in
116+
// the standard library.
117+
//
118+
// importFromBuildList can return an empty directory string, for fake packages
119+
// like "C" and "unsafe".
109120
//
110121
// If the package cannot be found in the current build list,
111-
// Import returns an ImportMissingError as the error.
112-
// If Import can identify a module that could be added to supply the package,
113-
// the ImportMissingError records that module.
114-
func Import(ctx context.Context, path string) (m module.Version, dir string, err error) {
122+
// importFromBuildList returns errImportMissing as the error.
123+
func importFromBuildList(ctx context.Context, path string) (m module.Version, dir string, err error) {
115124
if strings.Contains(path, "@") {
116125
return module.Version{}, "", fmt.Errorf("import path should not have @version")
117126
}
@@ -190,8 +199,14 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
190199
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
191200
}
192201

193-
// Look up module containing the package, for addition to the build list.
194-
// Goal is to determine the module, download it to dir, and return m, dir, ErrMissing.
202+
return module.Version{}, "", errImportMissing
203+
}
204+
205+
// queryImport attempts to locate a module that can be added to the current
206+
// build list to provide the package with the given import path.
207+
func queryImport(ctx context.Context, path string) (module.Version, error) {
208+
pathIsStd := search.IsStandardImportPath(path)
209+
195210
if cfg.BuildMod == "readonly" {
196211
var queryErr error
197212
if !pathIsStd {
@@ -201,10 +216,10 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
201216
queryErr = fmt.Errorf("import lookup disabled by -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
202217
}
203218
}
204-
return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: queryErr}
219+
return module.Version{}, &ImportMissingError{Path: path, QueryErr: queryErr}
205220
}
206221
if modRoot == "" && !allowMissingModuleImports {
207-
return module.Version{}, "", &ImportMissingError{
222+
return module.Version{}, &ImportMissingError{
208223
Path: path,
209224
QueryErr: errors.New("working directory is not part of a module"),
210225
}
@@ -226,7 +241,7 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
226241
}
227242
}
228243

229-
mods = make([]module.Version, 0, len(latest))
244+
mods := make([]module.Version, 0, len(latest))
230245
for p, v := range latest {
231246
// If the replacement didn't specify a version, synthesize a
232247
// pseudo-version with an appropriate major version and a timestamp below
@@ -252,19 +267,19 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
252267
root, isLocal, err := fetch(ctx, m)
253268
if err != nil {
254269
// Report fetch error as above.
255-
return module.Version{}, "", err
270+
return module.Version{}, err
256271
}
257272
if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
258-
return m, "", err
273+
return m, err
259274
} else if ok {
260-
return m, "", &ImportMissingError{Path: path, Module: m}
275+
return m, nil
261276
}
262277
}
263278
if len(mods) > 0 && module.CheckPath(path) != nil {
264279
// The package path is not valid to fetch remotely,
265280
// so it can only exist if in a replaced module,
266281
// and we know from the above loop that it is not.
267-
return module.Version{}, "", &PackageNotInModuleError{
282+
return module.Version{}, &PackageNotInModuleError{
268283
Mod: mods[0],
269284
Query: "latest",
270285
Pattern: path,
@@ -281,7 +296,7 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
281296
// QueryPackage cannot possibly find a module containing this package.
282297
//
283298
// Instead of trying QueryPackage, report an ImportMissingError immediately.
284-
return module.Version{}, "", &ImportMissingError{Path: path}
299+
return module.Version{}, &ImportMissingError{Path: path}
285300
}
286301

287302
fmt.Fprintf(os.Stderr, "go: finding module for package %s\n", path)
@@ -291,12 +306,13 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
291306
if errors.Is(err, os.ErrNotExist) {
292307
// Return "cannot find module providing package […]" instead of whatever
293308
// low-level error QueryPackage produced.
294-
return module.Version{}, "", &ImportMissingError{Path: path, QueryErr: err}
309+
return module.Version{}, &ImportMissingError{Path: path, QueryErr: err}
295310
} else {
296-
return module.Version{}, "", err
311+
return module.Version{}, err
297312
}
298313
}
299-
m = candidates[0].Mod
314+
315+
m := candidates[0].Mod
300316
newMissingVersion := ""
301317
for _, c := range candidates {
302318
cm := c.Mod
@@ -310,13 +326,20 @@ func Import(ctx context.Context, path string) (m module.Version, dir string, err
310326
// version (e.g., v1.0.0) of a module, but we have a newer version
311327
// of the same module in the build list (e.g., v1.0.1-beta), and
312328
// the package is not present there.
329+
//
330+
// TODO(#41113): This is probably incorrect when there are multiple
331+
// candidates, such as when a nested module is split out but only one
332+
// half of the split is tagged.
313333
m = cm
314334
newMissingVersion = bm.Version
315335
break
316336
}
317337
}
318338
}
319-
return m, "", &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
339+
if newMissingVersion != "" {
340+
return m, &ImportMissingError{Path: path, Module: m, newMissingVersion: newMissingVersion}
341+
}
342+
return m, nil
320343
}
321344

322345
// maybeInModule reports whether, syntactically,

src/cmd/go/internal/modload/import_test.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,52 @@ import (
1010
"regexp"
1111
"strings"
1212
"testing"
13+
14+
"golang.org/x/mod/module"
1315
)
1416

1517
var importTests = []struct {
1618
path string
19+
m module.Version
1720
err string
1821
}{
1922
{
2023
path: "golang.org/x/net/context",
21-
err: "missing module for import: golang.org/x/net@.* provides golang.org/x/net/context",
24+
m: module.Version{
25+
Path: "golang.org/x/net",
26+
},
2227
},
2328
{
2429
path: "golang.org/x/net",
2530
err: `module golang.org/x/net@.* found \(v0.0.0-.*\), but does not contain package golang.org/x/net`,
2631
},
2732
{
2833
path: "golang.org/x/text",
29-
err: "missing module for import: golang.org/x/text@.* provides golang.org/x/text",
34+
m: module.Version{
35+
Path: "golang.org/x/text",
36+
},
3037
},
3138
{
3239
path: "github.com/rsc/quote/buggy",
33-
err: "missing module for import: github.com/rsc/[email protected] provides github.com/rsc/quote/buggy",
40+
m: module.Version{
41+
Path: "github.com/rsc/quote",
42+
Version: "v1.5.2",
43+
},
3444
},
3545
{
3646
path: "github.com/rsc/quote",
37-
err: "missing module for import: github.com/rsc/[email protected] provides github.com/rsc/quote",
47+
m: module.Version{
48+
Path: "github.com/rsc/quote",
49+
Version: "v1.5.2",
50+
},
3851
},
3952
{
4053
path: "golang.org/x/foo/bar",
4154
err: "cannot find module providing package golang.org/x/foo/bar",
4255
},
4356
}
4457

45-
func TestImport(t *testing.T) {
58+
func TestQueryImport(t *testing.T) {
4659
testenv.MustHaveExternalNetwork(t)
4760
testenv.MustHaveExecPath(t, "git")
4861
defer func(old bool) {
@@ -55,12 +68,23 @@ func TestImport(t *testing.T) {
5568
for _, tt := range importTests {
5669
t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {
5770
// Note that there is no build list, so Import should always fail.
58-
m, dir, err := Import(ctx, tt.path)
59-
if err == nil {
60-
t.Fatalf("Import(%q) = %v, %v, nil; expected error", tt.path, m, dir)
71+
m, err := queryImport(ctx, tt.path)
72+
73+
if tt.err == "" {
74+
if err != nil {
75+
t.Fatalf("queryImport(_, %q): %v", tt.path, err)
76+
}
77+
} else {
78+
if err == nil {
79+
t.Fatalf("queryImport(_, %q) = %v, nil; expected error", tt.path, m)
80+
}
81+
if !regexp.MustCompile(tt.err).MatchString(err.Error()) {
82+
t.Fatalf("queryImport(_, %q): error %q, want error matching %#q", tt.path, err, tt.err)
83+
}
6184
}
62-
if !regexp.MustCompile(tt.err).MatchString(err.Error()) {
63-
t.Fatalf("Import(%q): error %q, want error matching %#q", tt.path, err, tt.err)
85+
86+
if m.Path != tt.m.Path || (tt.m.Version != "" && m.Version != tt.m.Version) {
87+
t.Errorf("queryImport(_, %q) = %v, _; want %v", tt.path, m, tt.m)
6488
}
6589
})
6690
}

src/cmd/go/internal/modload/load.go

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,7 @@ func loadFromRoots(params loaderParams) *loader {
881881

882882
ld.buildStacks()
883883

884-
modAddedBy := resolveMissingImports(addedModuleFor, ld.pkgs)
884+
modAddedBy := ld.resolveMissingImports(addedModuleFor)
885885
if len(modAddedBy) == 0 {
886886
break
887887
}
@@ -937,38 +937,45 @@ func loadFromRoots(params loaderParams) *loader {
937937
// The newly-resolved packages are added to the addedModuleFor map, and
938938
// resolveMissingImports returns a map from each newly-added module version to
939939
// the first package for which that module was added.
940-
func resolveMissingImports(addedModuleFor map[string]bool, pkgs []*loadPkg) (modAddedBy map[module.Version]*loadPkg) {
941-
haveMod := make(map[module.Version]bool)
942-
for _, m := range buildList {
943-
haveMod[m] = true
944-
}
945-
946-
modAddedBy = make(map[module.Version]*loadPkg)
947-
for _, pkg := range pkgs {
940+
func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAddedBy map[module.Version]*loadPkg) {
941+
var needPkgs []*loadPkg
942+
for _, pkg := range ld.pkgs {
948943
if pkg.isTest() {
949944
// If we are missing a test, we are also missing its non-test version, and
950945
// we should only add the missing import once.
951946
continue
952947
}
953-
if err, ok := pkg.err.(*ImportMissingError); ok && err.Module.Path != "" {
954-
if err.newMissingVersion != "" {
955-
base.Fatalf("go: %s: package provided by %s at latest version %s but not at required version %s", pkg.stackText(), err.Module.Path, err.Module.Version, err.newMissingVersion)
956-
}
957-
fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, err.Module.Path, err.Module.Version)
958-
if addedModuleFor[pkg.path] {
959-
base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
960-
}
961-
addedModuleFor[pkg.path] = true
962-
if !haveMod[err.Module] {
963-
haveMod[err.Module] = true
964-
modAddedBy[err.Module] = pkg
965-
buildList = append(buildList, err.Module)
966-
}
948+
if pkg.err != errImportMissing {
949+
// Leave other errors for Import or load.Packages to report.
967950
continue
968951
}
969-
// Leave other errors for Import or load.Packages to report.
952+
953+
needPkgs = append(needPkgs, pkg)
954+
955+
pkg := pkg
956+
ld.work.Add(func() {
957+
pkg.mod, pkg.err = queryImport(context.TODO(), pkg.path)
958+
})
959+
}
960+
<-ld.work.Idle()
961+
962+
modAddedBy = map[module.Version]*loadPkg{}
963+
for _, pkg := range needPkgs {
964+
if pkg.err != nil {
965+
continue
966+
}
967+
968+
fmt.Fprintf(os.Stderr, "go: found %s in %s %s\n", pkg.path, pkg.mod.Path, pkg.mod.Version)
969+
if addedModuleFor[pkg.path] {
970+
// TODO(bcmills): This should only be an error if pkg.mod is the same
971+
// version we already tried to add previously.
972+
base.Fatalf("go: %s: looping trying to add package", pkg.stackText())
973+
}
974+
if modAddedBy[pkg.mod] == nil {
975+
modAddedBy[pkg.mod] = pkg
976+
buildList = append(buildList, pkg.mod)
977+
}
970978
}
971-
base.ExitIfErrors()
972979

973980
return modAddedBy
974981
}
@@ -1079,7 +1086,7 @@ func (ld *loader) load(pkg *loadPkg) {
10791086
return
10801087
}
10811088

1082-
pkg.mod, pkg.dir, pkg.err = Import(context.TODO(), pkg.path)
1089+
pkg.mod, pkg.dir, pkg.err = importFromBuildList(context.TODO(), pkg.path)
10831090
if pkg.dir == "" {
10841091
return
10851092
}

0 commit comments

Comments
 (0)