Skip to content

Commit 1f9dce7

Browse files
author
Bryan C. Mills
committed
cmd/go: download fewer dependencies in 'go mod download'
In modules that specify 'go 1.17' or higher, the go.mod file explicitly requires modules for all packages transitively imported by the main module. Users tend to use 'go mod download' to prepare for testing the main module itself, so we should only download those relevant modules. In 'go 1.16' and earlier modules, we continue to download all modules in the module graph (because we cannot in general tell which ones are relevant without loading the full package import graph). 'go mod download all' continues to download every module in 'go list all', as it did before. Fixes #44435 Change-Id: I3f286c0e2549d6688b3832ff116e6cd77a19401c Reviewed-on: https://go-review.googlesource.com/c/go/+/357310 Trust: Bryan C. Mills <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Matloob <[email protected]>
1 parent 978e39e commit 1f9dce7

File tree

6 files changed

+113
-20
lines changed

6 files changed

+113
-20
lines changed

doc/go1.18.html

+13
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ <h3 id="go-command">Go command</h3>
9090
package.
9191
</p>
9292

93+
<p><!-- https://golang.org/issue/44435 -->
94+
If the main module's <code>go.mod</code> file
95+
specifies <a href="/ref/mod#go-mod-file-go"><code>go</code> <code>1.17</code></a>
96+
or higher, <code>go</code> <code>mod</code> <code>download</code> without
97+
arguments now downloads source code for only the modules
98+
explicitly <a href="/ref/mod#go-mod-file-require">required</a> in the main
99+
module's <code>go.mod</code> file. (In a <code>go</code> <code>1.17</code> or
100+
higher module, that set already includes all dependencies needed to build the
101+
packages and tests in the main module.)
102+
To also download source code for transitive dependencies, use
103+
<code>go</code> <code>mod</code> <code>download</code> <code>all</code>.
104+
</p>
105+
93106
<p>
94107
TODO: complete this section, or delete if not needed
95108
</p>

src/cmd/go/alldocs.go

+5-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/modcmd/download.go

+47-14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"cmd/go/internal/modload"
1717

1818
"golang.org/x/mod/module"
19+
"golang.org/x/mod/semver"
1920
)
2021

