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

     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  
     5  package modcmd
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"os"
    11  	"runtime"
    12  
    13  	"cmd/go/internal/base"
    14  	"cmd/go/internal/cfg"
    15  	"cmd/go/internal/modfetch"
    16  	"cmd/go/internal/modload"
    17  
    18  	"golang.org/x/mod/module"
    19  	"golang.org/x/mod/semver"
    20  )
    21  
    22  var cmdDownload = &base.Command{
    23  	UsageLine: "go mod download [-x] [-json] [modules]",
    24  	Short:     "download modules to local cache",
    25  	Long: `
    26  Download downloads the named modules, which can be module patterns selecting
    27  dependencies of the main module or module queries of the form path@version.
    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.
    33  
    34  The go command will automatically download modules as needed during ordinary
    35  execution. The "go mod download" command is useful mainly for pre-filling
    36  the local cache or to compute the answers for a Go module proxy.
    37  
    38  By default, download writes nothing to standard output. It may print progress
    39  messages and errors to standard error.
    40  
    41  The -json flag causes download to print a sequence of JSON objects
    42  to standard output, describing each downloaded module (or failure),
    43  corresponding to this Go struct:
    44  
    45      type Module struct {
    46          Path     string // module path
    47          Version  string // module version
    48          Error    string // error loading module
    49          Info     string // absolute path to cached .info file
    50          GoMod    string // absolute path to cached .mod file
    51          Zip      string // absolute path to cached .zip file
    52          Dir      string // absolute path to cached source root directory
    53          Sum      string // checksum for path, version (as in go.sum)
    54          GoModSum string // checksum for go.mod (as in go.sum)
    55      }
    56  
    57  The -x flag causes download to print the commands download executes.
    58  
    59  See https://golang.org/ref/mod#go-mod-download for more about 'go mod download'.
    60  
    61  See https://golang.org/ref/mod#version-queries for more about version queries.
    62  	`,
    63  }
    64  
    65  var downloadJSON = cmdDownload.Flag.Bool("json", false, "")
    66  
    67  func init() {
    68  	cmdDownload.Run = runDownload // break init cycle
    69  
    70  	// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
    71  	cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
    72  	base.AddModCommonFlags(&cmdDownload.Flag)
    73  }
    74  
    75  type moduleJSON struct {
    76  	Path     string `json:",omitempty"`
    77  	Version  string `json:",omitempty"`
    78  	Error    string `json:",omitempty"`
    79  	Info     string `json:",omitempty"`
    80  	GoMod    string `json:",omitempty"`
    81  	Zip      string `json:",omitempty"`
    82  	Dir      string `json:",omitempty"`
    83  	Sum      string `json:",omitempty"`
    84  	GoModSum string `json:",omitempty"`
    85  }
    86  
    87  func runDownload(ctx context.Context, cmd *base.Command, args []string) {
    88  	modload.InitWorkfile()
    89  
    90  	// Check whether modules are enabled and whether we're in a module.
    91  	modload.ForceUseModules = true
    92  	modload.ExplicitWriteGoMod = true
    93  	haveExplicitArgs := len(args) > 0
    94  
    95  	if modload.HasModRoot() || modload.WorkFilePath() != "" {
    96  		modload.LoadModFile(ctx) // to fill MainModules
    97  
    98  		if haveExplicitArgs {
    99  			for _, mainModule := range modload.MainModules.Versions() {
   100  				targetAtUpgrade := mainModule.Path + "@upgrade"
   101  				targetAtPatch := mainModule.Path + "@patch"
   102  				for _, arg := range args {
   103  					switch arg {
   104  					case mainModule.Path, targetAtUpgrade, targetAtPatch:
   105  						os.Stderr.WriteString("go: skipping download of " + arg + " that resolves to the main module\n")
   106  					}
   107  				}
   108  			}
   109  		} else if modload.WorkFilePath() != "" {
   110  			// TODO(#44435): Think about what the correct query is to download the
   111  			// right set of modules. Also see code review comment at
   112  			// https://go-review.googlesource.com/c/go/+/359794/comments/ce946a80_6cf53992.
   113  			args = []string{"all"}
   114  		} else {
   115  			mainModule := modload.MainModules.Versions()[0]
   116  			modFile := modload.MainModules.ModFile(mainModule)
   117  			if modFile.Go == nil || semver.Compare("v"+modFile.Go.Version, modload.ExplicitIndirectVersionV) < 0 {
   118  				if len(modFile.Require) > 0 {
   119  					args = []string{"all"}
   120  				}
   121  			} else {
   122  				// As of Go 1.17, the go.mod file explicitly requires every module
   123  				// that provides any package imported by the main module.
   124  				// 'go mod download' is typically run before testing packages in the
   125  				// main module, so by default we shouldn't download the others
   126  				// (which are presumed irrelevant to the packages in the main module).
   127  				// See https://golang.org/issue/44435.
   128  				//
   129  				// However, we also need to load the full module graph, to ensure that
   130  				// we have downloaded enough of the module graph to run 'go list all',
   131  				// 'go mod graph', and similar commands.
   132  				_ = modload.LoadModGraph(ctx, "")
   133  
   134  				for _, m := range modFile.Require {
   135  					args = append(args, m.Mod.Path)
   136  				}
   137  			}
   138  		}
   139  	}
   140  
   141  	if len(args) == 0 {
   142  		if modload.HasModRoot() {
   143  			os.Stderr.WriteString("go: no module dependencies to download\n")
   144  		} else {
   145  			base.Errorf("go: no modules specified (see 'go help mod download')")
   146  		}
   147  		base.Exit()
   148  	}
   149  
   150  	downloadModule := func(m *moduleJSON) {
   151  		var err error
   152  		m.Info, err = modfetch.InfoFile(m.Path, m.Version)
   153  		if err != nil {
   154  			m.Error = err.Error()
   155  			return
   156  		}
   157  		m.GoMod, err = modfetch.GoModFile(m.Path, m.Version)
   158  		if err != nil {
   159  			m.Error = err.Error()
   160  			return
   161  		}
   162  		m.GoModSum, err = modfetch.GoModSum(m.Path, m.Version)
   163  		if err != nil {
   164  			m.Error = err.Error()
   165  			return
   166  		}
   167  		mod := module.Version{Path: m.Path, Version: m.Version}
   168  		m.Zip, err = modfetch.DownloadZip(ctx, mod)
   169  		if err != nil {
   170  			m.Error = err.Error()
   171  			return
   172  		}
   173  		m.Sum = modfetch.Sum(mod)
   174  		m.Dir, err = modfetch.Download(ctx, mod)
   175  		if err != nil {
   176  			m.Error = err.Error()
   177  			return
   178  		}
   179  	}
   180  
   181  	var mods []*moduleJSON
   182  	type token struct{}
   183  	sem := make(chan token, runtime.GOMAXPROCS(0))
   184  	infos, infosErr := modload.ListModules(ctx, args, 0)
   185  	if !haveExplicitArgs {
   186  		// 'go mod download' is sometimes run without arguments to pre-populate the
   187  		// module cache. It may fetch modules that aren't needed to build packages
   188  		// in the main module. This is usually not intended, so don't save sums for
   189  		// downloaded modules (golang.org/issue/45332). We do still fix
   190  		// inconsistencies in go.mod though.
   191  		//
   192  		// TODO(#45551): In the future, report an error if go.mod or go.sum need to
   193  		// be updated after loading the build list. This may require setting
   194  		// the mode to "mod" or "readonly" depending on haveExplicitArgs.
   195  		if err := modload.WriteGoMod(ctx); err != nil {
   196  			base.Fatalf("go: %v", err)
   197  		}
   198  	}
   199  
   200  	for _, info := range infos {
   201  		if info.Replace != nil {
   202  			info = info.Replace
   203  		}
   204  		if info.Version == "" && info.Error == nil {
   205  			// main module or module replaced with file path.
   206  			// Nothing to download.
   207  			continue
   208  		}
   209  		m := &moduleJSON{
   210  			Path:    info.Path,
   211  			Version: info.Version,
   212  		}
   213  		mods = append(mods, m)
   214  		if info.Error != nil {
   215  			m.Error = info.Error.Err
   216  			continue
   217  		}
   218  		sem <- token{}
   219  		go func() {
   220  			downloadModule(m)
   221  			<-sem
   222  		}()
   223  	}
   224  
   225  	// Fill semaphore channel to wait for goroutines to finish.
   226  	for n := cap(sem); n > 0; n-- {
   227  		sem <- token{}
   228  	}
   229  
   230  	if *downloadJSON {
   231  		for _, m := range mods {
   232  			b, err := json.MarshalIndent(m, "", "\t")
   233  			if err != nil {
   234  				base.Fatalf("go: %v", err)
   235  			}
   236  			os.Stdout.Write(append(b, '\n'))
   237  			if m.Error != "" {
   238  				base.SetExitStatus(1)
   239  			}
   240  		}
   241  	} else {
   242  		for _, m := range mods {
   243  			if m.Error != "" {
   244  				base.Errorf("go: %v", m.Error)
   245  			}
   246  		}
   247  		base.ExitIfErrors()
   248  	}
   249  
   250  	// If there were explicit arguments, update go.mod and especially go.sum.
   251  	// 'go mod download mod@version' is a useful way to add a sum without using
   252  	// 'go get mod@version', which may have other side effects. We print this in
   253  	// some error message hints.
   254  	//
   255  	// Don't save sums for 'go mod download' without arguments; see comment above.
   256  	if haveExplicitArgs {
   257  		if err := modload.WriteGoMod(ctx); err != nil {
   258  			base.Errorf("go: %v", err)
   259  		}
   260  	}
   261  
   262  	// If there was an error matching some of the requested packages, emit it now
   263  	// (after we've written the checksums for the modules that were downloaded
   264  	// successfully).
   265  	if infosErr != nil {
   266  		base.Errorf("go: %v", infosErr)
   267  	}
   268  }
   269  

View as plain text