1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package modcmd
6
7import (
8	"context"
9	"encoding/json"
10	"errors"
11	"os"
12	"runtime"
13	"sync"
14
15	"cmd/go/internal/base"
16	"cmd/go/internal/cfg"
17	"cmd/go/internal/gover"
18	"cmd/go/internal/modfetch"
19	"cmd/go/internal/modfetch/codehost"
20	"cmd/go/internal/modload"
21	"cmd/go/internal/toolchain"
22
23	"golang.org/x/mod/module"
24)
25
26var cmdDownload = &base.Command{
27	UsageLine: "go mod download [-x] [-json] [-reuse=old.json] [modules]",
28	Short:     "download modules to local cache",
29	Long: `
30Download downloads the named modules, which can be module patterns selecting
31dependencies of the main module or module queries of the form path@version.
32
33With no arguments, download applies to the modules needed to build and test
34the packages in the main module: the modules explicitly required by the main
35module if it is at 'go 1.17' or higher, or all transitively-required modules
36if at 'go 1.16' or lower.
37
38The go command will automatically download modules as needed during ordinary
39execution. The "go mod download" command is useful mainly for pre-filling
40the local cache or to compute the answers for a Go module proxy.
41
42By default, download writes nothing to standard output. It may print progress
43messages and errors to standard error.
44
45The -json flag causes download to print a sequence of JSON objects
46to standard output, describing each downloaded module (or failure),
47corresponding to this Go struct:
48
49    type Module struct {
50        Path     string // module path
51        Query    string // version query corresponding to this version
52        Version  string // module version
53        Error    string // error loading module
54        Info     string // absolute path to cached .info file
55        GoMod    string // absolute path to cached .mod file
56        Zip      string // absolute path to cached .zip file
57        Dir      string // absolute path to cached source root directory
58        Sum      string // checksum for path, version (as in go.sum)
59        GoModSum string // checksum for go.mod (as in go.sum)
60        Origin   any    // provenance of module
61        Reuse    bool   // reuse of old module info is safe
62    }
63
64The -reuse flag accepts the name of file containing the JSON output of a
65previous 'go mod download -json' invocation. The go command may use this
66file to determine that a module is unchanged since the previous invocation
67and avoid redownloading it. Modules that are not redownloaded will be marked
68in the new output by setting the Reuse field to true. Normally the module
69cache provides this kind of reuse automatically; the -reuse flag can be
70useful on systems that do not preserve the module cache.
71
72The -x flag causes download to print the commands download executes.
73
74See https://golang.org/ref/mod#go-mod-download for more about 'go mod download'.
75
76See https://golang.org/ref/mod#version-queries for more about version queries.
77	`,
78}
79
80var (
81	downloadJSON  = cmdDownload.Flag.Bool("json", false, "")
82	downloadReuse = cmdDownload.Flag.String("reuse", "", "")
83)
84
85func init() {
86	cmdDownload.Run = runDownload // break init cycle
87
88	// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
89	cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
90	base.AddChdirFlag(&cmdDownload.Flag)
91	base.AddModCommonFlags(&cmdDownload.Flag)
92}
93
94// A ModuleJSON describes the result of go mod download.
95type ModuleJSON struct {
96	Path     string `json:",omitempty"`
97	Version  string `json:",omitempty"`
98	Query    string `json:",omitempty"`
99	Error    string `json:",omitempty"`
100	Info     string `json:",omitempty"`
101	GoMod    string `json:",omitempty"`
102	Zip      string `json:",omitempty"`
103	Dir      string `json:",omitempty"`
104	Sum      string `json:",omitempty"`
105	GoModSum string `json:",omitempty"`
106
107	Origin *codehost.Origin `json:",omitempty"`
108	Reuse  bool             `json:",omitempty"`
109}
110
111func runDownload(ctx context.Context, cmd *base.Command, args []string) {
112	modload.InitWorkfile()
113
114	// Check whether modules are enabled and whether we're in a module.
115	modload.ForceUseModules = true
116	modload.ExplicitWriteGoMod = true
117	haveExplicitArgs := len(args) > 0
118
119	if modload.HasModRoot() || modload.WorkFilePath() != "" {
120		modload.LoadModFile(ctx) // to fill MainModules
121
122		if haveExplicitArgs {
123			for _, mainModule := range modload.MainModules.Versions() {
124				targetAtUpgrade := mainModule.Path + "@upgrade"
125				targetAtPatch := mainModule.Path + "@patch"
126				for _, arg := range args {
127					switch arg {
128					case mainModule.Path, targetAtUpgrade, targetAtPatch:
129						os.Stderr.WriteString("go: skipping download of " + arg + " that resolves to the main module\n")
130					}
131				}
132			}
133		} else if modload.WorkFilePath() != "" {
134			// TODO(#44435): Think about what the correct query is to download the
135			// right set of modules. Also see code review comment at
136			// https://go-review.googlesource.com/c/go/+/359794/comments/ce946a80_6cf53992.
137			args = []string{"all"}
138		} else {
139			mainModule := modload.MainModules.Versions()[0]
140			modFile := modload.MainModules.ModFile(mainModule)
141			if modFile.Go == nil || gover.Compare(modFile.Go.Version, gover.ExplicitIndirectVersion) < 0 {
142				if len(modFile.Require) > 0 {
143					args = []string{"all"}
144				}
145			} else {
146				// As of Go 1.17, the go.mod file explicitly requires every module
147				// that provides any package imported by the main module.
148				// 'go mod download' is typically run before testing packages in the
149				// main module, so by default we shouldn't download the others
150				// (which are presumed irrelevant to the packages in the main module).
151				// See https://golang.org/issue/44435.
152				//
153				// However, we also need to load the full module graph, to ensure that
154				// we have downloaded enough of the module graph to run 'go list all',
155				// 'go mod graph', and similar commands.
156				_, err := modload.LoadModGraph(ctx, "")
157				if err != nil {
158					// TODO(#64008): call base.Fatalf instead of toolchain.SwitchOrFatal
159					// here, since we can only reach this point with an outdated toolchain
160					// if the go.mod file is inconsistent.
161					toolchain.SwitchOrFatal(ctx, err)
162				}
163
164				for _, m := range modFile.Require {
165					args = append(args, m.Mod.Path)
166				}
167			}
168		}
169	}
170
171	if len(args) == 0 {
172		if modload.HasModRoot() {
173			os.Stderr.WriteString("go: no module dependencies to download\n")
174		} else {
175			base.Errorf("go: no modules specified (see 'go help mod download')")
176		}
177		base.Exit()
178	}
179
180	if *downloadReuse != "" && modload.HasModRoot() {
181		base.Fatalf("go mod download -reuse cannot be used inside a module")
182	}
183
184	var mods []*ModuleJSON
185	type token struct{}
186	sem := make(chan token, runtime.GOMAXPROCS(0))
187	infos, infosErr := modload.ListModules(ctx, args, 0, *downloadReuse)
188
189	// There is a bit of a chicken-and-egg problem here: ideally we need to know
190	// which Go version to switch to to download the requested modules, but if we
191	// haven't downloaded the module's go.mod file yet the GoVersion field of its
192	// info struct is not yet populated.
193	//
194	// We also need to be careful to only print the info for each module once
195	// if the -json flag is set.
196	//
197	// In theory we could go through each module in the list, attempt to download
198	// its go.mod file, and record the maximum version (either from the file or
199	// from the resulting TooNewError), all before we try the actual full download
200	// of each module.
201	//
202	// For now, we go ahead and try all the downloads and collect the errors, and
203	// if any download failed due to a TooNewError, we switch toolchains and try
204	// again. Any downloads that already succeeded will still be in cache.
205	// That won't give optimal concurrency (we'll do two batches of concurrent
206	// downloads instead of all in one batch), and it might add a little overhead
207	// to look up the downloads from the first batch in the module cache when
208	// we see them again in the second batch. On the other hand, it's way simpler
209	// to implement, and not really any more expensive if the user is requesting
210	// no explicit arguments (their go.mod file should already list an appropriate
211	// toolchain version) or only one module (as is used by the Go Module Proxy).
212
213	if infosErr != nil {
214		var sw toolchain.Switcher
215		sw.Error(infosErr)
216		if sw.NeedSwitch() {
217			sw.Switch(ctx)
218		}
219		// Otherwise, wait to report infosErr after we have downloaded
220		// when we can.
221	}
222
223	if !haveExplicitArgs && modload.WorkFilePath() == "" {
224		// 'go mod download' is sometimes run without arguments to pre-populate the
225		// module cache. In modules that aren't at go 1.17 or higher, it may fetch
226		// modules that aren't needed to build packages in the main module. This is
227		// usually not intended, so don't save sums for downloaded modules
228		// (golang.org/issue/45332). We do still fix inconsistencies in go.mod
229		// though.
230		//
231		// TODO(#64008): In the future, report an error if go.mod or go.sum need to
232		// be updated after loading the build list. This may require setting
233		// the mode to "mod" or "readonly" depending on haveExplicitArgs.
234		if err := modload.WriteGoMod(ctx, modload.WriteOpts{}); err != nil {
235			base.Fatal(err)
236		}
237	}
238
239	var downloadErrs sync.Map
240	for _, info := range infos {
241		if info.Replace != nil {
242			info = info.Replace
243		}
244		if info.Version == "" && info.Error == nil {
245			// main module or module replaced with file path.
246			// Nothing to download.
247			continue
248		}
249		m := &ModuleJSON{
250			Path:    info.Path,
251			Version: info.Version,
252			Query:   info.Query,
253			Reuse:   info.Reuse,
254			Origin:  info.Origin,
255		}
256		mods = append(mods, m)
257		if info.Error != nil {
258			m.Error = info.Error.Err
259			continue
260		}
261		if m.Reuse {
262			continue
263		}
264		sem <- token{}
265		go func() {
266			err := DownloadModule(ctx, m)
267			if err != nil {
268				downloadErrs.Store(m, err)
269				m.Error = err.Error()
270			}
271			<-sem
272		}()
273	}
274
275	// Fill semaphore channel to wait for goroutines to finish.
276	for n := cap(sem); n > 0; n-- {
277		sem <- token{}
278	}
279
280	// If there were explicit arguments
281	// (like 'go mod download golang.org/x/tools@latest'),
282	// check whether we need to upgrade the toolchain in order to download them.
283	//
284	// (If invoked without arguments, we expect the module graph to already
285	// be tidy and the go.mod file to declare a 'go' version that satisfies
286	// transitive requirements. If that invariant holds, then we should have
287	// already upgraded when we loaded the module graph, and should not need
288	// an additional check here. See https://go.dev/issue/45551.)
289	//
290	// We also allow upgrades if in a workspace because in workspace mode
291	// with no arguments we download the module pattern "all",
292	// which may include dependencies that are normally pruned out
293	// of the individual modules in the workspace.
294	if haveExplicitArgs || modload.WorkFilePath() != "" {
295		var sw toolchain.Switcher
296		// Add errors to the Switcher in deterministic order so that they will be
297		// logged deterministically.
298		for _, m := range mods {
299			if erri, ok := downloadErrs.Load(m); ok {
300				sw.Error(erri.(error))
301			}
302		}
303		// Only call sw.Switch if it will actually switch.
304		// Otherwise, we may want to write the errors as JSON
305		// (instead of using base.Error as sw.Switch would),
306		// and we may also have other errors to report from the
307		// initial infos returned by ListModules.
308		if sw.NeedSwitch() {
309			sw.Switch(ctx)
310		}
311	}
312
313	if *downloadJSON {
314		for _, m := range mods {
315			b, err := json.MarshalIndent(m, "", "\t")
316			if err != nil {
317				base.Fatal(err)
318			}
319			os.Stdout.Write(append(b, '\n'))
320			if m.Error != "" {
321				base.SetExitStatus(1)
322			}
323		}
324	} else {
325		for _, m := range mods {
326			if m.Error != "" {
327				base.Error(errors.New(m.Error))
328			}
329		}
330		base.ExitIfErrors()
331	}
332
333	// If there were explicit arguments, update go.mod and especially go.sum.
334	// 'go mod download mod@version' is a useful way to add a sum without using
335	// 'go get mod@version', which may have other side effects. We print this in
336	// some error message hints.
337	//
338	// If we're in workspace mode, update go.work.sum with checksums for all of
339	// the modules we downloaded that aren't already recorded. Since a requirement
340	// in one module may upgrade a dependency of another, we can't be sure that
341	// the import graph matches the import graph of any given module in isolation,
342	// so we may end up needing to load packages from modules that wouldn't
343	// otherwise be relevant.
344	//
345	// TODO(#44435): If we adjust the set of modules downloaded in workspace mode,
346	// we may also need to adjust the logic for saving checksums here.
347	//
348	// Don't save sums for 'go mod download' without arguments unless we're in
349	// workspace mode; see comment above.
350	if haveExplicitArgs || modload.WorkFilePath() != "" {
351		if err := modload.WriteGoMod(ctx, modload.WriteOpts{}); err != nil {
352			base.Error(err)
353		}
354	}
355
356	// If there was an error matching some of the requested packages, emit it now
357	// (after we've written the checksums for the modules that were downloaded
358	// successfully).
359	if infosErr != nil {
360		base.Error(infosErr)
361	}
362}
363
364// DownloadModule runs 'go mod download' for [email protected],
365// leaving the results (including any error) in m itself.
366func DownloadModule(ctx context.Context, m *ModuleJSON) error {
367	var err error
368	_, file, err := modfetch.InfoFile(ctx, m.Path, m.Version)
369	if err != nil {
370		return err
371	}
372	m.Info = file
373	m.GoMod, err = modfetch.GoModFile(ctx, m.Path, m.Version)
374	if err != nil {
375		return err
376	}
377	m.GoModSum, err = modfetch.GoModSum(ctx, m.Path, m.Version)
378	if err != nil {
379		return err
380	}
381	mod := module.Version{Path: m.Path, Version: m.Version}
382	m.Zip, err = modfetch.DownloadZip(ctx, mod)
383	if err != nil {
384		return err
385	}
386	m.Sum = modfetch.Sum(ctx, mod)
387	m.Dir, err = modfetch.Download(ctx, mod)
388	return err
389}
390