github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modload/modfile.go (about)

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