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

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

View as plain text