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

View as plain text