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

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

View as plain text