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

View as plain text