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

     1  // Copyright 2020 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 modload
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"unicode"
    16  
    17  	"cmd/go/internal/base"
    18  	"cmd/go/internal/cfg"
    19  	"cmd/go/internal/fsys"
    20  	"cmd/go/internal/gover"
    21  	"cmd/go/internal/lockedfile"
    22  	"cmd/go/internal/modfetch"
    23  	"cmd/go/internal/trace"
    24  	"cmd/internal/par"
    25  
    26  	"golang.org/x/mod/modfile"
    27  	"golang.org/x/mod/module"
    28  )
    29  
    30  // ReadModFile reads and parses the mod file at gomod. ReadModFile properly applies the
    31  // overlay, locks the file while reading, and applies fix, if applicable.
    32  func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) {
    33  	if fsys.Replaced(gomod) {
    34  		// Don't lock go.mod if it's part of the overlay.
    35  		// On Plan 9, locking requires chmod, and we don't want to modify any file
    36  		// in the overlay. See #44700.
    37  		data, err = os.ReadFile(fsys.Actual(gomod))
    38  	} else {
    39  		data, err = lockedfile.Read(gomod)
    40  	}
    41  	if err != nil {
    42  		return nil, nil, err
    43  	}
    44  
    45  	f, err = modfile.Parse(gomod, data, fix)
    46  	if err != nil {
    47  		f, laxErr := modfile.ParseLax(gomod, data, fix)
    48  		if laxErr == nil {
    49  			if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
    50  				toolchain := ""
    51  				if f.Toolchain != nil {
    52  					toolchain = f.Toolchain.Name
    53  				}
    54  				return nil, nil, &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version, Toolchain: toolchain}
    55  			}
    56  		}
    57  
    58  		// Errors returned by modfile.Parse begin with file:line.
    59  		return nil, nil, fmt.Errorf("errors parsing %s:\n%w", base.ShortPath(gomod), shortPathErrorList(err))
    60  	}
    61  	if f.Go != nil && gover.Compare(f.Go.Version, gover.Local()) > 0 {
    62  		toolchain := ""
    63  		if f.Toolchain != nil {
    64  			toolchain = f.Toolchain.Name
    65  		}
    66  		return nil, nil, &gover.TooNewError{What: base.ShortPath(gomod), GoVersion: f.Go.Version, Toolchain: toolchain}
    67  	}
    68  	if f.Module == nil {
    69  		// No module declaration. Must add module path.
    70  		return nil, nil, fmt.Errorf("error reading %s: missing module declaration. To specify the module path:\n\tgo mod edit -module=example.com/mod", base.ShortPath(gomod))
    71  	} else if err := CheckReservedModulePath(f.Module.Mod.Path); err != nil {
    72  		return nil, nil, fmt.Errorf("error reading %s: invalid module path: %q", base.ShortPath(gomod), f.Module.Mod.Path)
    73  	}
    74  
    75  	return data, f, err
    76  }
    77  
    78  func shortPathErrorList(err error) error {
    79  	if el, ok := errors.AsType[modfile.ErrorList](err); ok {
    80  		for i := range el {
    81  			el[i].Filename = base.ShortPath(el[i].Filename)
    82  		}
    83  	}
    84  	return err
    85  }
    86  
    87  // A modFileIndex is an index of data corresponding to a modFile
    88  // at a specific point in time.
    89  type modFileIndex struct {
    90  	data         []byte
    91  	dataNeedsFix bool // true if fixVersion applied a change while parsing data
    92  	module       module.Version
    93  	goVersion    string // Go version (no "v" or "go" prefix)
    94  	toolchain    string
    95  	require      map[module.Version]requireMeta
    96  	replace      map[module.Version]module.Version
    97  	exclude      map[module.Version]bool
    98  	ignore       []string
    99  }
   100  
   101  type requireMeta struct {
   102  	indirect bool
   103  }
   104  
   105  // A modPruning indicates whether transitive dependencies of Go 1.17 dependencies
   106  // are pruned out of the module subgraph rooted at a given module.
   107  // (See https://golang.org/ref/mod#graph-pruning.)
   108  type modPruning uint8
   109  
   110  const (
   111  	pruned    modPruning = iota // transitive dependencies of modules at go 1.17 and higher are pruned out
   112  	unpruned                    // no transitive dependencies are pruned out
   113  	workspace                   // pruned to the union of modules in the workspace
   114  )
   115  
   116  func (p modPruning) String() string {
   117  	switch p {
   118  	case pruned:
   119  		return "pruned"
   120  	case unpruned:
   121  		return "unpruned"
   122  	case workspace:
   123  		return "workspace"
   124  	default:
   125  		return fmt.Sprintf("%T(%d)", p, p)
   126  	}
   127  }
   128  
   129  func pruningForGoVersion(goVersion string) modPruning {
   130  	if gover.Compare(goVersion, gover.ExplicitIndirectVersion) < 0 {
   131  		// The go.mod file does not duplicate relevant information about transitive
   132  		// dependencies, so they cannot be pruned out.
   133  		return unpruned
   134  	}
   135  	return pruned
   136  }
   137  
   138  // CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by
   139  // the main module's go.mod or retracted by its author. Most version queries use
   140  // this to filter out versions that should not be used.
   141  func (s *State) CheckAllowed(ctx context.Context, m module.Version) error {
   142  	if err := s.CheckExclusions(ctx, m); err != nil {
   143  		return err
   144  	}
   145  	if err := s.CheckRetractions(ctx, m); err != nil {
   146  		return err
   147  	}
   148  	return nil
   149  }
   150  
   151  // ErrDisallowed is returned by version predicates passed to Query and similar
   152  // functions to indicate that a version should not be considered.
   153  var ErrDisallowed = errors.New("disallowed module version")
   154  
   155  // CheckExclusions returns an error equivalent to ErrDisallowed if module m is
   156  // excluded by the main module's go.mod file.
   157  func (s *State) CheckExclusions(ctx context.Context, m module.Version) error {
   158  	for _, mainModule := range s.MainModules.Versions() {
   159  		if index := s.MainModules.Index(mainModule); index != nil && index.exclude[m] {
   160  			return module.VersionError(m, errExcluded)
   161  		}
   162  	}
   163  	return nil
   164  }
   165  
   166  var errExcluded = &excludedError{}
   167  
   168  type excludedError struct{}
   169  
   170  func (e *excludedError) Error() string     { return "excluded by go.mod" }
   171  func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
   172  
   173  // CheckRetractions returns an error if module m has been retracted by
   174  // its author.
   175  func (s *State) CheckRetractions(ctx context.Context, m module.Version) (err error) {
   176  	defer func() {
   177  		if err == nil {
   178  			return
   179  		}
   180  		if _, ok := errors.AsType[*ModuleRetractedError](err); ok {
   181  			return
   182  		}
   183  		// Attribute the error to the version being checked, not the version from
   184  		// which the retractions were to be loaded.
   185  		if mErr, ok := errors.AsType[*module.ModuleError](err); ok {
   186  			err = mErr.Err
   187  		}
   188  		err = &retractionLoadingError{m: m, err: err}
   189  	}()
   190  
   191  	if m.Version == "" {
   192  		// Main module, standard library, or file replacement module.
   193  		// Cannot be retracted.
   194  		return nil
   195  	}
   196  	if repl := Replacement(s, module.Version{Path: m.Path}); repl.Path != "" {
   197  		// All versions of the module were replaced.
   198  		// Don't load retractions, since we'd just load the replacement.
   199  		return nil
   200  	}
   201  
   202  	// Find the latest available version of the module, and load its go.mod. If
   203  	// the latest version is replaced, we'll load the replacement.
   204  	//
   205  	// If there's an error loading the go.mod, we'll return it here. These errors
   206  	// should generally be ignored by callers since they happen frequently when
   207  	// we're offline. These errors are not equivalent to ErrDisallowed, so they
   208  	// may be distinguished from retraction errors.
   209  	//
   210  	// We load the raw file here: the go.mod file may have a different module
   211  	// path that we expect if the module or its repository was renamed.
   212  	// We still want to apply retractions to other aliases of the module.
   213  	rm, err := queryLatestVersionIgnoringRetractions(s, ctx, m.Path)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	summary, err := rawGoModSummary(s, rm)
   218  	if err != nil && !errors.Is(err, gover.ErrTooNew) {
   219  		return err
   220  	}
   221  
   222  	var rationale []string
   223  	isRetracted := false
   224  	for _, r := range summary.retract {
   225  		if gover.ModCompare(m.Path, r.Low, m.Version) <= 0 && gover.ModCompare(m.Path, m.Version, r.High) <= 0 {
   226  			isRetracted = true
   227  			if r.Rationale != "" {
   228  				rationale = append(rationale, r.Rationale)
   229  			}
   230  		}
   231  	}
   232  	if isRetracted {
   233  		return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
   234  	}
   235  	return nil
   236  }
   237  
   238  type ModuleRetractedError struct {
   239  	Rationale []string
   240  }
   241  
   242  func (e *ModuleRetractedError) Error() string {
   243  	msg := "retracted by module author"
   244  	if len(e.Rationale) > 0 {
   245  		// This is meant to be a short error printed on a terminal, so just
   246  		// print the first rationale.
   247  		msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
   248  	}
   249  	return msg
   250  }
   251  
   252  func (e *ModuleRetractedError) Is(err error) bool {
   253  	return err == ErrDisallowed
   254  }
   255  
   256  type retractionLoadingError struct {
   257  	m   module.Version
   258  	err error
   259  }
   260  
   261  func (e *retractionLoadingError) Error() string {
   262  	return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
   263  }
   264  
   265  func (e *retractionLoadingError) Unwrap() error {
   266  	return e.err
   267  }
   268  
   269  // ShortMessage returns a string from go.mod (for example, a retraction
   270  // rationale or deprecation message) that is safe to print in a terminal.
   271  //
   272  // If the given string is empty, ShortMessage returns the given default. If the
   273  // given string is too long or contains non-printable characters, ShortMessage
   274  // returns a hard-coded string.
   275  func ShortMessage(message, emptyDefault string) string {
   276  	const maxLen = 500
   277  	if i := strings.Index(message, "\n"); i >= 0 {
   278  		message = message[:i]
   279  	}
   280  	message = strings.TrimSpace(message)
   281  	if message == "" {
   282  		return emptyDefault
   283  	}
   284  	if len(message) > maxLen {
   285  		return "(message omitted: too long)"
   286  	}
   287  	for _, r := range message {
   288  		if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
   289  			return "(message omitted: contains non-printable characters)"
   290  		}
   291  	}
   292  	// NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here.
   293  	return message
   294  }
   295  
   296  // CheckDeprecation returns a deprecation message from the go.mod file of the
   297  // latest version of the given module. Deprecation messages are comments
   298  // before or on the same line as the module directives that start with
   299  // "Deprecated:" and run until the end of the paragraph.
   300  //
   301  // CheckDeprecation returns an error if the message can't be loaded.
   302  // CheckDeprecation returns "", nil if there is no deprecation message.
   303  func CheckDeprecation(loaderstate *State, ctx context.Context, m module.Version) (deprecation string, err error) {
   304  	defer func() {
   305  		if err != nil {
   306  			err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err)
   307  		}
   308  	}()
   309  
   310  	if m.Version == "" {
   311  		// Main module, standard library, or file replacement module.
   312  		// Don't look up deprecation.
   313  		return "", nil
   314  	}
   315  	if repl := Replacement(loaderstate, module.Version{Path: m.Path}); repl.Path != "" {
   316  		// All versions of the module were replaced.
   317  		// We'll look up deprecation separately for the replacement.
   318  		return "", nil
   319  	}
   320  
   321  	latest, err := queryLatestVersionIgnoringRetractions(loaderstate, ctx, m.Path)
   322  	if err != nil {
   323  		return "", err
   324  	}
   325  	summary, err := rawGoModSummary(loaderstate, latest)
   326  	if err != nil && !errors.Is(err, gover.ErrTooNew) {
   327  		return "", err
   328  	}
   329  	return summary.deprecated, nil
   330  }
   331  
   332  func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) {
   333  	if r, ok := replace[mod]; ok {
   334  		return mod.Version, r, true
   335  	}
   336  	if r, ok := replace[module.Version{Path: mod.Path}]; ok {
   337  		return "", r, true
   338  	}
   339  	return "", module.Version{}, false
   340  }
   341  
   342  // Replacement returns the replacement for mod, if any. If the path in the
   343  // module.Version is relative it's relative to the single main module outside
   344  // workspace mode, or the workspace's directory in workspace mode.
   345  func Replacement(loaderstate *State, mod module.Version) module.Version {
   346  	r, foundModRoot, _ := replacementFrom(loaderstate, mod)
   347  	return canonicalizeReplacePath(loaderstate, r, foundModRoot)
   348  }
   349  
   350  // replacementFrom returns the replacement for mod, if any, the modroot of the replacement if it appeared in a go.mod,
   351  // and the source of the replacement. The replacement is relative to the go.work or go.mod file it appears in.
   352  func replacementFrom(loaderstate *State, mod module.Version) (r module.Version, modroot string, fromFile string) {
   353  	foundFrom, found, foundModRoot := "", module.Version{}, ""
   354  	if loaderstate.MainModules == nil {
   355  		return module.Version{}, "", ""
   356  	} else if loaderstate.MainModules.Contains(mod.Path) && mod.Version == "" {
   357  		// Don't replace the workspace version of the main module.
   358  		return module.Version{}, "", ""
   359  	}
   360  	if _, r, ok := replacement(mod, loaderstate.MainModules.WorkFileReplaceMap()); ok {
   361  		return r, "", loaderstate.workFilePath
   362  	}
   363  	for _, v := range loaderstate.MainModules.Versions() {
   364  		if index := loaderstate.MainModules.Index(v); index != nil {
   365  			if from, r, ok := replacement(mod, index.replace); ok {
   366  				modRoot := loaderstate.MainModules.ModRoot(v)
   367  				if foundModRoot != "" && foundFrom != from && found != r {
   368  					base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
   369  						mod, modFilePath(foundModRoot), modFilePath(modRoot))
   370  					return found, foundModRoot, modFilePath(foundModRoot)
   371  				}
   372  				found, foundModRoot = r, modRoot
   373  			}
   374  		}
   375  	}
   376  	return found, foundModRoot, modFilePath(foundModRoot)
   377  }
   378  
   379  func replaceRelativeTo(loaderstate *State) string {
   380  	if workFilePath := WorkFilePath(loaderstate); workFilePath != "" {
   381  		return filepath.Dir(workFilePath)
   382  	}
   383  	return loaderstate.MainModules.ModRoot(loaderstate.MainModules.mustGetSingleMainModule(loaderstate))
   384  }
   385  
   386  // canonicalizeReplacePath ensures that relative, on-disk, replaced module paths
   387  // are relative to the workspace directory (in workspace mode) or to the module's
   388  // directory (in module mode, as they already are).
   389  func canonicalizeReplacePath(loaderstate *State, r module.Version, modRoot string) module.Version {
   390  	if filepath.IsAbs(r.Path) || r.Version != "" || modRoot == "" {
   391  		return r
   392  	}
   393  	workFilePath := WorkFilePath(loaderstate)
   394  	if workFilePath == "" {
   395  		return r
   396  	}
   397  	abs := filepath.Join(modRoot, r.Path)
   398  	if rel, err := filepath.Rel(filepath.Dir(workFilePath), abs); err == nil {
   399  		return module.Version{Path: ToDirectoryPath(rel), Version: r.Version}
   400  	}
   401  	// We couldn't make the version's path relative to the workspace's path,
   402  	// so just return the absolute path. It's the best we can do.
   403  	return module.Version{Path: ToDirectoryPath(abs), Version: r.Version}
   404  }
   405  
   406  // resolveReplacement returns the module actually used to load the source code
   407  // for m: either m itself, or the replacement for m (iff m is replaced).
   408  // It also returns the modroot of the module providing the replacement if
   409  // one was found.
   410  func resolveReplacement(loaderstate *State, m module.Version) module.Version {
   411  	if r := Replacement(loaderstate, m); r.Path != "" {
   412  		return r
   413  	}
   414  	return m
   415  }
   416  
   417  func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version {
   418  	replaceMap := make(map[module.Version]module.Version, len(replacements))
   419  	for _, r := range replacements {
   420  		if prev, dup := replaceMap[r.Old]; dup && prev != r.New {
   421  			base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
   422  		}
   423  		replaceMap[r.Old] = r.New
   424  	}
   425  	return replaceMap
   426  }
   427  
   428  // indexModFile rebuilds the index of modFile.
   429  // If modFile has been changed since it was first read,
   430  // modFile.Cleanup must be called before indexModFile.
   431  func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
   432  	i := new(modFileIndex)
   433  	i.data = data
   434  	i.dataNeedsFix = needsFix
   435  
   436  	i.module = module.Version{}
   437  	if modFile.Module != nil {
   438  		i.module = modFile.Module.Mod
   439  	}
   440  
   441  	i.goVersion = ""
   442  	if modFile.Go == nil {
   443  		rawGoVersion.Store(mod, "")
   444  	} else {
   445  		i.goVersion = modFile.Go.Version
   446  		rawGoVersion.Store(mod, modFile.Go.Version)
   447  	}
   448  	if modFile.Toolchain != nil {
   449  		i.toolchain = modFile.Toolchain.Name
   450  	}
   451  
   452  	i.require = make(map[module.Version]requireMeta, len(modFile.Require))
   453  	for _, r := range modFile.Require {
   454  		i.require[r.Mod] = requireMeta{indirect: r.Indirect}
   455  	}
   456  
   457  	i.replace = toReplaceMap(modFile.Replace)
   458  
   459  	i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
   460  	for _, x := range modFile.Exclude {
   461  		i.exclude[x.Mod] = true
   462  	}
   463  	if modFile.Ignore != nil {
   464  		for _, x := range modFile.Ignore {
   465  			i.ignore = append(i.ignore, x.Path)
   466  		}
   467  	}
   468  	return i
   469  }
   470  
   471  // modFileIsDirty reports whether the go.mod file differs meaningfully
   472  // from what was indexed.
   473  // If modFile has been changed (even cosmetically) since it was first read,
   474  // modFile.Cleanup must be called before modFileIsDirty.
   475  func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
   476  	if i == nil {
   477  		return modFile != nil
   478  	}
   479  
   480  	if i.dataNeedsFix {
   481  		return true
   482  	}
   483  
   484  	if modFile.Module == nil {
   485  		if i.module != (module.Version{}) {
   486  			return true
   487  		}
   488  	} else if modFile.Module.Mod != i.module {
   489  		return true
   490  	}
   491  
   492  	var goV, toolchain string
   493  	if modFile.Go != nil {
   494  		goV = modFile.Go.Version
   495  	}
   496  	if modFile.Toolchain != nil {
   497  		toolchain = modFile.Toolchain.Name
   498  	}
   499  
   500  	if goV != i.goVersion ||
   501  		toolchain != i.toolchain ||
   502  		len(modFile.Require) != len(i.require) ||
   503  		len(modFile.Replace) != len(i.replace) ||
   504  		len(modFile.Exclude) != len(i.exclude) {
   505  		return true
   506  	}
   507  
   508  	for _, r := range modFile.Require {
   509  		if meta, ok := i.require[r.Mod]; !ok {
   510  			return true
   511  		} else if r.Indirect != meta.indirect {
   512  			if cfg.BuildMod == "readonly" {
   513  				// The module's requirements are consistent; only the "// indirect"
   514  				// comments that are wrong. But those are only guaranteed to be accurate
   515  				// after a "go mod tidy" — it's a good idea to run those before
   516  				// committing a change, but it's certainly not mandatory.
   517  			} else {
   518  				return true
   519  			}
   520  		}
   521  	}
   522  
   523  	for _, r := range modFile.Replace {
   524  		if r.New != i.replace[r.Old] {
   525  			return true
   526  		}
   527  	}
   528  
   529  	for _, x := range modFile.Exclude {
   530  		if !i.exclude[x.Mod] {
   531  			return true
   532  		}
   533  	}
   534  
   535  	return false
   536  }
   537  
   538  // rawGoVersion records the Go version parsed from each module's go.mod file.
   539  //
   540  // If a module is replaced, the version of the replacement is keyed by the
   541  // replacement module.Version, not the version being replaced.
   542  var rawGoVersion sync.Map // map[module.Version]string
   543  
   544  // A modFileSummary is a summary of a go.mod file for which we do not need to
   545  // retain complete information — for example, the go.mod file of a dependency
   546  // module.
   547  type modFileSummary struct {
   548  	module     module.Version
   549  	goVersion  string
   550  	toolchain  string
   551  	ignore     []string
   552  	pruning    modPruning
   553  	require    []module.Version
   554  	retract    []retraction
   555  	deprecated string
   556  }
   557  
   558  // A retraction consists of a retracted version interval and rationale.
   559  // retraction is like modfile.Retract, but it doesn't point to the syntax tree.
   560  type retraction struct {
   561  	modfile.VersionInterval
   562  	Rationale string
   563  }
   564  
   565  // goModSummary returns a summary of the go.mod file for module m,
   566  // taking into account any replacements for m, exclusions of its dependencies,
   567  // and/or vendoring.
   568  //
   569  // m must be a version in the module graph, reachable from the Target module.
   570  // In readonly mode, the go.sum file must contain an entry for m's go.mod file
   571  // (or its replacement). goModSummary must not be called for the Target module
   572  // itself, as its requirements may change. Use rawGoModSummary for other
   573  // module versions.
   574  //
   575  // The caller must not modify the returned summary.
   576  func goModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) {
   577  	if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
   578  		panic("internal error: goModSummary called on a main module")
   579  	}
   580  	if gover.IsToolchain(m.Path) {
   581  		return rawGoModSummary(loaderstate, m)
   582  	}
   583  
   584  	if cfg.BuildMod == "vendor" {
   585  		summary := &modFileSummary{
   586  			module: module.Version{Path: m.Path},
   587  		}
   588  
   589  		readVendorList(VendorDir(loaderstate))
   590  		if vendorVersion[m.Path] != m.Version {
   591  			// This module is not vendored, so packages cannot be loaded from it and
   592  			// it cannot be relevant to the build.
   593  			return summary, nil
   594  		}
   595  
   596  		// For every module other than the target,
   597  		// return the full list of modules from modules.txt.
   598  		// We don't know what versions the vendored module actually relies on,
   599  		// so assume that it requires everything.
   600  		summary.require = vendorList
   601  		return summary, nil
   602  	}
   603  
   604  	actual := resolveReplacement(loaderstate, m)
   605  	if mustHaveSums(loaderstate) && actual.Version != "" {
   606  		key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
   607  		if !modfetch.HaveSum(loaderstate.Fetcher(), key) {
   608  			suggestion := fmt.Sprintf(" for go.mod file; to add it:\n\tgo mod download %s", m.Path)
   609  			return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
   610  		}
   611  	}
   612  	summary, err := rawGoModSummary(loaderstate, actual)
   613  	if err != nil {
   614  		return nil, err
   615  	}
   616  
   617  	if actual.Version == "" {
   618  		// The actual module is a filesystem-local replacement, for which we have
   619  		// unfortunately not enforced any sort of invariants about module lines or
   620  		// matching module paths. Anything goes.
   621  		//
   622  		// TODO(bcmills): Remove this special-case, update tests, and add a
   623  		// release note.
   624  	} else {
   625  		if summary.module.Path == "" {
   626  			return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
   627  		}
   628  
   629  		// In theory we should only allow mpath to be unequal to m.Path here if the
   630  		// version that we fetched lacks an explicit go.mod file: if the go.mod file
   631  		// is explicit, then it should match exactly (to ensure that imports of other
   632  		// packages within the module are interpreted correctly). Unfortunately, we
   633  		// can't determine that information from the module proxy protocol: we'll have
   634  		// to leave that validation for when we load actual packages from within the
   635  		// module.
   636  		if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
   637  			return nil, module.VersionError(actual,
   638  				fmt.Errorf("parsing go.mod:\n"+
   639  					"\tmodule declares its path as: %s\n"+
   640  					"\t        but was required as: %s", mpath, m.Path))
   641  		}
   642  	}
   643  
   644  	for _, mainModule := range loaderstate.MainModules.Versions() {
   645  		if index := loaderstate.MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
   646  			// Drop any requirements on excluded versions.
   647  			// Don't modify the cached summary though, since we might need the raw
   648  			// summary separately.
   649  			haveExcludedReqs := false
   650  			for _, r := range summary.require {
   651  				if index.exclude[r] {
   652  					haveExcludedReqs = true
   653  					break
   654  				}
   655  			}
   656  			if haveExcludedReqs {
   657  				s := new(modFileSummary)
   658  				*s = *summary
   659  				s.require = make([]module.Version, 0, len(summary.require))
   660  				for _, r := range summary.require {
   661  					if !index.exclude[r] {
   662  						s.require = append(s.require, r)
   663  					}
   664  				}
   665  				summary = s
   666  			}
   667  		}
   668  	}
   669  	return summary, nil
   670  }
   671  
   672  // rawGoModSummary returns a new summary of the go.mod file for module m,
   673  // ignoring all replacements that may apply to m and excludes that may apply to
   674  // its dependencies.
   675  //
   676  // rawGoModSummary cannot be used on the main module outside of workspace mode.
   677  // The modFileSummary can still be used for retractions and deprecations
   678  // even if a TooNewError is returned.
   679  func rawGoModSummary(loaderstate *State, m module.Version) (*modFileSummary, error) {
   680  	if gover.IsToolchain(m.Path) {
   681  		if m.Path == "go" && gover.Compare(m.Version, gover.GoStrictVersion) >= 0 {
   682  			// Declare that go 1.21.3 requires toolchain 1.21.3,
   683  			// so that go get knows that downgrading toolchain implies downgrading go
   684  			// and similarly upgrading go requires upgrading the toolchain.
   685  			return &modFileSummary{module: m, require: []module.Version{{Path: "toolchain", Version: "go" + m.Version}}}, nil
   686  		}
   687  		return &modFileSummary{module: m}, nil
   688  	}
   689  	if m.Version == "" && !loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
   690  		// Calling rawGoModSummary implies that we are treating m as a module whose
   691  		// requirements aren't the roots of the module graph and can't be modified.
   692  		//
   693  		// If we are not in workspace mode, then the requirements of the main module
   694  		// are the roots of the module graph and we expect them to be kept consistent.
   695  		panic("internal error: rawGoModSummary called on a main module")
   696  	}
   697  	if m.Version == "" && loaderstate.inWorkspaceMode() && m.Path == "command-line-arguments" {
   698  		// "go work sync" calls LoadModGraph to make sure the module graph is valid.
   699  		// If there are no modules in the workspace, we synthesize an empty
   700  		// command-line-arguments module, which rawGoModData cannot read a go.mod for.
   701  		return &modFileSummary{module: m}, nil
   702  	} else if m.Version == "" && loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
   703  		// When go get uses EnterWorkspace to check that the workspace loads properly,
   704  		// it will update the contents of the workspace module's modfile in memory. To use the updated
   705  		// contents of the modfile when doing the load, don't read from disk and instead
   706  		// recompute a summary using the updated contents of the modfile.
   707  		if mf := loaderstate.MainModules.ModFile(m); mf != nil {
   708  			return summaryFromModFile(m, loaderstate.MainModules.modFiles[m])
   709  		}
   710  	}
   711  	return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) {
   712  		name, data, err := rawGoModData(loaderstate, m)
   713  		if err != nil {
   714  			return nil, err
   715  		}
   716  		f, err := modfile.ParseLax(name, data, nil)
   717  		if err != nil {
   718  			return nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))
   719  		}
   720  		return summaryFromModFile(m, f)
   721  	})
   722  }
   723  
   724  func summaryFromModFile(m module.Version, f *modfile.File) (*modFileSummary, error) {
   725  	summary := new(modFileSummary)
   726  	if f.Module != nil {
   727  		summary.module = f.Module.Mod
   728  		summary.deprecated = f.Module.Deprecated
   729  	}
   730  	if f.Go != nil {
   731  		rawGoVersion.LoadOrStore(m, f.Go.Version)
   732  		summary.goVersion = f.Go.Version
   733  		summary.pruning = pruningForGoVersion(f.Go.Version)
   734  	} else {
   735  		summary.pruning = unpruned
   736  	}
   737  	if f.Toolchain != nil {
   738  		summary.toolchain = f.Toolchain.Name
   739  	}
   740  	if f.Ignore != nil {
   741  		for _, i := range f.Ignore {
   742  			summary.ignore = append(summary.ignore, i.Path)
   743  		}
   744  	}
   745  	if len(f.Require) > 0 {
   746  		summary.require = make([]module.Version, 0, len(f.Require)+1)
   747  		for _, req := range f.Require {
   748  			summary.require = append(summary.require, req.Mod)
   749  		}
   750  	}
   751  
   752  	if len(f.Retract) > 0 {
   753  		summary.retract = make([]retraction, 0, len(f.Retract))
   754  		for _, ret := range f.Retract {
   755  			summary.retract = append(summary.retract, retraction{
   756  				VersionInterval: ret.VersionInterval,
   757  				Rationale:       ret.Rationale,
   758  			})
   759  		}
   760  	}
   761  
   762  	// This block must be kept at the end of the function because the summary may
   763  	// be used for reading retractions or deprecations even if a TooNewError is
   764  	// returned.
   765  	if summary.goVersion != "" && gover.Compare(summary.goVersion, gover.GoStrictVersion) >= 0 {
   766  		summary.require = append(summary.require, module.Version{Path: "go", Version: summary.goVersion})
   767  		if gover.Compare(summary.goVersion, gover.Local()) > 0 {
   768  			return summary, &gover.TooNewError{What: "module " + m.String(), GoVersion: summary.goVersion}
   769  		}
   770  	}
   771  
   772  	return summary, nil
   773  }
   774  
   775  var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary]
   776  
   777  // rawGoModData returns the content of the go.mod file for module m, ignoring
   778  // all replacements that may apply to m.
   779  //
   780  // rawGoModData cannot be used on the main module outside of workspace mode.
   781  //
   782  // Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
   783  // Use rawGoModSummary instead unless you specifically need these bytes.
   784  func rawGoModData(loaderstate *State, m module.Version) (name string, data []byte, err error) {
   785  	if m.Version == "" {
   786  		dir := m.Path
   787  		if !filepath.IsAbs(dir) {
   788  			if loaderstate.inWorkspaceMode() && loaderstate.MainModules.Contains(m.Path) {
   789  				dir = loaderstate.MainModules.ModRoot(m)
   790  			} else {
   791  				// m is a replacement module with only a file path.
   792  				dir = filepath.Join(replaceRelativeTo(loaderstate), dir)
   793  			}
   794  		}
   795  		name = filepath.Join(dir, "go.mod")
   796  		if fsys.Replaced(name) {
   797  			// Don't lock go.mod if it's part of the overlay.
   798  			// On Plan 9, locking requires chmod, and we don't want to modify any file
   799  			// in the overlay. See #44700.
   800  			data, err = os.ReadFile(fsys.Actual(name))
   801  		} else {
   802  			data, err = lockedfile.Read(name)
   803  		}
   804  		if err != nil {
   805  			return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err))
   806  		}
   807  	} else {
   808  		if !gover.ModIsValid(m.Path, m.Version) {
   809  			// Disallow the broader queries supported by fetch.Lookup.
   810  			base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
   811  		}
   812  		name = "go.mod"
   813  		data, err = loaderstate.Fetcher().GoMod(context.TODO(), m.Path, m.Version)
   814  	}
   815  	return name, data, err
   816  }
   817  
   818  // queryLatestVersionIgnoringRetractions looks up the latest version of the
   819  // module with the given path without considering retracted or excluded
   820  // versions.
   821  //
   822  // If all versions of the module are replaced,
   823  // queryLatestVersionIgnoringRetractions returns the replacement without making
   824  // a query.
   825  //
   826  // If the queried latest version is replaced,
   827  // queryLatestVersionIgnoringRetractions returns the replacement.
   828  func queryLatestVersionIgnoringRetractions(loaderstate *State, ctx context.Context, path string) (latest module.Version, err error) {
   829  	return latestVersionIgnoringRetractionsCache.Do(path, func() (module.Version, error) {
   830  		ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
   831  		defer span.Done()
   832  
   833  		if repl := Replacement(loaderstate, module.Version{Path: path}); repl.Path != "" {
   834  			// All versions of the module were replaced.
   835  			// No need to query.
   836  			return repl, nil
   837  		}
   838  
   839  		// Find the latest version of the module.
   840  		// Ignore exclusions from the main module's go.mod.
   841  		const ignoreSelected = ""
   842  		var allowAll AllowedFunc
   843  		rev, err := Query(loaderstate, ctx, path, "latest", ignoreSelected, allowAll)
   844  		if err != nil {
   845  			return module.Version{}, err
   846  		}
   847  		latest := module.Version{Path: path, Version: rev.Version}
   848  		if repl := resolveReplacement(loaderstate, latest); repl.Path != "" {
   849  			latest = repl
   850  		}
   851  		return latest, nil
   852  	})
   853  }
   854  
   855  var latestVersionIgnoringRetractionsCache par.ErrCache[string, module.Version] // path → queryLatestVersionIgnoringRetractions result
   856  
   857  // ToDirectoryPath adds a prefix if necessary so that path in unambiguously
   858  // an absolute path or a relative path starting with a '.' or '..'
   859  // path component.
   860  func ToDirectoryPath(path string) string {
   861  	if modfile.IsDirectoryPath(path) {
   862  		return path
   863  	}
   864  	// The path is not a relative path or an absolute path, so make it relative
   865  	// to the current directory.
   866  	return "./" + filepath.ToSlash(filepath.Clean(path))
   867  }
   868  

View as plain text