golang modfile 代码
文件路径:/src/cmd/go/internal/modload/modfile.go
/ Copyright 2020 The Go Authors. All rights reserved.
/ Use of this source code is governed by a BSD-style
/ license that can be found in the LICENSE file.
package modload
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"unicode"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/lockedfile"
"cmd/go/internal/modfetch"
"cmd/go/internal/par"
"cmd/go/internal/trace"
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
const (
/ narrowAllVersionV is the Go version (plus leading "v") at which the
/ module-module "all" pattern no longer closes over the dependencies of
/ tests outside of the main module.
narrowAllVersionV = "v1.16"
/ ExplicitIndirectVersionV is the Go version (plus leading "v") at which a
/ module's go.mod file is expected to list explicit requirements on every
/ module that provides any package transitively imported by that module.
/
/ Other indirect dependencies of such a module can be safely pruned out of
/ the module graph; see https://golang.org/ref/mod#graph-pruning.
ExplicitIndirectVersionV = "v1.17"
/ separateIndirectVersionV is the Go version (plus leading "v") at which
/ "/ indirect" dependencies are added in a block separate from the direct
/ ones. See https://golang.org/issue/45965.
separateIndirectVersionV = "v1.17"
)
/ ReadModFile reads and parses the mod file at gomod. ReadModFile properly applies the
/ overlay, locks the file while reading, and applies fix, if applicable.
func ReadModFile(gomod string, fix modfile.VersionFixer) (data []byte, f *modfile.File, err error) {
if gomodActual, ok := fsys.OverlayPath(gomod); ok {
/ Don't lock go.mod if it's part of the overlay.
/ On Plan 9, locking requires chmod, and we don't want to modify any file
/ in the overlay. See #44700.
data, err = os.ReadFile(gomodActual)
} else {
data, err = lockedfile.Read(gomodActual)
}
if err != nil {
return nil, nil, err
}
f, err = modfile.Parse(gomod, data, fix)
if err != nil {
/ Errors returned by modfile.Parse begin with file:line.
return nil, nil, fmt.Errorf("errors parsing go.mod:\n%s\n", err)
}
if f.Module == nil {
/ No module declaration. Must add module path.
return nil, nil, errors.New("no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
}
return data, f, err
}
/ modFileGoVersion returns the (non-empty) Go version at which the requirements
/ in modFile are interpreted, or the latest Go version if modFile is nil.
func modFileGoVersion(modFile *modfile.File) string {
if modFile == nil {
return LatestGoVersion()
}
if modFile.Go == nil || modFile.Go.Version == "" {
/ The main module necessarily has a go.mod file, and that file lacks a
/ 'go' directive. The 'go' command has been adding that directive
/ automatically since Go 1.12, so this module either dates to Go 1.11 or
/ has been erroneously hand-edited.
/
/ The semantics of the go.mod file are more-or-less the same from Go 1.11
/ through Go 1.16, changing at 1.17 to support module graph pruning.
/ So even though a go.mod file without a 'go' directive is theoretically a
/ Go 1.11 file, scripts may assume that it ends up as a Go 1.16 module.
return "1.16"
}
return modFile.Go.Version
}
/ A modFileIndex is an index of data corresponding to a modFile
/ at a specific point in time.
type modFileIndex struct {
data []byte
dataNeedsFix bool / true if fixVersion applied a change while parsing data
module module.Version
goVersionV string / GoVersion with "v" prefix
require map[module.Version]requireMeta
replace map[module.Version]module.Version
exclude map[module.Version]bool
}
type requireMeta struct {
indirect bool
}
/ A modPruning indicates whether transitive dependencies of Go 1.17 dependencies
/ are pruned out of the module subgraph rooted at a given module.
/ (See https://golang.org/ref/mod#graph-pruning.)
type modPruning uint8
const (
pruned modPruning = iota / transitive dependencies of modules at go 1.17 and higher are pruned out
unpruned / no transitive dependencies are pruned out
workspace / pruned to the union of modules in the workspace
)
func pruningForGoVersion(goVersion string) modPruning {
if semver.Compare("v"+goVersion, ExplicitIndirectVersionV) < 0 {
/ The go.mod file does not duplicate relevant information about transitive
/ dependencies, so they cannot be pruned out.
return unpruned
}
return pruned
}
/ CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by
/ the main module's go.mod or retracted by its author. Most version queries use
/ this to filter out versions that should not be used.
func CheckAllowed(ctx context.Context, m module.Version) error {
if err := CheckExclusions(ctx, m); err != nil {
return err
}
if err := CheckRetractions(ctx, m); err != nil {
return err
}
return nil
}
/ ErrDisallowed is returned by version predicates passed to Query and similar
/ functions to indicate that a version should not be considered.
var ErrDisallowed = errors.New("disallowed module version")
/ CheckExclusions returns an error equivalent to ErrDisallowed if module m is
/ excluded by the main module's go.mod file.
func CheckExclusions(ctx context.Context, m module.Version) error {
for _, mainModule := range MainModules.Versions() {
if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
return module.VersionError(m, errExcluded)
}
}
return nil
}
var errExcluded = &excludedError{}
type excludedError struct{}
func (e *excludedError) Error() string { return "excluded by go.mod" }
func (e *excludedError) Is(err error) bool { return err == ErrDisallowed }
/ CheckRetractions returns an error if module m has been retracted by
/ its author.
func CheckRetractions(ctx context.Context, m module.Version) (err error) {
defer func() {
if retractErr := (*ModuleRetractedError)(nil); err == nil || errors.As(err, &retractErr) {
return
}
/ Attribute the error to the version being checked, not the version from
/ which the retractions were to be loaded.
if mErr := (*module.ModuleError)(nil); errors.As(err, &mErr) {
err = mErr.Err
}
err = &retractionLoadingError{m: m, err: err}
}()
if m.Version == "" {
/ Main module, standard library, or file replacement module.
/ Cannot be retracted.
return nil
}
if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
/ All versions of the module were replaced.
/ Don't load retractions, since we'd just load the replacement.
return nil
}
/ Find the latest available version of the module, and load its go.mod. If
/ the latest version is replaced, we'll load the replacement.
/
/ If there's an error loading the go.mod, we'll return it here. These errors
/ should generally be ignored by callers since they happen frequently when
/ we're offline. These errors are not equivalent to ErrDisallowed, so they
/ may be distinguished from retraction errors.
/
/ We load the raw file here: the go.mod file may have a different module
/ path that we expect if the module or its repository was renamed.
/ We still want to apply retractions to other aliases of the module.
rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return err
}
summary, err := rawGoModSummary(rm)
if err != nil {
return err
}
var rationale []string
isRetracted := false
for _, r := range summary.retract {
if semver.Compare(r.Low, m.Version) <= 0 && semver.Compare(m.Version, r.High) <= 0 {
isRetracted = true
if r.Rationale != "" {
rationale = append(rationale, r.Rationale)
}
}
}
if isRetracted {
return module.VersionError(m, &ModuleRetractedError{Rationale: rationale})
}
return nil
}
type ModuleRetractedError struct {
Rationale []string
}
func (e *ModuleRetractedError) Error() string {
msg := "retracted by module author"
if len(e.Rationale) > 0 {
/ This is meant to be a short error printed on a terminal, so just
/ print the first rationale.
msg += ": " + ShortMessage(e.Rationale[0], "retracted by module author")
}
return msg
}
func (e *ModuleRetractedError) Is(err error) bool {
return err == ErrDisallowed
}
type retractionLoadingError struct {
m module.Version
err error
}
func (e *retractionLoadingError) Error() string {
return fmt.Sprintf("loading module retractions for %v: %v", e.m, e.err)
}
func (e *retractionLoadingError) Unwrap() error {
return e.err
}
/ ShortMessage returns a string from go.mod (for example, a retraction
/ rationale or deprecation message) that is safe to print in a terminal.
/
/ If the given string is empty, ShortMessage returns the given default. If the
/ given string is too long or contains non-printable characters, ShortMessage
/ returns a hard-coded string.
func ShortMessage(message, emptyDefault string) string {
const maxLen = 500
if i := strings.Index(message, "\n"); i >= 0 {
message = message[:i]
}
message = strings.TrimSpace(message)
if message == "" {
return emptyDefault
}
if len(message) > maxLen {
return "(message omitted: too long)"
}
for _, r := range message {
if !unicode.IsGraphic(r) && !unicode.IsSpace(r) {
return "(message omitted: contains non-printable characters)"
}
}
/ NOTE: the go.mod parser rejects invalid UTF-8, so we don't check that here.
return message
}
/ CheckDeprecation returns a deprecation message from the go.mod file of the
/ latest version of the given module. Deprecation messages are comments
/ before or on the same line as the module directives that start with
/ "Deprecated:" and run until the end of the paragraph.
/
/ CheckDeprecation returns an error if the message can't be loaded.
/ CheckDeprecation returns "", nil if there is no deprecation message.
func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("loading deprecation for %s: %w", m.Path, err)
}
}()
if m.Version == "" {
/ Main module, standard library, or file replacement module.
/ Don't look up deprecation.
return "", nil
}
if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
/ All versions of the module were replaced.
/ We'll look up deprecation separately for the replacement.
return "", nil
}
latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return "", err
}
summary, err := rawGoModSummary(latest)
if err != nil {
return "", err
}
return summary.deprecated, nil
}
func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) {
if r, ok := replace[mod]; ok {
return mod.Version, r, true
}
if r, ok := replace[module.Version{Path: mod.Path}]; ok {
return "", r, true
}
return "", module.Version{}, false
}
/ Replacement returns the replacement for mod, if any. If the path in the
/ module.Version is relative it's relative to the single main module outside
/ workspace mode, or the workspace's directory in workspace mode.
func Replacement(mod module.Version) module.Version {
foundFrom, found, foundModRoot := "", module.Version{}, ""
if MainModules == nil {
return module.Version{}
} else if MainModules.Contains(mod.Path) && mod.Version == "" {
/ Don't replace the workspace version of the main module.
return module.Version{}
}
if _, r, ok := replacement(mod, MainModules.WorkFileReplaceMap()); ok {
return r
}
for _, v := range MainModules.Versions() {
if index := MainModules.Index(v); index != nil {
if from, r, ok := replacement(mod, index.replace); ok {
modRoot := MainModules.ModRoot(v)
if foundModRoot != "" && foundFrom != from && found != r {
base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
mod, modFilePath(foundModRoot), modFilePath(modRoot))
return canonicalizeReplacePath(found, foundModRoot)
}
found, foundModRoot = r, modRoot
}
}
}
return canonicalizeReplacePath(found, foundModRoot)
}
func replaceRelativeTo() string {
if workFilePath := WorkFilePath(); workFilePath != "" {
return filepath.Dir(workFilePath)
}
return MainModules.ModRoot(MainModules.mustGetSingleMainModule())
}
/ canonicalizeReplacePath ensures that relative, on-disk, replaced module paths
/ are relative to the workspace directory (in workspace mode) or to the module's
/ directory (in module mode, as they already are).
func canonicalizeReplacePath(r module.Version, modRoot string) module.Version {
if filepath.IsAbs(r.Path) || r.Version != "" {
return r
}
workFilePath := WorkFilePath()
if workFilePath == "" {
return r
}
abs := filepath.Join(modRoot, r.Path)
if rel, err := filepath.Rel(filepath.Dir(workFilePath), abs); err == nil {
return module.Version{Path: rel, Version: r.Version}
}
/ We couldn't make the version's path relative to the workspace's path,
/ so just return the absolute path. It's the best we can do.
return module.Version{Path: abs, Version: r.Version}
}
/ resolveReplacement returns the module actually used to load the source code
/ for m: either m itself, or the replacement for m (iff m is replaced).
/ It also returns the modroot of the module providing the replacement if
/ one was found.
func resolveReplacement(m module.Version) module.Version {
if r := Replacement(m); r.Path != "" {
return r
}
return m
}
func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version {
replaceMap := make(map[module.Version]module.Version, len(replacements))
for _, r := range replacements {
if prev, dup := replaceMap[r.Old]; dup && prev != r.New {
base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
}
replaceMap[r.Old] = r.New
}
return replaceMap
}
/ indexModFile rebuilds the index of modFile.
/ If modFile has been changed since it was first read,
/ modFile.Cleanup must be called before indexModFile.
func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
i := new(modFileIndex)
i.data = data
i.dataNeedsFix = needsFix
i.module = module.Version{}
if modFile.Module != nil {
i.module = modFile.Module.Mod
}
i.goVersionV = ""
if modFile.Go == nil {
rawGoVersion.Store(mod, "")
} else {
/ We're going to use the semver package to compare Go versions, so go ahead
/ and add the "v" prefix it expects once instead of every time.
i.goVersionV = "v" + modFile.Go.Version
rawGoVersion.Store(mod, modFile.Go.Version)
}
i.require = make(map[module.Version]requireMeta, len(modFile.Require))
for _, r := range modFile.Require {
i.require[r.Mod] = requireMeta{indirect: r.Indirect}
}
i.replace = toReplaceMap(modFile.Replace)
i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
for _, x := range modFile.Exclude {
i.exclude[x.Mod] = true
}
return i
}
/ modFileIsDirty reports whether the go.mod file differs meaningfully
/ from what was indexed.
/ If modFile has been changed (even cosmetically) since it was first read,
/ modFile.Cleanup must be called before modFileIsDirty.
func (i *modFileIndex) modFileIsDirty(modFile *modfile.File) bool {
if i == nil {
return modFile != nil
}
if i.dataNeedsFix {
return true
}
if modFile.Module == nil {
if i.module != (module.Version{}) {
return true
}
} else if modFile.Module.Mod != i.module {
return true
}
if modFile.Go == nil {
if i.goVersionV != "" {
return true
}
} else if "v"+modFile.Go.Version != i.goVersionV {
if i.goVersionV == "" && cfg.BuildMod != "mod" {
/ go.mod files did not always require a 'go' version, so do not error out
/ if one is missing — we may be inside an older module in the module
/ cache, and should bias toward providing useful behavior.
} else {
return true
}
}
if len(modFile.Require) != len(i.require) ||
len(modFile.Replace) != len(i.replace) ||
len(modFile.Exclude) != len(i.exclude) {
return true
}
for _, r := range modFile.Require {
if meta, ok := i.require[r.Mod]; !ok {
return true
} else if r.Indirect != meta.indirect {
if cfg.BuildMod == "readonly" {
/ The module's requirements are consistent; only the "/ indirect"
/ comments that are wrong. But those are only guaranteed to be accurate
/ after a "go mod tidy" — it's a good idea to run those before
/ committing a change, but it's certainly not mandatory.
} else {
return true
}
}
}
for _, r := range modFile.Replace {
if r.New != i.replace[r.Old] {
return true
}
}
for _, x := range modFile.Exclude {
if !i.exclude[x.Mod] {
return true
}
}
return false
}
/ rawGoVersion records the Go version parsed from each module's go.mod file.
/
/ If a module is replaced, the version of the replacement is keyed by the
/ replacement module.Version, not the version being replaced.
var rawGoVersion sync.Map / map[module.Version]string
/ A modFileSummary is a summary of a go.mod file for which we do not need to
/ retain complete information — for example, the go.mod file of a dependency
/ module.
type modFileSummary struct {
module module.Version
goVersion string
pruning modPruning
require []module.Version
retract []retraction
deprecated string
}
/ A retraction consists of a retracted version interval and rationale.
/ retraction is like modfile.Retract, but it doesn't point to the syntax tree.
type retraction struct {
modfile.VersionInterval
Rationale string
}
/ goModSummary returns a summary of the go.mod file for module m,
/ taking into account any replacements for m, exclusions of its dependencies,
/ and/or vendoring.
/
/ m must be a version in the module graph, reachable from the Target module.
/ In readonly mode, the go.sum file must contain an entry for m's go.mod file
/ (or its replacement). goModSummary must not be called for the Target module
/ itself, as its requirements may change. Use rawGoModSummary for other
/ module versions.
/
/ The caller must not modify the returned summary.
func goModSummary(m module.Version) (*modFileSummary, error) {
if m.Version == "" && !inWorkspaceMode() && MainModules.Contains(m.Path) {
panic("internal error: goModSummary called on a main module")
}
if cfg.BuildMod == "vendor" {
summary := &modFileSummary{
module: module.Version{Path: m.Path},
}
if vendorVersion[m.Path] != m.Version {
/ This module is not vendored, so packages cannot be loaded from it and
/ it cannot be relevant to the build.
return summary, nil
}
/ For every module other than the target,
/ return the full list of modules from modules.txt.
readVendorList(MainModules.mustGetSingleMainModule())
/ We don't know what versions the vendored module actually relies on,
/ so assume that it requires everything.
summary.require = vendorList
return summary, nil
}
actual := resolveReplacement(m)
if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
if !modfetch.HaveSum(key) {
suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
}
}
summary, err := rawGoModSummary(actual)
if err != nil {
return nil, err
}
if actual.Version == "" {
/ The actual module is a filesystem-local replacement, for which we have
/ unfortunately not enforced any sort of invariants about module lines or
/ matching module paths. Anything goes.
/
/ TODO(bcmills): Remove this special-case, update tests, and add a
/ release note.
} else {
if summary.module.Path == "" {
return nil, module.VersionError(actual, errors.New("parsing go.mod: missing module line"))
}
/ In theory we should only allow mpath to be unequal to m.Path here if the
/ version that we fetched lacks an explicit go.mod file: if the go.mod file
/ is explicit, then it should match exactly (to ensure that imports of other
/ packages within the module are interpreted correctly). Unfortunately, we
/ can't determine that information from the module proxy protocol: we'll have
/ to leave that validation for when we load actual packages from within the
/ module.
if mpath := summary.module.Path; mpath != m.Path && mpath != actual.Path {
return nil, module.VersionError(actual, fmt.Errorf(`parsing go.mod:
module declares its path as: %s
but was required as: %s`, mpath, m.Path))
}
}
for _, mainModule := range MainModules.Versions() {
if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
/ Drop any requirements on excluded versions.
/ Don't modify the cached summary though, since we might need the raw
/ summary separately.
haveExcludedReqs := false
for _, r := range summary.require {
if index.exclude[r] {
haveExcludedReqs = true
break
}
}
if haveExcludedReqs {
s := new(modFileSummary)
*s = *summary
s.require = make([]module.Version, 0, len(summary.require))
for _, r := range summary.require {
if !index.exclude[r] {
s.require = append(s.require, r)
}
}
summary = s
}
}
}
return summary, nil
}
/ rawGoModSummary returns a new summary of the go.mod file for module m,
/ ignoring all replacements that may apply to m and excludes that may apply to
/ its dependencies.
/
/ rawGoModSummary cannot be used on the Target module.
func rawGoModSummary(m module.Version) (*modFileSummary, error) {
if m.Path == "" && MainModules.Contains(m.Path) {
panic("internal error: rawGoModSummary called on the Target module")
}
type key struct {
m module.Version
}
type cached struct {
summary *modFileSummary
err error
}
c := rawGoModSummaryCache.Do(key{m}, func() any {
summary := new(modFileSummary)
name, data, err := rawGoModData(m)
if err != nil {
return cached{nil, err}
}
f, err := modfile.ParseLax(name, data, nil)
if err != nil {
return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))}
}
if f.Module != nil {
summary.module = f.Module.Mod
summary.deprecated = f.Module.Deprecated
}
if f.Go != nil && f.Go.Version != "" {
rawGoVersion.LoadOrStore(m, f.Go.Version)
summary.goVersion = f.Go.Version
summary.pruning = pruningForGoVersion(f.Go.Version)
} else {
summary.pruning = unpruned
}
if len(f.Require) > 0 {
summary.require = make([]module.Version, 0, len(f.Require))
for _, req := range f.Require {
summary.require = append(summary.require, req.Mod)
}
}
if len(f.Retract) > 0 {
summary.retract = make([]retraction, 0, len(f.Retract))
for _, ret := range f.Retract {
summary.retract = append(summary.retract, retraction{
VersionInterval: ret.VersionInterval,
Rationale: ret.Rationale,
})
}
}
return cached{summary, nil}
}).(cached)
return c.summary, c.err
}
var rawGoModSummaryCache par.Cache / module.Version → rawGoModSummary result
/ rawGoModData returns the content of the go.mod file for module m, ignoring
/ all replacements that may apply to m.
/
/ rawGoModData cannot be used on the Target module.
/
/ Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
/ Use rawGoModSummary instead unless you specifically need these bytes.
func rawGoModData(m module.Version) (name string, data []byte, err error) {
if m.Version == "" {
/ m is a replacement module with only a file path.
dir := m.Path
if !filepath.IsAbs(dir) {
if inWorkspaceMode() && MainModules.Contains(m.Path) {
dir = MainModules.ModRoot(m)
} else {
dir = filepath.Join(replaceRelativeTo(), dir)
}
}
name = filepath.Join(dir, "go.mod")
if gomodActual, ok := fsys.OverlayPath(name); ok {
/ Don't lock go.mod if it's part of the overlay.
/ On Plan 9, locking requires chmod, and we don't want to modify any file
/ in the overlay. See #44700.
data, err = os.ReadFile(gomodActual)
} else {
data, err = lockedfile.Read(gomodActual)
}
if err != nil {
return "", nil, module.VersionError(m, fmt.Errorf("reading %s: %v", base.ShortPath(name), err))
}
} else {
if !semver.IsValid(m.Version) {
/ Disallow the broader queries supported by fetch.Lookup.
base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", m.Path, m.Version)
}
name = "go.mod"
data, err = modfetch.GoMod(m.Path, m.Version)
}
return name, data, err
}
/ queryLatestVersionIgnoringRetractions looks up the latest version of the
/ module with the given path without considering retracted or excluded
/ versions.
/
/ If all versions of the module are replaced,
/ queryLatestVersionIgnoringRetractions returns the replacement without making
/ a query.
/
/ If the queried latest version is replaced,
/ queryLatestVersionIgnoringRetractions returns the replacement.
func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
type entry struct {
latest module.Version
err error
}
e := latestVersionIgnoringRetractionsCache.Do(path, func() any {
ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
defer span.Done()
if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
/ All versions of the module were replaced.
/ No need to query.
return &entry{latest: repl}
}
/ Find the latest version of the module.
/ Ignore exclusions from the main module's go.mod.
const ignoreSelected = ""
var allowAll AllowedFunc
rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
if err != nil {
return &entry{err: err}
}
latest := module.Version{Path: path, Version: rev.Version}
if repl := resolveReplacement(latest); repl.Path != "" {
latest = repl
}
return &entry{latest: latest}
}).(*entry)
return e.latest, e.err
}
var latestVersionIgnoringRetractionsCache par.Cache / path → queryLatestVersionIgnoringRetractions result
/ ToDirectoryPath adds a prefix if necessary so that path in unambiguously
/ an absolute path or a relative path starting with a '.' or '..'
/ path component.
func ToDirectoryPath(path string) string {
if path == "." || modfile.IsDirectoryPath(path) {
return path
}
/ The path is not a relative path or an absolute path, so make it relative
/ to the current directory.
return "./" + filepath.ToSlash(filepath.Clean(path))
}