<h3 id="go-command">Go command</h3>
-<h4 id="modules">Modules</h4>
+<h4 id="lazy-loading">Lazy module loading</h4>
+
+<p><!-- golang.org/issue/36460 -->
+ If a module specifies <code>go</code> <code>1.17</code> or higher in its
+ <code>go.mod</code> file, its transitive requirements are now loaded lazily,
+ avoding the need to download or read <code>go.mod</code> files for
+ otherwise-irrelevant dependencies. To support lazy loading, in Go 1.17 modules
+ the <code>go</code> command maintains <em>explicit</em> requirements in
+ the <code>go.mod</code> file for every dependency that provides any package
+ transitively imported by any package or test within the module.
+ See <a href="https://golang.org/design/36460-lazy-module-loading">the design
+ document</a> for more detail.
+ <!-- TODO(bcmills): replace the design-doc link with proper documentation. -->
+</p>
+
+<h4 id="module-deprecation-comments">Module deprecation comments</h4>
<p><!-- golang.org/issue/40357 -->
Module authors may deprecate a module by adding a
require (
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5
+ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
golang.org/x/arch v0.0.0-20210308155006-05f8f0431f72
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/mod v0.4.3-0.20210409134425-858fdbee9c24
+ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d
golang.org/x/tools v0.1.1-0.20210422170518-f946a157eefe
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
// go117EnableLazyLoading toggles whether lazy-loading code paths should be
// active. It will be removed once the lazy loading implementation is stable
// and well-tested.
- go117EnableLazyLoading = false
+ go117EnableLazyLoading = true
// go1117LazyTODO is a constant that exists only until lazy loading is
// implemented. Its use indicates a condition that will need to change if the
rm vendor
-# Convert all modules to go 1.16 to enable lazy loading.
-go mod edit -go=1.16 a/go.mod
-go mod edit -go=1.16 b/go.mod
-go mod edit -go=1.16 c/go.mod
-go mod edit -go=1.16 d/go.mod
-go mod edit -go=1.16 q/go.mod
-go mod edit -go=1.16 r/go.mod
-go mod edit -go=1.16 s/go.mod
-go mod edit -go=1.16 t/go.mod
-go mod edit -go=1.16 u/go.mod
-go mod edit -go=1.16 w/go.mod
-go mod edit -go=1.16 x/go.mod
-go mod edit -go=1.16
+# Convert all modules to go 1.17 to enable lazy loading.
+go mod edit -go=1.17 a/go.mod
+go mod edit -go=1.17 b/go.mod
+go mod edit -go=1.17 c/go.mod
+go mod edit -go=1.17 d/go.mod
+go mod edit -go=1.17 q/go.mod
+go mod edit -go=1.17 r/go.mod
+go mod edit -go=1.17 s/go.mod
+go mod edit -go=1.17 t/go.mod
+go mod edit -go=1.17 u/go.mod
+go mod edit -go=1.17 w/go.mod
+go mod edit -go=1.17 x/go.mod
+go mod edit -go=1.17
+cp go.mod go.mod.orig
+go mod tidy
+cmp go.mod go.mod.orig
# With lazy loading, 'go list all' with neither -mod=vendor nor -test should
# match -mod=vendor without -test in 1.15.
stdout '^example.com/u.test$'
stdout '^example.com/u_test \[example.com/u.test\]$'
+# 'go list -m all' should cover all of the modules providing packages in
+# 'go list -test -deps all', but should exclude modules d and x,
+# which are not relevant to the main module and are outside of the
+# lazy-loading horizon.
-# TODO(#36460):
-# 'go list -m all' should exactly cover the packages in 'go list -test all'.
+go list -m -f $MODFMT all
+stdout -count=10 '^.'
+stdout '^example.com/a$'
+stdout '^example.com/b$'
+stdout '^example.com/c$'
+! stdout '^example.com/d$'
+stdout '^example.com/main$'
+stdout '^example.com/q$'
+stdout '^example.com/r$'
+stdout '^example.com/s$'
+stdout '^example.com/t$'
+stdout '^example.com/u$'
+stdout '^example.com/w$'
+! stdout '^example.com/x$'
-- go.mod --
module example.com/main
+// Note: this go.mod file initially specifies go 1.15,
+// but includes some redundant roots so that it
+// also already obeys the 1.17 lazy loading invariants.
go 1.15
require (
example.com/a v0.1.0
example.com/b v0.1.0
example.com/q v0.1.0
+ example.com/r v0.1.0 // indirect
example.com/t v0.1.0
+ example.com/u v0.1.0 // indirect
)
replace (
-- go.mod --
module use
-go 1.17
+go 1.16
require rsc.io/quote v1.5.2
-- go.sum.tidy --
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: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
+stderr '^rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
! go install -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0
-stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
+stderr '^rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
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
# This test illustrates the interaction between lazy loading and downgrading in
-# 'go get.
+# 'go get'.
# The package import graph used in this test looks like:
#
# outside of the deepening scan should not affect the downgrade.
cp go.mod.orig go.mod
-go mod edit -go=1.16
+go mod edit -go=1.17
go list -m all
stdout '^example.com/a v0.1.0 '
stdout '^example.com/b v0.2.0 '
stdout '^example.com/c v0.1.0 '
+# At this point, b.2 is still an explicit root, so its dependency on c
+# is still tracked, and it will still be downgraded away if we remove c.
+# ('go get' never makes a root into a non-root. Only 'go mod tidy' does that.)
+
go get -d example.com/c@none
go list -m all
-! stdout '^example.com/a ' # TODO(#36460): example.com/a v0.1.0
-! stdout '^example.com/b ' # TODO(#36460): example.com/b v0.1.0
+! stdout '^example.com/a '
+! stdout '^example.com/b '
! stdout '^example.com/c '
+
+# This time, we drop the explicit 'b' root by downgrading it to v0.1.0
+# (the version required by a.1) and running 'go mod tidy'.
+# It is still selected at v0.1.0 (as a dependency of a),
+# but its dependency on c is now pruned from the module graph, so it doesn't
+# result in any downgrades to b or a if we run 'go get c@none'.
+
+cp go.mod.orig go.mod
+go mod edit -go=1.17
+
+go list -m all
+stdout '^example.com/a v0.1.0 '
+stdout '^example.com/b v0.3.0 '
+stdout '^example.com/c v0.2.0 '
+
+go get -d example.com/c@v0.1.0 example.com/b@v0.1.0
+go list -m all
+stdout '^example.com/a v0.1.0 '
+stdout '^example.com/b v0.1.0 '
+stdout '^example.com/c v0.1.0 '
+
+go mod tidy
+go list -m all
+stdout '^example.com/a v0.1.0 '
+stdout '^example.com/b v0.1.0 '
+! stdout '^example.com/c '
+
+go get -d example.com/c@none
+go list -m all
+stdout '^example.com/a v0.1.0'
+stdout '^example.com/b v0.1.0'
+! stdout '^example.com/c '
+
+
-- go.mod --
module example.com/lazy
-- a/go.mod --
module example.com/a
-go 1.15
+go 1.17
require example.com/b v0.1.0
-- a/a.go --
-- b1/go.mod --
module example.com/b
-go 1.15
+go 1.17
require example.com/c v0.1.0
-- b1/b.go --
-- b2/go.mod --
module example.com/b
-go 1.15
+go 1.17
require example.com/c v0.1.0
-- b2/b.go --
-- b3/go.mod --
module example.com/b
-go 1.15
+go 1.17
require example.com/c v0.2.0
-- b3/b.go --
-- c/go.mod --
module example.com/c
-go 1.15
+go 1.17
-- c/c.go --
package c
cp m.go.orig m.go
cp go.mod.orig go.mod
-go mod edit -go=1.16
-go mod edit -go=1.16 go.mod.new
+go mod edit -go=1.17
+go mod edit -go=1.17 go.mod.new
cp go.mod go.mod.orig
go mod tidy
go list -m all
stdout '^a v0.1.0 '
stdout '^b v0.1.0 '
-stdout '^c v0.1.0 ' # TODO(#36460): This should be pruned out.
+! stdout '^c '
-# After adding a new import of b/y,
-# the import of c from b/y should again resolve to the version required by b.
+# After adding a new direct import of b/y,
+# the existing verison of b should be promoted to a root,
+# bringing the version of c required by b into the build list.
cp m.go.new m.go
go mod tidy
-cmp go.mod.new go.mod
+cmp go.mod.lazy go.mod
go list -m all
stdout '^a v0.1.0 '
b v0.1.0
)
+replace (
+ a v0.1.0 => ./a1
+ b v0.1.0 => ./b1
+ c v0.1.0 => ./c1
+ c v0.2.0 => ./c2
+)
+-- go.mod.lazy --
+module m
+
+go 1.17
+
+require (
+ a v0.1.0
+ b v0.1.0
+ c v0.1.0 // indirect
+)
+
replace (
a v0.1.0 => ./a1
b v0.1.0 => ./b1
-- a1/go.mod --
module a
-go 1.16
+go 1.17
require b v0.1.0
-- a1/a.go --
-- b1/go.mod --
module b
-go 1.16
+go 1.17
require c v0.1.0
-- b1/x/x.go --
-- c1/go.mod --
module c
-go 1.16
+go 1.17
-- c1/c.go --
package c
#
# lazy ---- a/x ---- b
# \
-# ---- a/y ---- c
+# ---- a/y (new) ---- c
#
# Where a/x and x/y are disjoint packages, but both contained in module a.
#
stdout '^example.com/c v0.1.0' # not v0.2.0 as would be resolved by 'latest'
cmp go.mod go.mod.old
-# TODO(#36460):
+# Now, we repeat the test with a lazy main module.
cp lazy.go.old lazy.go
-cp go.mod.old go.mod
-go mod edit -go=1.16
+cp go.mod.117 go.mod
+
+# Before adding a new import, the go.mod file should
+# enumerate modules for all packages already imported.
+go list all
+cmp go.mod go.mod.117
# When a new import is found, we should perform a deepening scan of the existing
# dependencies and add a requirement on the version required by those
# dependencies — not re-resolve 'latest'.
+cp lazy.go.new lazy.go
+
+! go list all
+stderr '^go: updates to go.mod needed; to update it:\n\tgo mod tidy$'
+
+go mod tidy
+go list all
+go list -m all
+stdout '^example.com/c v0.1.0' # not v0.2.0 as would be resolved by 'latest'
+
+cmp go.mod go.mod.new
-- go.mod --
require example.com/a v0.1.0
+replace (
+ example.com/a v0.1.0 => ./a
+ example.com/b v0.1.0 => ./b
+ example.com/c v0.1.0 => ./c1
+ example.com/c v0.2.0 => ./c2
+)
+-- go.mod.117 --
+module example.com/lazy
+
+go 1.17
+
+require (
+ example.com/a v0.1.0
+ example.com/b v0.1.0 // indirect
+)
+
+replace (
+ example.com/a v0.1.0 => ./a
+ example.com/b v0.1.0 => ./b
+ example.com/c v0.1.0 => ./c1
+ example.com/c v0.2.0 => ./c2
+)
+-- go.mod.new --
+module example.com/lazy
+
+go 1.17
+
+require (
+ example.com/a v0.1.0
+ example.com/b v0.1.0 // indirect
+ example.com/c v0.1.0 // indirect
+)
+
replace (
example.com/a v0.1.0 => ./a
example.com/b v0.1.0 => ./b
# but the irrelevant dependency on c v0.2.0 should be pruned out,
# leaving only the relevant dependency on c v0.1.0.
-go mod edit -go=1.16
+go mod edit -go=1.17
go list -m c
-stdout '^c v0.2.0' # TODO(#36460): v0.1.0
+stdout '^c v0.1.0'
[!short] go test -v x
-[!short] stdout ' c v0.2.0$' # TODO(#36460): v0.1.0
+[!short] stdout ' c v0.1.0$'
-- m.go --
package m
-- a1/go.mod --
module a
-go 1.16
+go 1.17
require b v0.1.0
-- a1/a.go --
-- b1/go.mod --
module b
-go 1.16
+go 1.17
require c v0.2.0
-- b1/b.go --
-- c1/go.mod --
module c
-go 1.16
+go 1.17
-- c1/c.go --
package c
-- c2/go.mod --
module c
-go 1.16
+go 1.17
-- c2/c.go --
package c
-- x1/go.mod --
module x
-go 1.16
+go 1.17
require c v0.1.0
-- x1/x.go --
go mod tidy
cmp go.mod go.mod.old
+
# In Go 1.15 mode, 'go list -m all' includes modules needed by the
# transitive closure of tests of dependencies of tests of dependencies of ….
go list -m all
-stdout 'example.com/b v0.1.0'
-stdout 'example.com/c v0.1.0'
+stdout '^example.com/b v0.1.0 '
+stdout '^example.com/c v0.1.0 '
cmp go.mod go.mod.old
# 'go test' (or equivalent) of any such dependency, no matter how remote, does
stdout example.com/b
! stdout example.com/c
-[!short] go test -c example.com/a
+[!short] go test -c -o $devnull example.com/a
[!short] cmp go.mod go.mod.old
go list -test -deps example.com/b
stdout example.com/c
-[!short] go test -c example.com/b
+[!short] go test -c -o $devnull example.com/b
[!short] cmp go.mod go.mod.old
-# TODO(#36460):
+go mod edit -go=1.17 a/go.mod
+go mod edit -go=1.17 b1/go.mod
+go mod edit -go=1.17 b2/go.mod
+go mod edit -go=1.17 c1/go.mod
+go mod edit -go=1.17 c2/go.mod
+go mod edit -go=1.17
+
-# After changing to 'go 1.16` uniformly, 'go list -m all' should prune out
+# After changing to 'go 1.17` uniformly, 'go list -m all' should prune out
# example.com/c, because it is not imported by any package (or test of a package)
# transitively imported by the main module.
#
# version of its module.
# However, if we reach c by running successive tests starting from the main
-# module, we should end up with exactly the version require by c, with an update
+# module, we should end up with exactly the version required by b, with an update
# to the go.mod file as soon as we test a test dependency that is not itself in
# "all".
+cp go.mod go.mod.117
+go mod tidy
+cmp go.mod go.mod.117
+
+go list -m all
+stdout '^example.com/b v0.1.0 '
+! stdout '^example.com/c '
+
+# 'go test' of a package (transitively) imported by the main module
+# should work without changes to the go.mod file.
+
+go list -test -deps example.com/a
+stdout example.com/b
+! stdout example.com/c
+
+[!short] go test -c -o $devnull example.com/a
+
+# However, 'go test' of a package that is itself a dependency should require an
+# update to the go.mod file.
+! go list -test -deps example.com/b
+
+ # TODO(#36460): The hint here is wrong. We should suggest
+ # 'go get -t example.com/b@v0.1.0' instead of 'go mod tidy'.
+stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$'
+
+[!short] ! go test -c -o $devnull example.com/b
+[!short] stderr '^go: updates to go\.mod needed; to update it:\n\tgo mod tidy$'
+
+go get -t example.com/b@v0.1.0
+go list -test -deps example.com/b
+stdout example.com/c
+
+[!short] go test -c -o $devnull example.com/b
+
+# The update should bring the version required by b, not the latest version of c.
+
+go list -m example.com/c
+stdout '^example.com/c v0.1.0 '
+
+cmp go.mod go.mod.b
+
+
+# We should reach the same state if we arrive at it via `go test -mod=mod`.
+
+cp go.mod.117 go.mod
+
+[short] go list -mod=mod -test -deps example.com/a
+[!short] go test -mod=mod -c -o $devnull example.com/a
+
+[short] go list -mod=mod -test -deps example.com/b
+[!short] go test -mod=mod -c -o $devnull example.com/b
+
+cmp go.mod go.mod.b
+
+
+
-- go.mod --
module example.com/lazy
require example.com/a v0.1.0
+replace (
+ example.com/a v0.1.0 => ./a
+ example.com/b v0.1.0 => ./b1
+ example.com/b v0.2.0 => ./b2
+ example.com/c v0.1.0 => ./c1
+ example.com/c v0.2.0 => ./c2
+)
+-- go.mod.b --
+module example.com/lazy
+
+go 1.17
+
+require (
+ example.com/a v0.1.0
+ example.com/b v0.1.0 // indirect
+)
+
replace (
example.com/a v0.1.0 => ./a
example.com/b v0.1.0 => ./b1
--- /dev/null
+# https://golang.org/issue/41297: 'go list -m' should not require go.sum with
+# -versions or when all args are version queries.
+
+go mod init m
+go mod edit -require=rsc.io/quote@v1.5.1
+
+# 'go list' currently loads the whole build list, even when listing only
+# non-dependencies.
+#
+# TODO(#41297): Thes should not be errors.
+
+! go list -m -mod=readonly rsc.io/quote@latest
+stderr '^go list -m: rsc\.io/quote@v1\.5\.1: missing go\.sum entry; to add it:\n\tgo mod download rsc\.io/quote$'
+
+! go list -m -mod=readonly -versions rsc.io/quote
+stderr '^go list -m: rsc\.io/quote@v1\.5\.1: missing go\.sum entry; to add it:\n\tgo mod download rsc\.io/quote$'
+
+# Incidentally fetching the required version of a module records its checksum,
+# just because it happens to be in the build list, and recording the checksum
+# triggers an error under -mod=readonly.
+#
+# TODO(#41297): This should not be an error.
+! go list -m -mod=readonly rsc.io/quote@<v1.5.2
+stderr '^go list -m: rsc\.io/quote@v1\.5\.1: missing go\.sum entry; to add it:\n\tgo mod download rsc\.io/quote$'
+! stderr '^go: updates to go.sum needed, disabled by -mod=readonly$'
+
+# Attempting to list the versions of a module that is not a root dependency
+# causes the build list to be resolved (so that the selected version can *also*
+# be identified, even though it is not relevant to this particular output).
+# That, in turn, causes us to need checksums for the go.sum files for the
+# modules in the module graph.
+#
+# TODO(#41297): This should not be an error either.
+! go list -m -mod=readonly -versions rsc.io/sampler
+stderr '^go list -m: rsc\.io/quote@v1\.5\.1: missing go\.sum entry; to add it:\n\tgo mod download rsc\.io/quote$'
go mod init tmp
go mod edit -require=rsc.io/fortune@v1.0.0
! go run -mod=readonly $GOPATH/pkg/mod/rsc.io/fortune@v1.0.0
-stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
+stderr '^rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
! go run -mod=readonly ../../pkg/mod/rsc.io/fortune@v1.0.0
-stderr '^go: rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
+stderr '^rsc.io/fortune@v1.0.0: missing go.sum entry; to add it:\n\tgo mod download rsc.io/fortune$'
cd ..
rm tmp
cmp go.mod go.mod.postget
+# The 'tidy' logic for a lazy main module is somewhat different from that for an
+# eager main module, but the overall behavior is the same.
-# TODO(#36460): Repeat this test with a lazy main module.
+cp go.mod.orig go.mod
+go mod edit -go=1.17 go.mod
+go mod edit -go=1.17 go.mod.tidye
+go mod edit -go=1.17 go.mod.postget
+
+go mod tidy -e
+cmp go.mod go.mod.tidye
+stderr '^go: found example\.net/y in example\.net/y v0.2.0$'
+stderr '^example\.net/m imports\n\texample\.net/x: package example\.net/x provided by example\.net/x at latest version v0\.1\.0 but not at required version v0\.2\.0-pre$'
+
+go get -d example.net/x@v0.1.0 example.net/y@v0.1.0
+go mod tidy
+cmp go.mod go.mod.postget
-- go.mod --
cmp go.mod go.mod.postget
-# TODO(#36460): Repeat this test with a lazy main module.
+# The 'tidy' logic for a lazy main module requires more iterations to converge,
+# because it is willing to drop dependencies on non-root modules that do not
+# otherwise provide imported packages.
+#
+# On the first iteration, it adds x.1 as a root, which upgrades z and w,
+# dropping w.1's requirement on y. w.1 was initially a root, so the upgraded
+# w.2-pre is retained as a root.
+#
+# On the second iteration, it adds y.1 as a root, which upgrades w and x,
+# dropping x.1's requirement on z. x.1 was added as a root in the previous step,
+# so the upgraded x.2-pre is retained as a root.
+#
+# On the third iteration, it adds z.1 as a root, which upgrades x and y.
+# x and y were already roots (from the previous steps), so their upgraded versions
+# are retained (not dropped) and the iteration stops.
+#
+# At that point, we have z.1 as a root providing package z,
+# and w, x, and y have all been upgraded to no longer provide any packages.
+# So only z is retained as a new root.
+#
+# (From the above, we can see that in a lazy module we still cycle through the
+# same possible root states, but in a different order from the eager case.)
+#
+# TODO(bcmills): if we retained the upgrades on w, x, and y (since they are
+# lexical prefixes for unresolved packages w, x, and y, respectively), then 'go
+# mod tidy -e' itself would become stable and no longer cycle through states.
+
+cp go.mod.orig go.mod
+go mod edit -go=1.17 go.mod
+cp go.mod go.mod.117
+go mod edit -go=1.17 go.mod.tidye1
+go mod edit -go=1.17 go.mod.tidye2
+go mod edit -go=1.17 go.mod.tidye3
+go mod edit -go=1.17 go.mod.postget
+
+go list -m all
+
+go mod tidy -e
+cmp go.mod go.mod.tidye3
+
+go mod tidy -e
+cmp go.mod go.mod.tidye2
+
+go mod tidy -e
+cmp go.mod go.mod.tidye1
+
+go mod tidy -e
+cmp go.mod go.mod.117
+
+
+# As in the eager case, for the lazy module the fully-upgraded dependency graph
+# becomes empty, and the empty graph is stable.
+
+go get -d example.net/w@v0.2.0-pre example.net/x@v0.2.0-pre example.net/y@v0.2.0-pre example.net/z@v0.2.0-pre
+go mod tidy -e
+cmp go.mod go.mod.postget
+go mod tidy -e
+cmp go.mod go.mod.postget
-- m.go --
github.com/google/pprof/third_party/d3flamegraph
github.com/google/pprof/third_party/svgpan
# github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639
+## explicit
github.com/ianlancetaylor/demangle
# golang.org/x/arch v0.0.0-20210308155006-05f8f0431f72
## explicit
golang.org/x/mod/sumdb/tlog
golang.org/x/mod/zip
# golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
+## explicit
golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/tools/internal/analysisinternal
golang.org/x/tools/internal/lsp/fuzzy
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
+## explicit
golang.org/x/xerrors
golang.org/x/xerrors/internal