2122
var cmdDownload = &base.Command{
@@ -24,8 +25,11 @@ var cmdDownload = &base.Command{
2425
Long: `
2526
Download downloads the named modules, which can be module patterns selecting
2627
dependencies of the main module or module queries of the form path@version.
27-
With no arguments, download applies to all dependencies of the main module
28-
(equivalent to 'go mod download all').
28+
29+
With no arguments, download applies to the modules needed to build and test
30+
the packages in the main module: the modules explicitly required by the main
31+
module if it is at 'go 1.17' or higher, or all transitively-required modules
32+
if at 'go 1.16' or lower.
2933
3034
The go command will automatically download modules as needed during ordinary
3135
execution. The "go mod download" command is useful mainly for pre-filling
@@ -87,13 +91,8 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
8791
// Check whether modules are enabled and whether we're in a module.
8892
modload.ForceUseModules = true
8993
modload.ExplicitWriteGoMod = true
90-
if !modload.HasModRoot() && len(args) == 0 {
91-
base.Fatalf("go: no modules specified (see 'go help mod download')")
92-
}
9394
haveExplicitArgs := len(args) > 0
94-
if !haveExplicitArgs {
95-
args = []string{"all"}
96-
}
95+
9796
if modload.HasModRoot() {
9897
modload.LoadModFile(ctx) // to fill MainModules
9998

@@ -102,14 +101,48 @@ func runDownload(ctx context.Context, cmd *base.Command, args []string) {
102101
}
103102
mainModule := modload.MainModules.Versions()[0]
104103

105-
targetAtUpgrade := mainModule.Path + "@upgrade"
106-
targetAtPatch := mainModule.Path + "@patch"
107-
for _, arg := range args {
108-
switch arg {
109-
case mainModule.Path, targetAtUpgrade, targetAtPatch:
110-
os.Stderr.WriteString("go: skipping download of " + arg + " that resolves to the main module\n")
104+
if haveExplicitArgs {
105+
targetAtUpgrade := mainModule.Path + "@upgrade"
106+
targetAtPatch := mainModule.Path + "@patch"
107+
for _, arg := range args {
108+
switch arg {
109+
case mainModule.Path, targetAtUpgrade, targetAtPatch:
110+
os.Stderr.WriteString("go: skipping download of " + arg + " that resolves to the main module\n")
111+
}
111112
}
113+
} else {
114+
modFile := modload.MainModules.ModFile(mainModule)
115+
if modFile.Go == nil || semver.Compare("v"+modFile.Go.Version, modload.ExplicitIndirectVersionV) < 0 {
116+
if len(modFile.Require) > 0 {
117+
args = []string{"all"}
118+
}
119+
} else {
120+
// As of Go 1.17, the go.mod file explicitly requires every module
121+
// that provides any package imported by the main module.
122+
// 'go mod download' is typically run before testing packages in the
123+
// main module, so by default we shouldn't download the others
124+
// (which are presumed irrelevant to the packages in the main module).
125+
// See https://golang.org/issue/44435.
126+
//
127+
// However, we also need to load the full module graph, to ensure that
128+
// we have downloaded enough of the module graph to run 'go list all',
129+
// 'go mod graph', and similar commands.
130+
_ = modload.LoadModGraph(ctx, "")
131+
132+
for _, m := range modFile.Require {
133+
args = append(args, m.Mod.Path)
134+
}
135+
}
136+
}
137+
}
138+
139+
if len(args) == 0 {
140+
if modload.HasModRoot() {
141+
os.Stderr.WriteString("go: no module dependencies to download\n")
142+
} else {
143+
base.Errorf("go: no modules specified (see 'go help mod download')")
112144
}
145+
base.Exit()
113146
}
114147

115148
downloadModule := func(m *moduleJSON) {

src/cmd/go/internal/modload/buildlist.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ func (rs *Requirements) IsDirect(path string) bool {
236236
// A ModuleGraph represents the complete graph of module dependencies
237237
// of a main module.
238238
//
239-
// If the main module is lazily loaded, the graph does not include
239+
// If the main module supports module graph pruning, the graph does not include
240240
// transitive dependencies of non-root (implicit) dependencies.
241241
type ModuleGraph struct {
242242
g *mvs.Graph

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ const (
3333
// tests outside of the main module.
3434
narrowAllVersionV = "v1.16"
3535

36-
// explicitIndirectVersionV is the Go version (plus leading "v") at which a
36+
// ExplicitIndirectVersionV is the Go version (plus leading "v") at which a
3737
// module's go.mod file is expected to list explicit requirements on every
3838
// module that provides any package transitively imported by that module.
3939
//
4040
// Other indirect dependencies of such a module can be safely pruned out of
4141
// the module graph; see https://golang.org/ref/mod#graph-pruning.
42-
explicitIndirectVersionV = "v1.17"
42+
ExplicitIndirectVersionV = "v1.17"
4343

4444
// separateIndirectVersionV is the Go version (plus leading "v") at which
4545
// "// indirect" dependencies are added in a block separate from the direct
@@ -123,7 +123,7 @@ const (
123123
)
124124

125125
func pruningForGoVersion(goVersion string) modPruning {
126-
if semver.Compare("v"+goVersion, explicitIndirectVersionV) < 0 {
126+
if semver.Compare("v"+goVersion, ExplicitIndirectVersionV) < 0 {
127127
// The go.mod file does not duplicate relevant information about transitive
128128
// dependencies, so they cannot be pruned out.
129129
return unpruned

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

+44
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,50 @@ rm go.sum
128128
go mod download all
129129
cmp go.mod.update go.mod
130130
grep '^rsc.io/sampler v1.3.0 ' go.sum
131+
132+
# https://golang.org/issue/44435: At go 1.17 or higher, 'go mod download'
133+
# (without arguments) should only download the modules explicitly required in
134+
# the go.mod file, not (presumed-irrelevant) transitive dependencies.
135+
#
136+
# (If the go.mod file is inconsistent, the version downloaded should be the
137+
# selected version from the broader graph, but the go.mod file will also be
138+
# updated to list the correct versions. If at some point we change 'go mod
139+
# download' to stop updating for consistency, then it should fail if the
140+
# requirements are inconsistent.)
141+
142+
rm go.sum
143+
cp go.mod.orig go.mod
144+
go mod edit -go=1.17
145+
cp go.mod.update go.mod.go117
146+
go mod edit -go=1.17 go.mod.go117
147+
148+
go clean -modcache
149+
go mod download
150+
cmp go.mod go.mod.go117
151+
152+
go list -e -m all
153+
stdout '^rsc.io/quote v1.5.2$'
154+
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
155+
stdout '^rsc.io/sampler v1.3.0$'
156+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/sampler/@v/v1.2.1.zip
157+
exists $GOPATH/pkg/mod/cache/download/rsc.io/sampler/@v/v1.3.0.zip
158+
stdout '^golang\.org/x/text v0.0.0-20170915032832-14c0d48ead0c$'
159+
! exists $GOPATH/pkg/mod/cache/download/golang.org/x/text/@v/v0.0.0-20170915032832-14c0d48ead0c.zip
160+
cmp go.mod go.mod.go117
161+
162+
# However, 'go mod download all' continues to download the selected version
163+
# of every module reported by 'go list -m all'.
164+
165+
cp go.mod.orig go.mod
166+
go mod edit -go=1.17
167+
go clean -modcache
168+
go mod download all
169+
exists $GOPATH/pkg/mod/cache/download/rsc.io/quote/@v/v1.5.2.zip
170+
! exists $GOPATH/pkg/mod/cache/download/rsc.io/sampler/@v/v1.2.1.zip
171+
exists $GOPATH/pkg/mod/cache/download/rsc.io/sampler/@v/v1.3.0.zip
172+
exists $GOPATH/pkg/mod/cache/download/golang.org/x/text/@v/v0.0.0-20170915032832-14c0d48ead0c.zip
173+
cmp go.mod go.mod.go117
174+
131175
cd ..
132176

133177
# allow go mod download without go.mod

0 commit comments

Comments
 (0)