Skip to content

Commit 5cd4390

Browse files
author
Jay Conrod
committed
cmd/go: don't fetch files missing sums in readonly mode
If the go command needs a .mod or .zip file in -mod=readonly mode (now the default), and that file doesn't have a hash in the main module's go.sum file, the go command will now report an error before fetching the file, rather than at the end when failing to update go.sum. The error says specifically which entry is missing. If this error is encountered when loading the build list, it will suggest 'go mod tidy'. If this error is encountered when loading a specific package (an import or command line argument), the error will mention that package and will suggest 'go mod tidy' or 'go get -d'. Fixes #41934 Fixes #41935 Change-Id: I96ec2ef9258bd4bade9915c43d47e6243c376a81 Reviewed-on: https://go-review.googlesource.com/c/go/+/262341 Reviewed-by: Bryan C. Mills <[email protected]> Trust: Jay Conrod <[email protected]>
1 parent 5f616a6 commit 5cd4390

File tree

14 files changed

+216
-49
lines changed

14 files changed

+216
-49
lines changed

src/cmd/go/internal/load/pkg.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ func (p *Package) setLoadPackageDataError(err error, path string, stk *ImportSta
254254
// package's source files themselves (scanner errors).
255255
//
256256
// TODO(matloob): Perhaps make each of those the errors in the first group
257-
// (including modload.ImportMissingError, and the corresponding
258-
// "cannot find package %q in any of" GOPATH-mode error
257+
// (including modload.ImportMissingError, ImportMissingSumError, and the
258+
// corresponding "cannot find package %q in any of" GOPATH-mode error
259259
// produced in build.(*Context).Import; modload.AmbiguousImportError,
260260
// and modload.PackageNotInModuleError; and the malformed module path errors
261261
// produced in golang.org/x/mod/module.CheckMod) implement an interface
@@ -430,6 +430,7 @@ type ImportPathError interface {
430430
var (
431431
_ ImportPathError = (*importError)(nil)
432432
_ ImportPathError = (*modload.ImportMissingError)(nil)
433+
_ ImportPathError = (*modload.ImportMissingSumError)(nil)
433434
)
434435

435436
type importError struct {

src/cmd/go/internal/modfetch/fetch.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,28 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) error
428428
return nil
429429
}
430430

431+
// HaveSum returns true if the go.sum file contains an entry for mod.
432+
// The entry's hash must be generated with a known hash algorithm.
433+
// mod.Version may have a "/go.mod" suffix to distinguish sums for
434+
// .mod and .zip files.
435+
func HaveSum(mod module.Version) bool {
436+
goSum.mu.Lock()
437+
defer goSum.mu.Unlock()
438+
inited, err := initGoSum()
439+
if err != nil || !inited {
440+
return false
441+
}
442+
for _, h := range goSum.m[mod] {
443+
if !strings.HasPrefix(h, "h1:") {
444+
continue
445+
}
446+
if !goSum.status[modSum{mod, h}].dirty {
447+
return true
448+
}
449+
}
450+
return false
451+
}
452+
431453
// checkMod checks the given module's checksum.
432454
func checkMod(mod module.Version) {
433455
if cfg.GOMODCACHE == "" {

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

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,31 @@ func (e *AmbiguousImportError) Error() string {
124124
return buf.String()
125125
}
126126

127+
// ImportMissingSumError is reported in readonly mode when we need to check
128+
// if a module in the build list contains a package, but we don't have a sum
129+
// for its .zip file.
130+
type ImportMissingSumError struct {
131+
importPath string
132+
found, inAll bool
133+
}
134+
135+
func (e *ImportMissingSumError) Error() string {
136+
var message string
137+
if e.found {
138+
message = fmt.Sprintf("missing go.sum entry needed to verify package %s is provided by exactly one module", e.importPath)
139+
} else {
140+
message = fmt.Sprintf("missing go.sum entry for module providing package %s", e.importPath)
141+
}
142+
if e.inAll {
143+
return message + "; try 'go mod tidy' to add it"
144+
}
145+
return message
146+
}
147+
148+
func (e *ImportMissingSumError) ImportPath() string {
149+
return e.importPath
150+
}
151+
127152
type invalidImportError struct {
128153
importPath string
129154
err error
@@ -208,13 +233,23 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
208233
// Check each module on the build list.
209234
var dirs []string
210235
var mods []module.Version
236+
haveSumErr := false
211237
for _, m := range buildList {
212238
if !maybeInModule(path, m.Path) {
213239
// Avoid possibly downloading irrelevant modules.
214240
continue
215241
}
216-
root, isLocal, err := fetch(ctx, m)
242+
needSum := true
243+
root, isLocal, err := fetch(ctx, m, needSum)
217244
if err != nil {
245+
if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
246+
// We are missing a sum needed to fetch a module in the build list.
247+
// We can't verify that the package is unique, and we may not find
248+
// the package at all. Keep checking other modules to decide which
249+
// error to report.
250+
haveSumErr = true
251+
continue
252+
}
218253
// Report fetch error.
219254
// Note that we don't know for sure this module is necessary,
220255
// but it certainly _could_ provide the package, and even if we
@@ -230,12 +265,15 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
230265
dirs = append(dirs, dir)
231266
}
232267
}
268+
if len(mods) > 1 {
269+
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
270+
}
271+
if haveSumErr {
272+
return module.Version{}, "", &ImportMissingSumError{importPath: path, found: len(mods) > 0}
273+
}
233274
if len(mods) == 1 {
234275
return mods[0], dirs[0], nil
235276
}
236-
if len(mods) > 0 {
237-
return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
238-
}
239277

240278
return module.Version{}, "", &ImportMissingError{Path: path, isStd: pathIsStd}
241279
}
@@ -306,7 +344,8 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
306344
return len(mods[i].Path) > len(mods[j].Path)
307345
})
308346
for _, m := range mods {
309-
root, isLocal, err := fetch(ctx, m)
347+
needSum := true
348+
root, isLocal, err := fetch(ctx, m, needSum)
310349
if err != nil {
311350
// Report fetch error as above.
312351
return module.Version{}, err
@@ -473,9 +512,14 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
473512
// fetch downloads the given module (or its replacement)
474513
// and returns its location.
475514
//
515+
// needSum indicates whether the module may be downloaded in readonly mode
516+
// without a go.sum entry. It should only be false for modules fetched
517+
// speculatively (for example, for incompatible version filtering). The sum
518+
// will still be verified normally.
519+
//
476520
// The isLocal return value reports whether the replacement,
477521
// if any, is local to the filesystem.
478-
func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) {
522+
func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
479523
if mod == Target {
480524
return ModRoot(), true, nil
481525
}
@@ -505,6 +549,18 @@ func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, e
505549
mod = r
506550
}
507551

552+
if cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
553+
return "", false, module.VersionError(mod, &sumMissingError{})
554+
}
555+
508556
dir, err = modfetch.Download(ctx, mod)
509557
return dir, false, err
510558
}
559+
560+
type sumMissingError struct {
561+
suggestion string
562+
}
563+
564+
func (e *sumMissingError) Error() string {
565+
return "missing go.sum entry" + e.suggestion
566+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
276276
if pkg.flags.has(pkgInAll) {
277277
if imErr := (*ImportMissingError)(nil); errors.As(pkg.err, &imErr) {
278278
imErr.inAll = true
279+
} else if sumErr := (*ImportMissingSumError)(nil); errors.As(pkg.err, &sumErr) {
280+
sumErr.inAll = true
279281
}
280282
}
281283

src/cmd/go/internal/modload/modfile.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,11 @@ type retraction struct {
406406
// taking into account any replacements for m, exclusions of its dependencies,
407407
// and/or vendoring.
408408
//
409-
// goModSummary cannot be used on the Target module, as its requirements
410-
// may change.
409+
// m must be a version in the module graph, reachable from the Target module.
410+
// In readonly mode, the go.sum file must contain an entry for m's go.mod file
411+
// (or its replacement). goModSummary must not be called for the Target module
412+
// itself, as its requirements may change. Use rawGoModSummary for other
413+
// module versions.
411414
//
412415
// The caller must not modify the returned summary.
413416
func goModSummary(m module.Version) (*modFileSummary, error) {
@@ -442,6 +445,13 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
442445
if actual.Path == "" {
443446
actual = m
444447
}
448+
if cfg.BuildMod == "readonly" && actual.Version != "" {
449+
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
450+
if !modfetch.HaveSum(key) {
451+
suggestion := fmt.Sprintf("; try 'go mod download %s' to add it", m.Path)
452+
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
453+
}
454+
}
445455
summary, err := rawGoModSummary(actual)
446456
if err != nil {
447457
return nil, err

src/cmd/go/internal/modload/query.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,8 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
599599
return r, err
600600
}
601601
r.Mod.Version = r.Rev.Version
602-
root, isLocal, err := fetch(ctx, r.Mod)
602+
needSum := true
603+
root, isLocal, err := fetch(ctx, r.Mod, needSum)
603604
if err != nil {
604605
return r, err
605606
}
@@ -816,7 +817,8 @@ func (e *PackageNotInModuleError) ImportPath() string {
816817

817818
// ModuleHasRootPackage returns whether module m contains a package m.Path.
818819
func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
819-
root, isLocal, err := fetch(ctx, m)
820+
needSum := false
821+
root, isLocal, err := fetch(ctx, m, needSum)
820822
if err != nil {
821823
return false, err
822824
}
@@ -825,7 +827,8 @@ func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
825827
}
826828

827829
func versionHasGoMod(ctx context.Context, m module.Version) (bool, error) {
828-
root, _, err := fetch(ctx, m)
830+
needSum := false
831+
root, _, err := fetch(ctx, m, needSum)
829832
if err != nil {
830833
return false, err
831834
}

src/cmd/go/internal/modload/search.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
156156
isLocal = true
157157
} else {
158158
var err error
159-
root, isLocal, err = fetch(ctx, mod)
159+
needSum := true
160+
root, isLocal, err = fetch(ctx, mod, needSum)
160161
if err != nil {
161162
m.AddError(err)
162163
continue

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ env GO111MODULE=auto
1616
cd m
1717
cp go.mod go.mod.orig
1818
! go list -m all
19-
stderr 'example.com/[email protected]:.*404 Not Found'
19+
stderr '^go: example.com/[email protected]: missing go.sum entry; try ''go mod download example.com/cmd'' to add it$'
2020
go install example.com/cmd/a@latest
2121
cmp go.mod go.mod.orig
2222
exists $GOPATH/bin/a$GOEXE
@@ -67,9 +67,9 @@ cd tmp
6767
go mod init tmp
6868
go mod edit -require=rsc.io/[email protected]
6969
! go install -mod=readonly $GOPATH/pkg/mod/rsc.io/[email protected]
70-
stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
70+
stderr '^go: rsc.io/[email protected]: missing go.sum entry; try ''go mod download rsc.io/fortune'' to add it$'
7171
! go install -mod=readonly ../../pkg/mod/rsc.io/[email protected]
72-
stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
72+
stderr '^go: rsc.io/[email protected]: missing go.sum entry; try ''go mod download rsc.io/fortune'' to add it$'
7373
go get -d rsc.io/[email protected]
7474
go install -mod=readonly $GOPATH/pkg/mod/rsc.io/[email protected]
7575
exists $GOPATH/bin/fortune$GOEXE

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ module m
4040
go 1.13
4141

4242
require example.com/badchain/a v1.0.0
43+
-- go.sum --
44+
example.com/badchain/a v1.0.0 h1:iJDLiHLmpQgr9Zrv+44UqywAE2IG6WkHnH4uG08vf+s=
45+
example.com/badchain/a v1.0.0/go.mod h1:6/gnCYHdVrs6mUgatUYUSbuHxEY+/yWedmTggLz23EI=
46+
example.com/badchain/a v1.1.0 h1:cPxQpsOjaIrn05yDfl4dFFgGSbjYmytLqtIIBfTsEqA=
47+
example.com/badchain/a v1.1.0/go.mod h1:T15b2BEK+RY7h7Lr2dgS38p1pgH5/t7Kf5nQXBlcW/A=
48+
example.com/badchain/b v1.0.0 h1:kjDVlBxpjQavYxHE7ECCyyXhfwsfhWIqvghfRgPktSA=
49+
example.com/badchain/b v1.0.0/go.mod h1:sYsH934pMc3/A2vQZh019qrWmp4+k87l3O0VFUYqL+I=
50+
example.com/badchain/b v1.1.0 h1:iEALV+DRN62FArnYylBR4YwCALn/hCdITvhdagHa0L4=
51+
example.com/badchain/b v1.1.0/go.mod h1:mlCgKO7lRZ+ijwMFIBFRPCGt5r5oqCcHdhSSE0VL4uY=
52+
example.com/badchain/c v1.0.0 h1:lOeUHQKR7SboSH7Bj6eIDWoNHaDQXI0T2GfaH2x9fNA=
53+
example.com/badchain/c v1.0.0/go.mod h1:4U3gzno17SaQ2koSVNxITu9r60CeLSgye9y4/5LnfOE=
54+
example.com/badchain/c v1.1.0 h1:VtTg1g7fOutWKHQf+ag04KLRpdMGSfQ9s9tagVtGW14=
55+
example.com/badchain/c v1.1.0/go.mod h1:tyoJj5qh+qtb48sflwdVvk4R+OjPQEY2UJOoibsVLPk=
4356
-- use/use.go --
4457
package use
4558

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# the original module and its location, report an error with all three paths.
33
# In particular, the "required as" path should be the original.
44
# Verifies golang.org/issue/38220.
5-
! go list .
5+
! go mod download
66
cmp stderr want
77

88
-- go.mod --

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ go list -m all
4141

4242
# -mod=readonly should reject inconsistent go.mod files
4343
# (ones that would be rewritten).
44-
go mod edit -require rsc.io/[email protected]
44+
go get -d rsc.io/[email protected]
45+
go mod edit -require rsc.io/[email protected]
4546
cp go.mod go.mod.inconsistent
4647
! go list
4748
stderr 'go: updates to go.mod needed, disabled by -mod=readonly'
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Test that go.sum does not get updated when -mod=readonly flag is set
2+
env GO111MODULE=on
3+
4+
# When a sum is needed to load the build list, we get an error for the
5+
# specific module. The .mod file is not downloaded, and go.sum is not written.
6+
! go list -m all
7+
stderr '^go: rsc.io/[email protected]: missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
8+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
9+
! exists go.sum
10+
11+
# If go.sum exists but contains hashes from an algorithm we don't know about,
12+
# we should see the same error.
13+
cp go.sum.h2only go.sum
14+
! go list -m all
15+
stderr '^go: rsc.io/[email protected]: missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
16+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
17+
cmp go.sum go.sum.h2only
18+
rm go.sum
19+
20+
# If we replace a module, we should see a missing sum error for the replacement.
21+
cp go.mod go.mod.orig
22+
go mod edit -replace rsc.io/[email protected]=rsc.io/[email protected]
23+
! go list -m all
24+
stderr '^go: rsc.io/[email protected] \(replaced by rsc.io/[email protected]\): missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
25+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.1.mod
26+
! exists go.sum
27+
cp go.mod.orig go.mod
28+
29+
# Control: when sums are present, loading the build list downloads .mod files.
30+
cp go.sum.buildlistonly go.sum
31+
go list -m all
32+
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
33+
34+
35+
# When a sum is needed to load a .mod file for a package outside the build list,
36+
# we get a generic missing import error.
37+
! go list example.com/doesnotexist
38+
stderr '^no required module provides package example.com/doesnotexist; try ''go get -d example.com/doesnotexist'' to add it$'
39+
40+
# When a sum is needed to load a .zip file, we get a more specific error.
41+
# The .zip file is not downloaded.
42+
! go list rsc.io/quote
43+
stderr '^missing go.sum entry for module providing package rsc.io/quote$'
44+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
45+
46+
# The error is attached to the package from the missing module. We can load
47+
# a package that imports it without that error.
48+
go list -e -deps -f '{{.ImportPath}}{{with .Error}} {{.Err}}{{end}}' .
49+
stdout '^m$'
50+
stdout '^rsc.io/quote missing go.sum entry for module providing package rsc.io/quote; try ''go mod tidy'' to add it$'
51+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
52+
53+
# go.sum should not have been written.
54+
cmp go.sum go.sum.buildlistonly
55+
56+
# Control: when sums are present, 'go list' downloads .zip files.
57+
cp go.sum.tidy go.sum
58+
go list .
59+
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
60+
61+
-- go.mod --
62+
module m
63+
64+
go 1.15
65+
66+
require rsc.io/quote v1.5.2
67+
-- use.go --
68+
package use
69+
70+
import _ "rsc.io/quote"
71+
-- go.sum.h2only --
72+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h2:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
73+
rsc.io/quote v1.5.2/go.mod h2:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
74+
rsc.io/sampler v1.3.0/go.mod h2:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
75+
-- go.sum.buildlistonly --
76+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
77+
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
78+
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
79+
-- go.sum.tidy --
80+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
81+
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
82+
rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=
83+
rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
84+
rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
85+
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
86+
rsc.io/testonly v1.0.0 h1:K/VWHdO+Jv7woUXG0GzVNx1czBXUt3Ib1deaMn+xk64=
87+
rsc.io/testonly v1.0.0/go.mod h1:OqmGbIFOcF+XrFReLOGZ6BhMM7uMBiQwZsyNmh74SzY=

0 commit comments

Comments
 (0)