Skip to content

Commit 3a65abf

Browse files
author
Bryan C. Mills
committed
cmd/go: adjust ImportMissingError when module lookup is disabled
Previously, ImportMissingError said "cannot find module providing package …" even when we didn't even attempt to find such a module. Now, we write "no module requirement provides package …" when we did not attempt to identify a suitable module, and suggest either 'go mod tidy' or 'go get -d' as appropriate. Fixes #41576 Change-Id: I979bb999da4066828c54d99a310ea66bb31032ec Reviewed-on: https://go-review.googlesource.com/c/go/+/258298 Trust: Bryan C. Mills <[email protected]> Trust: Jay Conrod <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 076a45a commit 3a65abf

12 files changed

+110
-42
lines changed

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

+23-17
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ import (
2626
"golang.org/x/mod/semver"
2727
)
2828

29-
var errImportMissing = errors.New("import missing")
30-
3129
type ImportMissingError struct {
3230
Path string
3331
Module module.Version
3432
QueryErr error
3533

34+
// inAll indicates whether Path is in the "all" package pattern,
35+
// and thus would be added by 'go mod tidy'.
36+
inAll bool
37+
3638
// newMissingVersion is set to a newer version of Module if one is present
3739
// in the build list. When set, we can't automatically upgrade.
3840
newMissingVersion string
@@ -46,7 +48,19 @@ func (e *ImportMissingError) Error() string {
4648
if e.QueryErr != nil {
4749
return fmt.Sprintf("cannot find module providing package %s: %v", e.Path, e.QueryErr)
4850
}
49-
return "cannot find module providing package " + e.Path
51+
if cfg.BuildMod == "mod" {
52+
return "cannot find module providing package " + e.Path
53+
}
54+
55+
suggestion := ""
56+
if !HasModRoot() {
57+
suggestion = ": working directory is not part of a module"
58+
} else if e.inAll {
59+
suggestion = "; try 'go mod tidy' to add it"
60+
} else {
61+
suggestion = fmt.Sprintf("; try 'go get -d %s' to add it", e.Path)
62+
}
63+
return fmt.Sprintf("no required module provides package %s%s", e.Path, suggestion)
5064
}
5165

5266
if e.newMissingVersion != "" {
@@ -132,7 +146,7 @@ func (e *invalidImportError) Unwrap() error {
132146
// like "C" and "unsafe".
133147
//
134148
// If the package cannot be found in the current build list,
135-
// importFromBuildList returns errImportMissing as the error.
149+
// importFromBuildList returns an *ImportMissingError.
136150
func importFromBuildList(ctx context.Context, path string) (m module.Version, dir string, err error) {
137151
if strings.Contains(path, "@") {
138152
return module.Version{}, "", fmt.Errorf("import path should not have @version")
@@ -144,6 +158,10 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
144158
// There's no directory for import "C" or import "unsafe".
145159
return module.Version{}, "", nil
146160
}
161+
// Before any further lookup, check that the path is valid.
162+
if err := module.CheckImportPath(path); err != nil {
163+
return module.Version{}, "", &invalidImportError{importPath: path, err: err}
164+
}
147165

148166
// Is the package in the standard library?
149167
pathIsStd := search.IsStandardImportPath(path)
@@ -212,21 +230,14 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
212230
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
213231
}
214232

215-
return module.Version{}, "", errImportMissing
233+
return module.Version{}, "", &ImportMissingError{Path: path}
216234
}
217235

218236
// queryImport attempts to locate a module that can be added to the current
219237
// build list to provide the package with the given import path.
220238
func queryImport(ctx context.Context, path string) (module.Version, error) {
221239
pathIsStd := search.IsStandardImportPath(path)
222240

223-
if modRoot == "" && !allowMissingModuleImports {
224-
return module.Version{}, &ImportMissingError{
225-
Path: path,
226-
QueryErr: errors.New("working directory is not part of a module"),
227-
}
228-
}
229-
230241
// Not on build list.
231242
// To avoid spurious remote fetches, next try the latest replacement for each
232243
// module (golang.org/issue/26241). This should give a useful message
@@ -291,11 +302,6 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
291302
}
292303
}
293304

294-
// Before any further lookup, check that the path is valid.
295-
if err := module.CheckImportPath(path); err != nil {
296-
return module.Version{}, &invalidImportError{importPath: path, err: err}
297-
}
298-
299305
if pathIsStd {
300306
// This package isn't in the standard library, isn't in any module already
301307
// in the build list, and isn't in any other module that the user has

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

+18-7
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,19 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
270270
// Report errors, if any.
271271
checkMultiplePaths()
272272
for _, pkg := range loaded.pkgs {
273-
if pkg.err != nil && !opts.SilenceErrors {
274-
if opts.AllowErrors {
275-
fmt.Fprintf(os.Stderr, "%s: %v\n", pkg.stackText(), pkg.err)
276-
} else {
277-
base.Errorf("%s: %v", pkg.stackText(), pkg.err)
273+
if pkg.err != nil {
274+
if pkg.flags.has(pkgInAll) {
275+
if imErr := (*ImportMissingError)(nil); errors.As(pkg.err, &imErr) {
276+
imErr.inAll = true
277+
}
278+
}
279+
280+
if !opts.SilenceErrors {
281+
if opts.AllowErrors {
282+
fmt.Fprintf(os.Stderr, "%s: %v\n", pkg.stackText(), pkg.err)
283+
} else {
284+
base.Errorf("%s: %v", pkg.stackText(), pkg.err)
285+
}
278286
}
279287
}
280288
if !pkg.isTest() {
@@ -809,7 +817,7 @@ func loadFromRoots(params loaderParams) *loader {
809817

810818
ld.buildStacks()
811819

812-
if !ld.resolveMissing {
820+
if !ld.resolveMissing || (!HasModRoot() && !allowMissingModuleImports) {
813821
// We've loaded as much as we can without resolving missing imports.
814822
break
815823
}
@@ -872,12 +880,15 @@ func loadFromRoots(params loaderParams) *loader {
872880
func (ld *loader) resolveMissingImports(addedModuleFor map[string]bool) (modAddedBy map[module.Version]*loadPkg) {
873881
var needPkgs []*loadPkg
874882
for _, pkg := range ld.pkgs {
883+
if pkg.err == nil {
884+
continue
885+
}
875886
if pkg.isTest() {
876887
// If we are missing a test, we are also missing its non-test version, and
877888
// we should only add the missing import once.
878889
continue
879890
}
880-
if pkg.err != errImportMissing {
891+
if !errors.As(pkg.err, new(*ImportMissingError)) {
881892
// Leave other errors for Import or load.Packages to report.
882893
continue
883894
}

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

+14-2
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,30 @@ stderr '^go get appengine: package appengine is not in GOROOT \(.*\)$'
66
! go get x/y.z
77
stderr 'malformed module path "x/y.z": missing dot in first path element'
88

9+
910
# 'go list -m' should report errors about module names, never GOROOT.
1011
! go list -m -versions appengine
1112
stderr 'malformed module path "appengine": missing dot in first path element'
1213
! go list -m -versions x/y.z
1314
stderr 'malformed module path "x/y.z": missing dot in first path element'
1415

16+
1517
# build should report all unsatisfied imports,
1618
# but should be more definitive about non-module import paths
1719
! go build ./useappengine
18-
stderr 'cannot find package'
20+
stderr '^useappengine[/\\]x.go:2:8: cannot find package$'
1921
! go build ./usenonexistent
20-
stderr 'cannot find module providing package nonexistent.rsc.io'
22+
stderr '^usenonexistent[/\\]x.go:2:8: no required module provides package nonexistent.rsc.io; try ''go mod tidy'' to add it$'
23+
24+
25+
# 'get -d' should be similarly definitive
26+
27+
go get -d ./useappengine # TODO(#41315): This should fail.
28+
# stderr '^useappengine[/\\]x.go:2:8: cannot find package$'
29+
30+
! go get -d ./usenonexistent
31+
stderr '^x/usenonexistent imports\n\tnonexistent.rsc.io: cannot find module providing package nonexistent.rsc.io$'
32+
2133

2234
# go mod vendor and go mod tidy should ignore appengine imports.
2335
rm usenonexistent/x.go

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ stderr '^bad[/\\]bad.go:3:8: malformed import path "🐧.example.com/string": in
1212
# TODO(#41688): This should include a file and line, and report the reason for the error..
1313
# (Today it includes only an import stack, and does not indicate the actual problem.)
1414
! go get -d ./main
15-
stderr '^m/main imports\n\tm/bad imports\n\t🐧.example.com/string: import missing$'
15+
stderr '^m/main imports\n\tm/bad imports\n\t🐧.example.com/string: malformed import path "🐧.example.com/string": invalid char ''🐧''$'
1616

1717

1818
-- go.mod --

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ cmp go.mod.orig go.mod
1414

1515
! go get example.net/[email protected] .
1616
stderr -count=1 '^go: found example.net/pkgadded/subpkg in example.net/pkgadded v1\.2\.0$' # TODO: We shouldn't even try v1.2.0.
17-
stderr '^example.com/m imports\n\texample.net/pkgadded/subpkg: import missing' # TODO: better error message
17+
stderr '^example.com/m imports\n\texample.net/pkgadded/subpkg: cannot find module providing package example.net/pkgadded/subpkg$'
1818
cmp go.mod.orig go.mod
1919

2020
go get example.net/[email protected]

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ cp go.mod go.mod.orig
66
# the package in the current directory) cannot be resolved.
77

88
! go get
9-
stderr '^example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: import missing$' # TODO: better error message
9+
stderr '^example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: cannot find module providing package example.net/oops$'
1010
cmp go.mod.orig go.mod
1111

1212
! go get -d
13-
stderr '^example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: import missing$' # TODO: better error message
13+
stderr '^example.com/m imports\n\texample.com/badimport imports\n\texample.net/oops: cannot find module providing package example.net/oops$'
1414
cmp go.mod.orig go.mod
1515

1616
cd importsyntax

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ exec $WORK/testimport$GOEXE other/x/y/z/w .
1919
stdout w2.go
2020

2121
! exec $WORK/testimport$GOEXE gobuild.example.com/x/y/z/w .
22-
stderr 'cannot find module providing package gobuild.example.com/x/y/z/w'
22+
stderr 'no required module provides package gobuild.example.com/x/y/z/w; try ''go get -d gobuild.example.com/x/y/z/w'' to add it'
2323

2424
cd z
2525
exec $WORK/testimport$GOEXE other/x/y/z/w .

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

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

3-
# golang.org/issue/31248: module requirements imposed by dependency versions
3+
# golang.org/issue/31248: required modules imposed by dependency versions
44
# older than the selected version must still be taken into account.
55

66
env GOFLAGS=-mod=readonly

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ stdout example.com/notfound
3939

4040
# Listing the missing dependency directly should fail outright...
4141
! go list -f '{{if .Error}}error{{end}} {{if .Incomplete}}incomplete{{end}}' example.com/notfound
42-
stderr 'cannot find module providing package example.com/notfound'
42+
stderr 'no required module provides package example.com/notfound; try ''go get -d example.com/notfound'' to add it'
4343
! stdout error
4444
! stdout incomplete
4545

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

+12-7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ stdout '^fmt$'
3939
go list ./needmod/needmod.go
4040
stdout 'command-line-arguments'
4141

42+
# 'go list' on a package from a module should fail.
43+
! go list example.com/printversion
44+
stderr '^no required module provides package example.com/printversion: working directory is not part of a module$'
45+
46+
4247
# 'go list -m' with an explicit version should resolve that version.
4348
go list -m example.com/version@latest
4449
stdout 'example.com/version v1.1.0'
@@ -151,7 +156,7 @@ stderr 'cannot find main module'
151156

152157
# 'go build' of source files should fail if they import anything outside std.
153158
! go build -n ./needmod/needmod.go
154-
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
159+
stderr '^needmod[/\\]needmod.go:10:2: no required module provides package example.com/version: working directory is not part of a module$'
155160

156161
# 'go build' of source files should succeed if they do not import anything outside std.
157162
go build -n -o ignore ./stdonly/stdonly.go
@@ -174,7 +179,7 @@ go doc fmt
174179

175180
# 'go doc' should fail for a package path outside a module.
176181
! go doc example.com/version
177-
stderr 'doc: cannot find module providing package example.com/version: working directory is not part of a module'
182+
stderr 'doc: no required module provides package example.com/version: working directory is not part of a module'
178183

179184
# 'go install' with a version should succeed if all constraints are met.
180185
# See mod_install_pkg_version.
@@ -184,12 +189,12 @@ exists $GOPATH/bin/printversion$GOEXE
184189

185190
# 'go install' should fail if a package argument must be resolved to a module.
186191
! go install example.com/printversion
187-
stderr 'cannot find module providing package example.com/printversion: working directory is not part of a module'
192+
stderr 'no required module provides package example.com/printversion: working directory is not part of a module'
188193

189194
# 'go install' should fail if a source file imports a package that must be
190195
# resolved to a module.
191196
! go install ./needmod/needmod.go
192-
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
197+
stderr 'needmod[/\\]needmod.go:10:2: no required module provides package example.com/version: working directory is not part of a module'
193198

194199

195200
# 'go run' with a verison should fail due to syntax.
@@ -198,12 +203,12 @@ stderr 'can only use path@version syntax with'
198203

199204
# 'go run' should fail if a package argument must be resolved to a module.
200205
! go run example.com/printversion
201-
stderr 'cannot find module providing package example.com/printversion: working directory is not part of a module'
206+
stderr '^no required module provides package example.com/printversion: working directory is not part of a module$'
202207

203208
# 'go run' should fail if a source file imports a package that must be
204209
# resolved to a module.
205210
! go run ./needmod/needmod.go
206-
stderr 'needmod[/\\]needmod.go:10:2: cannot find module providing package example.com/version: working directory is not part of a module'
211+
stderr '^needmod[/\\]needmod.go:10:2: no required module provides package example.com/version: working directory is not part of a module$'
207212

208213

209214
# 'go fmt' should be able to format files outside of a module.
@@ -221,7 +226,7 @@ stdout 'main is example.com/printversion v0.1.0'
221226
stdout 'using example.com/version v1.1.0'
222227

223228
# 'go get' of a versioned binary should build and install the latest version
224-
# using its minimal module requirements, ignoring replacements and exclusions.
229+
# using its minimal required modules, ignoring replacements and exclusions.
225230
go get example.com/printversion
226231
exec ../bin/printversion
227232
stdout 'path is example.com/printversion'

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ cmp go.mod go.mod.empty
1313
# -mod=readonly should be set by default.
1414
env GOFLAGS=
1515
! go list all
16-
stderr '^x.go:2:8: cannot find module providing package rsc\.io/quote$'
16+
stderr '^x.go:2:8: no required module provides package rsc\.io/quote; try ''go mod tidy'' to add it$'
1717
cmp go.mod go.mod.empty
1818

1919
env GOFLAGS=-mod=readonly
@@ -68,6 +68,23 @@ cp go.mod.indirect go.mod
6868
go list all
6969
cmp go.mod go.mod.indirect
7070

71+
72+
# If we identify a missing package as a dependency of some other package in the
73+
# main module, we should suggest 'go mod tidy' instead of resolving it.
74+
75+
cp go.mod.untidy go.mod
76+
! go list all
77+
stderr '^x.go:2:8: no required module provides package rsc.io/quote; try ''go mod tidy'' to add it$'
78+
79+
! go list -deps .
80+
stderr '^x.go:2:8: no required module provides package rsc.io/quote; try ''go mod tidy'' to add it$'
81+
82+
# However, if we didn't see an import from the main module, we should suggest
83+
# 'go get -d' instead, because we don't know whether 'go mod tidy' would add it.
84+
! go list rsc.io/quote
85+
stderr '^no required module provides package rsc.io/quote; try ''go get -d rsc.io/quote'' to add it$'
86+
87+
7188
-- go.mod --
7289
module m
7390

@@ -103,3 +120,11 @@ require (
103120
rsc.io/sampler v1.3.0 // indirect
104121
rsc.io/testonly v1.0.0 // indirect
105122
)
123+
-- go.mod.untidy --
124+
module m
125+
126+
go 1.20
127+
128+
require (
129+
rsc.io/sampler v1.3.0 // indirect
130+
)

src/go/build/build_test.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,13 @@ func TestImportPackageOutsideModule(t *testing.T) {
612612
ctxt.GOPATH = gopath
613613
ctxt.Dir = filepath.Join(gopath, "src/example.com/p")
614614

615-
want := "cannot find module providing package"
615+
want := "working directory is not part of a module"
616616
if _, err := ctxt.Import("example.com/p", gopath, FindOnly); err == nil {
617617
t.Fatal("importing package when no go.mod is present succeeded unexpectedly")
618618
} else if errStr := err.Error(); !strings.Contains(errStr, want) {
619619
t.Fatalf("error when importing package when no go.mod is present: got %q; want %q", errStr, want)
620+
} else {
621+
t.Logf(`ctxt.Import("example.com/p", _, FindOnly): %v`, err)
620622
}
621623
}
622624

@@ -677,9 +679,16 @@ func TestMissingImportErrorRepetition(t *testing.T) {
677679
if err == nil {
678680
t.Fatal("unexpected success")
679681
}
682+
680683
// Don't count the package path with a URL like https://...?go-get=1.
681684
// See golang.org/issue/35986.
682685
errStr := strings.ReplaceAll(err.Error(), "://"+pkgPath+"?go-get=1", "://...?go-get=1")
686+
687+
// Also don't count instances in suggested "go get" or similar commands
688+
// (see https://golang.org/issue/41576). The suggested command typically
689+
// follows a semicolon.
690+
errStr = strings.SplitN(errStr, ";", 2)[0]
691+
683692
if n := strings.Count(errStr, pkgPath); n != 1 {
684693
t.Fatalf("package path %q appears in error %d times; should appear once\nerror: %v", pkgPath, n, err)
685694
}

0 commit comments

Comments
 (0)