]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go: don't fetch files missing sums in readonly mode
authorJay Conrod <jayconrod@google.com>
Tue, 13 Oct 2020 22:19:21 +0000 (18:19 -0400)
committerJay Conrod <jayconrod@google.com>
Fri, 23 Oct 2020 20:54:30 +0000 (20:54 +0000)
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 <bcmills@google.com>
Trust: Jay Conrod <jayconrod@google.com>

14 files changed:
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/modfetch/fetch.go
src/cmd/go/internal/modload/import.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/modload/query.go
src/cmd/go/internal/modload/search.go
src/cmd/go/testdata/script/mod_install_pkg_version.txt
src/cmd/go/testdata/script/mod_load_badchain.txt
src/cmd/go/testdata/script/mod_load_replace_mismatch.txt
src/cmd/go/testdata/script/mod_readonly.txt
src/cmd/go/testdata/script/mod_sum_readonly.txt [new file with mode: 0644]
src/cmd/go/testdata/script/mod_sumdb.txt
src/cmd/go/testdata/script/sum_readonly.txt [deleted file]

index 29709a6dd37d09e0deb61ca51202029fa09a33d9..fcd7728c7b4694384a001f243bb548b8445fe456 100644 (file)
@@ -254,8 +254,8 @@ func (p *Package) setLoadPackageDataError(err error, path string, stk *ImportSta
        // package's source files themselves (scanner errors).
        //
        // TODO(matloob): Perhaps make each of those the errors in the first group
-       // (including modload.ImportMissingError, and the corresponding
-       // "cannot find package %q in any of" GOPATH-mode error
+       // (including modload.ImportMissingError, ImportMissingSumError, and the
+       // corresponding "cannot find package %q in any of" GOPATH-mode error
        // produced in build.(*Context).Import; modload.AmbiguousImportError,
        // and modload.PackageNotInModuleError; and the malformed module path errors
        // produced in golang.org/x/mod/module.CheckMod) implement an interface
@@ -430,6 +430,7 @@ type ImportPathError interface {
 var (
        _ ImportPathError = (*importError)(nil)
        _ ImportPathError = (*modload.ImportMissingError)(nil)
+       _ ImportPathError = (*modload.ImportMissingSumError)(nil)
 )
 
 type importError struct {
index 40196c4e9a8e59276da0ffb4267bdddbbb8735ba..25e9fb62c13722940985b49d3dc91baad0ef4cbe 100644 (file)
@@ -428,6 +428,28 @@ func readGoSum(dst map[module.Version][]string, file string, data []byte) error
        return nil
 }
 
+// HaveSum returns true if the go.sum file contains an entry for mod.
+// The entry's hash must be generated with a known hash algorithm.
+// mod.Version may have a "/go.mod" suffix to distinguish sums for
+// .mod and .zip files.
+func HaveSum(mod module.Version) bool {
+       goSum.mu.Lock()
+       defer goSum.mu.Unlock()
+       inited, err := initGoSum()
+       if err != nil || !inited {
+               return false
+       }
+       for _, h := range goSum.m[mod] {
+               if !strings.HasPrefix(h, "h1:") {
+                       continue
+               }
+               if !goSum.status[modSum{mod, h}].dirty {
+                       return true
+               }
+       }
+       return false
+}
+
 // checkMod checks the given module's checksum.
 func checkMod(mod module.Version) {
        if cfg.GOMODCACHE == "" {
index bcbc9b0c3adefeea2bcd2f3ea3a9824cccb0da4d..ffe8733af6f262c1e9b71b42ceca112f60a06a2e 100644 (file)
@@ -124,6 +124,31 @@ func (e *AmbiguousImportError) Error() string {
        return buf.String()
 }
 
+// ImportMissingSumError is reported in readonly mode when we need to check
+// if a module in the build list contains a package, but we don't have a sum
+// for its .zip file.
+type ImportMissingSumError struct {
+       importPath   string
+       found, inAll bool
+}
+
+func (e *ImportMissingSumError) Error() string {
+       var message string
+       if e.found {
+               message = fmt.Sprintf("missing go.sum entry needed to verify package %s is provided by exactly one module", e.importPath)
+       } else {
+               message = fmt.Sprintf("missing go.sum entry for module providing package %s", e.importPath)
+       }
+       if e.inAll {
+               return message + "; try 'go mod tidy' to add it"
+       }
+       return message
+}
+
+func (e *ImportMissingSumError) ImportPath() string {
+       return e.importPath
+}
+
 type invalidImportError struct {
        importPath string
        err        error
@@ -208,13 +233,23 @@ func importFromBuildList(ctx context.Context, path string) (m module.Version, di
        // Check each module on the build list.
        var dirs []string
        var mods []module.Version
+       haveSumErr := false
        for _, m := range buildList {
                if !maybeInModule(path, m.Path) {
                        // Avoid possibly downloading irrelevant modules.
                        continue
                }
-               root, isLocal, err := fetch(ctx, m)
+               needSum := true
+               root, isLocal, err := fetch(ctx, m, needSum)
                if err != nil {
+                       if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
+                               // We are missing a sum needed to fetch a module in the build list.
+                               // We can't verify that the package is unique, and we may not find
+                               // the package at all. Keep checking other modules to decide which
+                               // error to report.
+                               haveSumErr = true
+                               continue
+                       }
                        // Report fetch error.
                        // Note that we don't know for sure this module is necessary,
                        // 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
                        dirs = append(dirs, dir)
                }
        }
+       if len(mods) > 1 {
+               return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
+       }
+       if haveSumErr {
+               return module.Version{}, "", &ImportMissingSumError{importPath: path, found: len(mods) > 0}
+       }
        if len(mods) == 1 {
                return mods[0], dirs[0], nil
        }
-       if len(mods) > 0 {
-               return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: dirs, Modules: mods}
-       }
 
        return module.Version{}, "", &ImportMissingError{Path: path, isStd: pathIsStd}
 }
@@ -306,7 +344,8 @@ func queryImport(ctx context.Context, path string) (module.Version, error) {
                        return len(mods[i].Path) > len(mods[j].Path)
                })
                for _, m := range mods {
-                       root, isLocal, err := fetch(ctx, m)
+                       needSum := true
+                       root, isLocal, err := fetch(ctx, m, needSum)
                        if err != nil {
                                // Report fetch error as above.
                                return module.Version{}, err
@@ -473,9 +512,14 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
 // fetch downloads the given module (or its replacement)
 // and returns its location.
 //
+// needSum indicates whether the module may be downloaded in readonly mode
+// without a go.sum entry. It should only be false for modules fetched
+// speculatively (for example, for incompatible version filtering). The sum
+// will still be verified normally.
+//
 // The isLocal return value reports whether the replacement,
 // if any, is local to the filesystem.
-func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, err error) {
+func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
        if mod == Target {
                return ModRoot(), true, nil
        }
@@ -505,6 +549,18 @@ func fetch(ctx context.Context, mod module.Version) (dir string, isLocal bool, e
                mod = r
        }
 
+       if cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
+               return "", false, module.VersionError(mod, &sumMissingError{})
+       }
+
        dir, err = modfetch.Download(ctx, mod)
        return dir, false, err
 }
+
+type sumMissingError struct {
+       suggestion string
+}
+
+func (e *sumMissingError) Error() string {
+       return "missing go.sum entry" + e.suggestion
+}
index dc816540b9c26fd193740d8b75653d27ec37c83e..b770c19c7c8f6350fc6e42ea1fe6930acb7993e4 100644 (file)
@@ -276,6 +276,8 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                        if pkg.flags.has(pkgInAll) {
                                if imErr := (*ImportMissingError)(nil); errors.As(pkg.err, &imErr) {
                                        imErr.inAll = true
+                               } else if sumErr := (*ImportMissingSumError)(nil); errors.As(pkg.err, &sumErr) {
+                                       sumErr.inAll = true
                                }
                        }
 
index 006db4f169ebe5308b9dd89c6ed19dfd5b2bb7bb..7a8963246bed2df728c78d72f5eb3d2f0246b602 100644 (file)
@@ -406,8 +406,11 @@ type retraction struct {
 // taking into account any replacements for m, exclusions of its dependencies,
 // and/or vendoring.
 //
-// goModSummary cannot be used on the Target module, as its requirements
-// may change.
+// m must be a version in the module graph, reachable from the Target module.
+// In readonly mode, the go.sum file must contain an entry for m's go.mod file
+// (or its replacement). goModSummary must not be called for the Target module
+// itself, as its requirements may change. Use rawGoModSummary for other
+// module versions.
 //
 // The caller must not modify the returned summary.
 func goModSummary(m module.Version) (*modFileSummary, error) {
@@ -442,6 +445,13 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
        if actual.Path == "" {
                actual = m
        }
+       if cfg.BuildMod == "readonly" && actual.Version != "" {
+               key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
+               if !modfetch.HaveSum(key) {
+                       suggestion := fmt.Sprintf("; try 'go mod download %s' to add it", m.Path)
+                       return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
+               }
+       }
        summary, err := rawGoModSummary(actual)
        if err != nil {
                return nil, err
index 6b147683888f51137c9b8285d5671fc5fac1d456..3927051015ae3713610ab27898bcef6dd8b26abb 100644 (file)
@@ -599,7 +599,8 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
                                return r, err
                        }
                        r.Mod.Version = r.Rev.Version
-                       root, isLocal, err := fetch(ctx, r.Mod)
+                       needSum := true
+                       root, isLocal, err := fetch(ctx, r.Mod, needSum)
                        if err != nil {
                                return r, err
                        }
@@ -816,7 +817,8 @@ func (e *PackageNotInModuleError) ImportPath() string {
 
 // ModuleHasRootPackage returns whether module m contains a package m.Path.
 func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
-       root, isLocal, err := fetch(ctx, m)
+       needSum := false
+       root, isLocal, err := fetch(ctx, m, needSum)
        if err != nil {
                return false, err
        }
@@ -825,7 +827,8 @@ func ModuleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
 }
 
 func versionHasGoMod(ctx context.Context, m module.Version) (bool, error) {
-       root, _, err := fetch(ctx, m)
+       needSum := false
+       root, _, err := fetch(ctx, m, needSum)
        if err != nil {
                return false, err
        }
index be4cb7e745a44a8760f087aae822f77b245f452e..f6d6f5f764e59503e5101c73199fa7c2a72d306e 100644 (file)
@@ -156,7 +156,8 @@ func matchPackages(ctx context.Context, m *search.Match, tags map[string]bool, f
                        isLocal = true
                } else {
                        var err error
-                       root, isLocal, err = fetch(ctx, mod)
+                       needSum := true
+                       root, isLocal, err = fetch(ctx, mod, needSum)
                        if err != nil {
                                m.AddError(err)
                                continue
index dc4a329688c126d8d7dc9644c7b412b7603ff704..93318b6659d4e3c0f3ff4c2f328a1b8ca442bff5 100644 (file)
@@ -16,7 +16,7 @@ env GO111MODULE=auto
 cd m
 cp go.mod go.mod.orig
 ! go list -m all
-stderr 'example.com/cmd@v1.1.0-doesnotexist:.*404 Not Found'
+stderr '^go: example.com/cmd@v1.1.0-doesnotexist: missing go.sum entry; try ''go mod download example.com/cmd'' to add it$'
 go install example.com/cmd/a@latest
 cmp go.mod go.mod.orig
 exists $GOPATH/bin/a$GOEXE
@@ -67,9 +67,9 @@ cd tmp
 go mod init tmp
 go mod edit -require=rsc.io/fortune@v1.0.0
 ! go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
-stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
+stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; try ''go mod download rsc.io/fortune'' to add it$'
 ! go install -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0
-stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
+stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; try ''go mod download rsc.io/fortune'' to add it$'
 go get -d rsc.io/fortune@v1.0.0
 go install -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
 exists $GOPATH/bin/fortune$GOEXE
index e943179c5418e12b44cc9198abf2015af6185b59..a71c4a849e502fb38fcdaa4b792c39dc754473a1 100644 (file)
@@ -40,6 +40,19 @@ module m
 go 1.13
 
 require example.com/badchain/a v1.0.0
+-- go.sum --
+example.com/badchain/a v1.0.0 h1:iJDLiHLmpQgr9Zrv+44UqywAE2IG6WkHnH4uG08vf+s=
+example.com/badchain/a v1.0.0/go.mod h1:6/gnCYHdVrs6mUgatUYUSbuHxEY+/yWedmTggLz23EI=
+example.com/badchain/a v1.1.0 h1:cPxQpsOjaIrn05yDfl4dFFgGSbjYmytLqtIIBfTsEqA=
+example.com/badchain/a v1.1.0/go.mod h1:T15b2BEK+RY7h7Lr2dgS38p1pgH5/t7Kf5nQXBlcW/A=
+example.com/badchain/b v1.0.0 h1:kjDVlBxpjQavYxHE7ECCyyXhfwsfhWIqvghfRgPktSA=
+example.com/badchain/b v1.0.0/go.mod h1:sYsH934pMc3/A2vQZh019qrWmp4+k87l3O0VFUYqL+I=
+example.com/badchain/b v1.1.0 h1:iEALV+DRN62FArnYylBR4YwCALn/hCdITvhdagHa0L4=
+example.com/badchain/b v1.1.0/go.mod h1:mlCgKO7lRZ+ijwMFIBFRPCGt5r5oqCcHdhSSE0VL4uY=
+example.com/badchain/c v1.0.0 h1:lOeUHQKR7SboSH7Bj6eIDWoNHaDQXI0T2GfaH2x9fNA=
+example.com/badchain/c v1.0.0/go.mod h1:4U3gzno17SaQ2koSVNxITu9r60CeLSgye9y4/5LnfOE=
+example.com/badchain/c v1.1.0 h1:VtTg1g7fOutWKHQf+ag04KLRpdMGSfQ9s9tagVtGW14=
+example.com/badchain/c v1.1.0/go.mod h1:tyoJj5qh+qtb48sflwdVvk4R+OjPQEY2UJOoibsVLPk=
 -- use/use.go --
 package use
 
index 067e209b01825f060ffc4c1505c0a7106628434d..2ca8b3cace22ced2b2dac9ca4e7dc8e47a244ff8 100644 (file)
@@ -2,7 +2,7 @@
 # the original module and its location, report an error with all three paths.
 # In particular, the "required as" path should be the original.
 # Verifies golang.org/issue/38220.
-! go list .
+! go mod download
 cmp stderr want
 
 -- go.mod --
index c2ee3ff97b80d82a4b029f041dc36ced3f840ec9..f2c77de806294fbfe2bb9c7ee89d373dc43819eb 100644 (file)
@@ -41,7 +41,8 @@ go list -m all
 
 # -mod=readonly should reject inconsistent go.mod files
 # (ones that would be rewritten).
-go mod edit -require rsc.io/sampler@v1.2.0
+go get -d rsc.io/sampler@v1.2.0
+go mod edit -require rsc.io/quote@v1.5.2
 cp go.mod go.mod.inconsistent
 ! go list
 stderr 'go: updates to go.mod needed, disabled by -mod=readonly'
diff --git a/src/cmd/go/testdata/script/mod_sum_readonly.txt b/src/cmd/go/testdata/script/mod_sum_readonly.txt
new file mode 100644 (file)
index 0000000..4d6e8aa
--- /dev/null
@@ -0,0 +1,87 @@
+# Test that go.sum does not get updated when -mod=readonly flag is set
+env GO111MODULE=on
+
+# When a sum is needed to load the build list, we get an error for the
+# specific module. The .mod file is not downloaded, and go.sum is not written.
+! go list -m all
+stderr '^go: rsc.io/quote@v1.5.2: missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
+! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
+! exists go.sum
+
+# If go.sum exists but contains hashes from an algorithm we don't know about,
+# we should see the same error.
+cp go.sum.h2only go.sum
+! go list -m all
+stderr '^go: rsc.io/quote@v1.5.2: missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
+! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
+cmp go.sum go.sum.h2only
+rm go.sum
+
+# If we replace a module, we should see a missing sum error for the replacement.
+cp go.mod go.mod.orig
+go mod edit -replace rsc.io/quote@v1.5.2=rsc.io/quote@v1.5.1
+! go list -m all
+stderr '^go: rsc.io/quote@v1.5.2 \(replaced by rsc.io/quote@v1.5.1\): missing go.sum entry; try ''go mod download rsc.io/quote'' to add it$'
+! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.1.mod
+! exists go.sum
+cp go.mod.orig go.mod
+
+# Control: when sums are present, loading the build list downloads .mod files.
+cp go.sum.buildlistonly go.sum
+go list -m all
+exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.mod
+
+
+# When a sum is needed to load a .mod file for a package outside the build list,
+# we get a generic missing import error.
+! go list example.com/doesnotexist
+stderr '^no required module provides package example.com/doesnotexist; try ''go get -d example.com/doesnotexist'' to add it$'
+
+# When a sum is needed to load a .zip file, we get a more specific error.
+# The .zip file is not downloaded.
+! go list rsc.io/quote
+stderr '^missing go.sum entry for module providing package rsc.io/quote$'
+! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
+
+# The error is attached to the package from the missing module. We can load
+# a package that imports it without that error.
+go list -e -deps -f '{{.ImportPath}}{{with .Error}} {{.Err}}{{end}}' .
+stdout '^m$'
+stdout '^rsc.io/quote missing go.sum entry for module providing package rsc.io/quote; try ''go mod tidy'' to add it$'
+! exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
+
+# go.sum should not have been written.
+cmp go.sum go.sum.buildlistonly
+
+# Control: when sums are present, 'go list' downloads .zip files.
+cp go.sum.tidy go.sum
+go list .
+exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
+
+-- go.mod --
+module m
+
+go 1.15
+
+require rsc.io/quote v1.5.2
+-- use.go --
+package use
+
+import _ "rsc.io/quote"
+-- go.sum.h2only --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h2:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h2:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h2:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- go.sum.buildlistonly --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- go.sum.tidy --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+rsc.io/testonly v1.0.0 h1:K/VWHdO+Jv7woUXG0GzVNx1czBXUt3Ib1deaMn+xk64=
+rsc.io/testonly v1.0.0/go.mod h1:OqmGbIFOcF+XrFReLOGZ6BhMM7uMBiQwZsyNmh74SzY=
index 68bbd9c274eebd528ee5b7e5fd0c14af8019a7f5..fb320a557abf8e02aa3f535caed3c2e0da8dfc4e 100644 (file)
@@ -17,7 +17,7 @@ stderr 'SECURITY ERROR\nThis download does NOT match the one reported by the che
 ! go get -d golang.org/x/text
 
 go mod edit -require rsc.io/quote@v1.5.2
-! go list all
+! go mod tidy
 stderr 'go: rsc.io/quote@v1.5.2: verifying go.mod: checksum mismatch'
 stderr 'SECURITY ERROR\n'
 
diff --git a/src/cmd/go/testdata/script/sum_readonly.txt b/src/cmd/go/testdata/script/sum_readonly.txt
deleted file mode 100644 (file)
index 8aa6116..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-# Test that go.sum does not get updated when -mod=readonly flag is set
-env GO111MODULE=on
-
-go get -d rsc.io/quote
-go mod tidy
-
-# go.sum != dirty; -mod=readonly
-go list -mod=readonly
-
-# dirty up go.sum by removing it.
-rm go.sum
-
-# go.sum == dirty; -mod=readonly
-! go list -mod=readonly
-
-stderr 'go: updates to go.sum needed, disabled by -mod=readonly'
-
--- go.mod --
-module m
-
--- main.go --
-
-package main
-
-import "rsc.io/quote"
-
-func main() {
-    println(quote.Hello())
-}
\ No newline at end of file