blob: 8c1fac2df458c22ef5eb246d67e6a604eb41c3c0 [file] [log] [blame]
// Copyright 2018 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 (
"bytes"
"errors"
"fmt"
"go/build"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/imports"
"cmd/go/internal/modfetch"
"cmd/go/internal/modfile"
"cmd/go/internal/module"
"cmd/go/internal/mvs"
"cmd/go/internal/par"
"cmd/go/internal/search"
"cmd/go/internal/semver"
)
// buildList is the list of modules to use for building packages.
// It is initialized by calling ImportPaths, ImportFromFiles,
// LoadALL, or LoadBuildList, each of which uses loaded.load.
//
// Ideally, exactly ONE of those functions would be called,
// and exactly once. Most of the time, that's true.
// During "go get" it may not be. TODO(rsc): Figure out if
// that restriction can be established, or else document why not.
//
var buildList []module.Version
// loaded is the most recently-used package loader.
// It holds details about individual packages.
//
// Note that loaded.buildList is only valid during a load operation;
// afterward, it is copied back into the global buildList,
// which should be used instead.
var loaded *loader
// ImportPaths returns the set of packages matching the args (patterns),
// adding modules to the build list as needed to satisfy new imports.
func ImportPaths(args []string) []string {
if Init(); !Enabled() {
return search.ImportPaths(args)
}
InitMod()
cleaned := search.CleanImportPaths(args)
loaded = newLoader()
var paths []string
loaded.load(func() []string {
var roots []string
paths = nil
for _, pkg := range cleaned {
switch {
case build.IsLocalImport(pkg):
list := []string{pkg}
if strings.Contains(pkg, "...") {
// TODO: Where is the go.mod cutoff?
list = warnPattern(pkg, search.AllPackagesInFS(pkg))
}
for _, pkg := range list {
dir := filepath.Join(cwd, pkg)
if dir == ModRoot {
pkg = Target.Path
} else if strings.HasPrefix(dir, ModRoot+string(filepath.Separator)) {
suffix := filepath.ToSlash(dir[len(ModRoot):])
if strings.HasPrefix(suffix, "/vendor/") {
// TODO getmode vendor check
pkg = strings.TrimPrefix(suffix, "/vendor/")
} else {
pkg = Target.Path + suffix
}
} else {
base.Errorf("go: package %s outside module root", pkg)
continue
}
roots = append(roots, pkg)
paths = append(paths, pkg)
}
case pkg == "all":
if loaded.testRoots {
loaded.testAll = true
}
// TODO: Don't print warnings multiple times.
roots = append(roots, warnPattern("all", matchPackages("...", loaded.tags, []module.Version{Target}))...)
paths = append(paths, "all") // will expand after load completes
case search.IsMetaPackage(pkg): // std, cmd
fmt.Fprintf(os.Stderr, "go: warning: %q matches no packages when using modules\n", pkg)
case strings.Contains(pkg, "..."):
// TODO: Don't we need to reevaluate this one last time once the build list stops changing?
list := warnPattern(pkg, matchPackages(pkg, loaded.tags, buildList))
roots = append(roots, list...)
paths = append(paths, list...)
default:
roots = append(roots, pkg)
paths = append(paths, pkg)
}
}
return roots
})
WriteGoMod()
// Process paths to produce final paths list.
// Remove duplicates and expand "all".
have := make(map[string]bool)
var final []string
for _, path := range paths {
if have[path] {
continue
}
have[path] = true
if path == "all" {
for _, pkg := range loaded.pkgs {
if !have[pkg.path] {
have[pkg.path] = true
final = append(final, pkg.path)
}
}
continue
}
final = append(final, path)
}
return final
}
// warnPattern returns list, the result of matching pattern,
// but if list is empty then first it prints a warning about
// the pattern not matching any packages.
func warnPattern(pattern string, list []string) []string {
if len(list) == 0 {
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
}
return list
}
// ImportFromFiles adds modules to the build list as needed
// to satisfy the imports in the named Go source files.
func ImportFromFiles(gofiles []string) {
if Init(); !Enabled() {
return
}
InitMod()
imports, testImports, err := imports.ScanFiles(gofiles, imports.Tags())
if err != nil {
base.Fatalf("go: %v", err)
}
loaded = newLoader()
loaded.load(func() []string {
var roots []string
roots = append(roots, imports...)
roots = append(roots, testImports...)
return roots
})
WriteGoMod()
}
// LoadBuildList loads and returns the build list from go.mod.
// The loading of the build list happens automatically in ImportPaths:
// LoadBuildList need only be called if ImportPaths is not
// (typically in commands that care about the module but
// no particular package).
func LoadBuildList() []module.Version {
if Init(); !Enabled() {
base.Fatalf("go: LoadBuildList called but modules not enabled")
}
InitMod()
ReloadBuildList()
WriteGoMod()
return buildList
}
func ReloadBuildList() []module.Version {
loaded = newLoader()
loaded.load(func() []string { return nil })
return buildList
}
// LoadALL returns the set of all packages in the current module
// and their dependencies in any other modules, without filtering
// due to build tags, except "+build ignore".
// It adds modules to the build list as needed to satisfy new imports.
// This set is useful for deciding whether a particular import is needed
// anywhere in a module.
func LoadALL() []string {
return loadAll(true)
}
// LoadVendor is like LoadALL but only follows test dependencies
// for tests in the main module. Tests in dependency modules are
// ignored completely.
// This set is useful for identifying the which packages to include in a vendor directory.
func LoadVendor() []string {
return loadAll(false)
}
func loadAll(testAll bool) []string {
if Init(); !Enabled() {
panic("go: misuse of LoadALL/LoadVendor")
}
InitMod()
loaded = newLoader()
loaded.isALL = true
loaded.tags = anyTags
loaded.testAll = testAll
if !testAll {
loaded.testRoots = true
}
all := TargetPackages()
loaded.load(func() []string { return all })
WriteGoMod()
var paths []string
for _, pkg := range loaded.pkgs {
paths = append(paths, pkg.path)
}
return paths
}
// anyTags is a special tags map that satisfies nearly all build tag expressions.
// Only "ignore" and malformed build tag requirements are considered false.
var anyTags = map[string]bool{"*": true}
// TargetPackages returns the list of packages in the target (top-level) module,
// under all build tag settings.
func TargetPackages() []string {
return matchPackages("...", anyTags, []module.Version{Target})
}
// BuildList returns the module build list,
// typically constructed by a previous call to
// LoadBuildList or ImportPaths.
// The caller must not modify the returned list.
func BuildList() []module.Version {
return buildList
}
// SetBuildList sets the module build list.
// The caller is responsible for ensuring that the list is valid.
// SetBuildList does not retain a reference to the original list.
func SetBuildList(list []module.Version) {
buildList = append([]module.Version{}, list...)
}
// ImportMap returns the actual package import path
// for an import path found in source code.
// If the given import path does not appear in the source code
// for the packages that have been loaded, ImportMap returns the empty string.
func ImportMap(path string) string {
pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
if !ok {
return ""
}
return pkg.path
}
// PackageDir returns the directory containing the source code
// for the package named by the import path.
func PackageDir(path string) string {
pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
if !ok {
return ""
}
return pkg.dir
}
// PackageModule returns the module providing the package named by the import path.
func PackageModule(path string) module.Version {
pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
if !ok {
return module.Version{}
}
return pkg.mod
}
// ModuleUsedDirectly reports whether the main module directly imports
// some package in the module with the given path.
func ModuleUsedDirectly(path string) bool {
return loaded.direct[path]
}
// Lookup XXX TODO.
func Lookup(parentPath, path string) (dir, realPath string, err error) {
realPath = ImportMap(path)
if realPath == "" {
if isStandardImportPath(path) {
dir := filepath.Join(cfg.GOROOT, "src", path)
if _, err := os.Stat(dir); err == nil {
return dir, path, nil
}
}
return "", "", fmt.Errorf("no such package in module")
}
return PackageDir(realPath), realPath, nil
}
// A loader manages the process of loading information about
// the required packages for a particular build,
// checking that the packages are available in the module set,
// and updating the module set if needed.
// Loading is an iterative process: try to load all the needed packages,
// but if imports are missing, try to resolve those imports, and repeat.
//
// Although most of the loading state is maintained in the loader struct,
// one key piece - the build list - is a global, so that it can be modified
// separate from the loading operation, such as during "go get"
// upgrades/downgrades or in "go mod" operations.
// TODO(rsc): It might be nice to make the loader take and return
// a buildList rather than hard-coding use of the global.
type loader struct {
tags map[string]bool // tags for scanDir
testRoots bool // include tests for roots
isALL bool // created with LoadALL
testAll bool // include tests for all packages
// missingMu protects found, but also buildList, modFile
missingMu sync.Mutex
found map[string]bool
// reset on each iteration
roots []*loadPkg
pkgs []*loadPkg
work *par.Work // current work queue
pkgCache *par.Cache // map from string to *loadPkg
missing *par.Work // missing work queue
// computed at end of iterations
direct map[string]bool // imported directly by main module
}
func newLoader() *loader {
ld := new(loader)
ld.tags = imports.Tags()
ld.found = make(map[string]bool)
switch cfg.CmdName {
case "test", "vet":
ld.testRoots = true
}
return ld
}
func (ld *loader) reset() {
ld.roots = nil
ld.pkgs = nil
ld.work = new(par.Work)
ld.pkgCache = new(par.Cache)
ld.missing = nil
}
// A loadPkg records information about a single loaded package.
type loadPkg struct {
path string // import path
mod module.Version // module providing package
dir string // directory containing source code
imports []*loadPkg // packages imported by this one
err error // error loading package
stack *loadPkg // package importing this one in minimal import stack for this pkg
test *loadPkg // package with test imports, if we need test
testOf *loadPkg
testImports []string // test-only imports, saved for use by pkg.test.
}
var errMissing = errors.New("cannot find package")
// load attempts to load the build graph needed to process a set of root packages.
// The set of root packages is defined by the addRoots function,
// which must call add(path) with the import path of each root package.
func (ld *loader) load(roots func() []string) {
var err error
buildList, err = mvs.BuildList(Target, Reqs())
if err != nil {
base.Fatalf("go: %v", err)
}
for {
ld.reset()
if roots != nil {
// Note: the returned roots can change on each iteration,
// since the expansion of package patterns depends on the
// build list we're using.
for _, path := range roots() {
ld.work.Add(ld.pkg(path, true))
}
}
ld.work.Do(10, ld.doPkg)
ld.buildStacks()
for _, pkg := range ld.pkgs {
if pkg.err == errMissing {
if ld.missing == nil {
ld.missing = new(par.Work)
}
ld.missing.Add(pkg)
} else if pkg.err != nil {
base.Errorf("go: %s: %s", pkg.stackText(), pkg.err)
}
}
if ld.missing == nil {
break
}
ld.missing.Do(10, ld.findMissing)
base.ExitIfErrors()
buildList, err = mvs.BuildList(Target, Reqs())
if err != nil {
base.Fatalf("go: %v", err)
}
}
base.ExitIfErrors()
// Compute directly referenced dependency modules.
ld.direct = make(map[string]bool)
for _, pkg := range ld.pkgs {
if pkg.mod == Target {
for _, dep := range pkg.imports {
if dep.mod.Path != "" {
ld.direct[dep.mod.Path] = true
}
}
}
}
// Mix in direct markings (really, lack of indirect markings)
// from go.mod, unless we scanned the whole module
// and can therefore be sure we know better than go.mod.
if !ld.isALL && modFile != nil {
for _, r := range modFile.Require {
if !r.Indirect {
ld.direct[r.Mod.Path] = true
}
}
}
}
// pkg returns the *loadPkg for path, creating and queuing it if needed.
// If the package should be tested, its test is created but not queued
// (the test is queued after processing pkg).
// If isRoot is true, the pkg is being queued as one of the roots of the work graph.
func (ld *loader) pkg(path string, isRoot bool) *loadPkg {
return ld.pkgCache.Do(path, func() interface{} {
pkg := &loadPkg{
path: path,
}
if ld.testRoots && isRoot || ld.testAll {
test := &loadPkg{
path: path,
testOf: pkg,
}
pkg.test = test
}
if isRoot {
ld.roots = append(ld.roots, pkg)
}
ld.work.Add(pkg)
return pkg
}).(*loadPkg)
}
// doPkg processes a package on the work queue.
func (ld *loader) doPkg(item interface{}) {
// TODO: what about replacements?
pkg := item.(*loadPkg)
var imports []string
if pkg.testOf != nil {
pkg.dir = pkg.testOf.dir
pkg.mod = pkg.testOf.mod
imports = pkg.testOf.testImports
} else {
pkg.dir, pkg.mod, pkg.err = ld.findDir(pkg.path)
if pkg.dir == "" {
return
}
var testImports []string
var err error
imports, testImports, err = scanDir(pkg.dir, ld.tags)
if err != nil {
if strings.HasPrefix(err.Error(), "no Go ") {
// Don't print about directories with no Go source files.
// Let the eventual real package load do that.
return
}
pkg.err = err
return
}
if pkg.test != nil {
pkg.testImports = testImports
}
}
for _, path := range imports {
pkg.imports = append(pkg.imports, ld.pkg(path, false))
}
// Now that pkg.dir, pkg.mod, pkg.testImports are set, we can queue pkg.test.
// TODO: All that's left is creating new imports. Why not just do it now?
if pkg.test != nil {
ld.work.Add(pkg.test)
}
}
// importPathInModule reports whether, syntactically,
// a package with the given import path could be supplied
// by a module with the given module path (mpath).
func importPathInModule(path, mpath string) bool {
return mpath == path ||
len(path) > len(mpath) && path[len(mpath)] == '/' && path[:len(mpath)] == mpath
}
// findDir finds the directory holding source code for the given import path.
// It returns the directory, the module containing the directory,
// and any error encountered.
// It is possible to return successfully (err == nil) with an empty directory,
// for built-in packages like "unsafe" and "C".
// It is also possible to return successfully with a zero module.Version,
// for packages in the standard library or when using vendored code.
func (ld *loader) findDir(path string) (dir string, mod module.Version, err error) {
// Is the package in the standard library?
if search.IsStandardImportPath(path) {
if path == "C" || path == "unsafe" {
// There's no directory for import "C" or import "unsafe".
return "", module.Version{}, nil
}
if strings.HasPrefix(path, "golang_org/") {
return filepath.Join(cfg.GOROOT, "src/vendor", path), module.Version{}, nil
}
dir := filepath.Join(cfg.GOROOT, "src", path)
if _, err := os.Stat(dir); err == nil {
return dir, module.Version{}, nil
}
}
// Is the package in the main module?
// Note that having the main module path as a prefix
// does not guarantee that the package is in the
// main module. It might still be supplied by some
// other module. For example, this might be
// module x/y, and we might be looking for x/y/v2/z.
// or maybe x/y/z/w in separate module x/y/z.
var mainDir string
if importPathInModule(path, Target.Path) {
mainDir = ModRoot
if len(path) > len(Target.Path) {
mainDir = filepath.Join(ModRoot, path[len(Target.Path)+1:])
}
if _, err := os.Stat(mainDir); err == nil {
return mainDir, Target, nil
}
}
// With -getmode=vendor, we expect everything else to be in vendor.
if cfg.BuildGetmode == "vendor" {
// Using -getmode=vendor, everything the module needs
// (beyond the current module and standard library)
// must be in the module's vendor directory.
// If the package exists in vendor, use it.
// If the package is not covered by the main module (mainDir == ""), use vendor.
// Otherwise, if the package could be in either place but is in neither, report the main module.
vendorDir := filepath.Join(ModRoot, "vendor", path)
if _, err := os.Stat(vendorDir); err == nil || mainDir == "" {
// TODO(rsc): We could look up the module information from vendor/modules.txt.
return vendorDir, module.Version{}, nil
}
return mainDir, Target, nil
}
// Scan all the possible modules that might contain this package,
// and complain if there are multiple choices. This correctly handles
// module boundaries that change over time, detecting mismatched
// module version pairings.
// (See comment about module paths in modfetch/repo.go.)
var mod1 module.Version
var dir1 string
for _, mod := range buildList {
if !importPathInModule(path, mod.Path) {
continue
}
dir, err := fetch(mod)
if err != nil {
return "", module.Version{}, err
}
if len(path) > len(mod.Path) {
dir = filepath.Join(dir, path[len(mod.Path)+1:])
}
if dir1 != "" {
return "", module.Version{}, fmt.Errorf("found in both %v@%v and %v@%v", mod1.Path, mod1.Version, mod.Path, mod.Version)
}
dir1 = dir
mod1 = mod
}
if dir1 != "" {
return dir1, mod1, nil
}
return "", module.Version{}, errMissing
}
func (ld *loader) findMissing(item interface{}) {
pkg := item.(*loadPkg)
path := pkg.path
if build.IsLocalImport(path) {
base.Errorf("go: relative import is not supported: %s", path)
}
// TODO: This is wrong (if path = foo/v2/bar and m.Path is foo,
// maybe we should fall through to the loop at the bottom and check foo/v2).
ld.missingMu.Lock()
for _, m := range buildList {
if importPathInModule(path, m.Path) {
ld.missingMu.Unlock()
return
}
}
ld.missingMu.Unlock()
fmt.Fprintf(os.Stderr, "resolving import %q\n", path)
repo, info, err := modfetch.Import(path, Allowed)
if err != nil {
base.Errorf("go: %s: %v", pkg.stackText(), err)
return
}
root := repo.ModulePath()
ld.missingMu.Lock()
defer ld.missingMu.Unlock()
// Double-check before adding repo twice.
for _, m := range buildList {
if importPathInModule(path, m.Path) {
return
}
}
fmt.Fprintf(os.Stderr, "go: finding %s (latest)\n", root)
if ld.found[path] {
base.Fatalf("internal error: findMissing loop on %s", path)
}
ld.found[path] = true
fmt.Fprintf(os.Stderr, "go: adding %s %s\n", root, info.Version)
buildList = append(buildList, module.Version{Path: root, Version: info.Version})
modFile.AddRequire(root, info.Version)
}
// scanDir is like imports.ScanDir but elides known magic imports from the list,
// so that we do not go looking for packages that don't really exist.
//
// The standard magic import is "C", for cgo.
//
// The only other known magic imports are appengine and appengine/*.
// These are so old that they predate "go get" and did not use URL-like paths.
// Most code today now uses google.golang.org/appengine instead,
// but not all code has been so updated. When we mostly ignore build tags
// during "go vendor", we look into "// +build appengine" files and
// may see these legacy imports. We drop them so that the module
// search does not look for modules to try to satisfy them.
func scanDir(dir string, tags map[string]bool) (imports_, testImports []string, err error) {
imports_, testImports, err = imports.ScanDir(dir, tags)
filter := func(x []string) []string {
w := 0
for _, pkg := range x {
if pkg != "C" && pkg != "appengine" && !strings.HasPrefix(pkg, "appengine/") &&
pkg != "appengine_internal" && !strings.HasPrefix(pkg, "appengine_internal/") {
x[w] = pkg
w++
}
}
return x[:w]
}
return filter(imports_), filter(testImports), err
}
// buildStacks computes minimal import stacks for each package,
// for use in error messages. When it completes, packages that
// are part of the original root set have pkg.stack == nil,
// and other packages have pkg.stack pointing at the next
// package up the import stack in their minimal chain.
// As a side effect, buildStacks also constructs ld.pkgs,
// the list of all packages loaded.
func (ld *loader) buildStacks() {
if len(ld.pkgs) > 0 {
panic("buildStacks")
}
for _, pkg := range ld.roots {
pkg.stack = pkg // sentinel to avoid processing in next loop
ld.pkgs = append(ld.pkgs, pkg)
}
for i := 0; i < len(ld.pkgs); i++ { // not range: appending to ld.pkgs in loop
pkg := ld.pkgs[i]
for _, next := range pkg.imports {
if next.stack == nil {
next.stack = pkg
ld.pkgs = append(ld.pkgs, next)
}
}
if next := pkg.test; next != nil && next.stack == nil {
next.stack = pkg
ld.pkgs = append(ld.pkgs, next)
}
}
for _, pkg := range ld.roots {
pkg.stack = nil
}
}
// stackText builds the import stack text to use when
// reporting an error in pkg. It has the general form
//
// import root ->
// import other ->
// import other2 ->
// import pkg
//
func (pkg *loadPkg) stackText() string {
var stack []*loadPkg
for p := pkg.stack; p != nil; p = p.stack {
stack = append(stack, p)
}
var buf bytes.Buffer
for i := len(stack) - 1; i >= 0; i-- {
p := stack[i]
if p.testOf != nil {
fmt.Fprintf(&buf, "test ->\n\t")
} else {
fmt.Fprintf(&buf, "import %q ->\n\t", p.path)
}
}
fmt.Fprintf(&buf, "import %q", pkg.path)
return buf.String()
}
// Replacement returns the replacement for mod, if any, from go.mod.
// If there is no replacement for mod, Replacement returns
// a module.Version with Path == "".
func Replacement(mod module.Version) module.Version {
var found *modfile.Replace
for _, r := range modFile.Replace {
if r.Old == mod {
found = r // keep going
}
}
if found == nil {
return module.Version{}
}
return found.New
}
// mvsReqs implements mvs.Reqs for module semantic versions,
// with any exclusions or replacements applied internally.
type mvsReqs struct {
buildList []module.Version
cache par.Cache
}
// Reqs returns the current module requirement graph.
// Future calls to SetBuildList do not affect the operation
// of the returned Reqs.
func Reqs() mvs.Reqs {
r := &mvsReqs{
buildList: buildList,
}
return r
}
func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
type cached struct {
list []module.Version
err error
}
c := r.cache.Do(mod, func() interface{} {
list, err := r.required(mod)
if err != nil {
return cached{nil, err}
}
for i, mv := range list {
for excluded[mv] {
mv1, err := r.next(mv)
if err != nil {
return cached{nil, err}
}
if mv1.Version == "none" {
return cached{nil, fmt.Errorf("%s(%s) depends on excluded %s(%s) with no newer version available", mod.Path, mod.Version, mv.Path, mv.Version)}
}
mv = mv1
}
list[i] = mv
}
return cached{list, nil}
}).(cached)
return c.list, c.err
}
func (r *mvsReqs) required(mod module.Version) ([]module.Version, error) {
if mod == Target {
var list []module.Version
list = append(list, r.buildList[1:]...)
return list, nil
}
origPath := mod.Path
if repl := Replacement(mod); repl.Path != "" {
if repl.Version == "" {
// TODO: need to slip the new version into the tags list etc.
dir := repl.Path
if !filepath.IsAbs(dir) {
dir = filepath.Join(ModRoot, dir)
}
gomod := filepath.Join(dir, "go.mod")
data, err := ioutil.ReadFile(gomod)
if err != nil {
return nil, err
}
f, err := modfile.Parse(gomod, data, nil)
if err != nil {
return nil, err
}
var list []module.Version
for _, r := range f.Require {
list = append(list, r.Mod)
}
return list, nil
}
mod = repl
}
if mod.Version == "none" {
return nil, nil
}
if !semver.IsValid(mod.Version) {
// Disallow the broader queries supported by fetch.Lookup.
panic(fmt.Errorf("invalid semantic version %q for %s", mod.Version, mod.Path))
// TODO: return nil, fmt.Errorf("invalid semantic version %q", mod.Version)
}
data, err := modfetch.GoMod(mod.Path, mod.Version)
if err != nil {
base.Errorf("go: %s %s: %v\n", mod.Path, mod.Version, err)
return nil, err
}
f, err := modfile.Parse("go.mod", data, nil)
if err != nil {
return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
}
if f.Module == nil {
return nil, fmt.Errorf("%v@%v go.mod: missing module line", mod.Path, mod.Version)
}
if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
return nil, fmt.Errorf("downloaded %q and got module %q", mod.Path, mpath)
}
var list []module.Version
for _, req := range f.Require {
list = append(list, req.Mod)
}
if false {
fmt.Fprintf(os.Stderr, "REQLIST %v:\n", mod)
for _, req := range list {
fmt.Fprintf(os.Stderr, "\t%v\n", req)
}
}
return list, nil
}
func (*mvsReqs) Max(v1, v2 string) string {
if v1 != "" && semver.Compare(v1, v2) == -1 {
return v2
}
return v1
}
// Upgrade is a no-op, here to implement mvs.Reqs.
// The upgrade logic for go get -u is in ../modget/get.go.
func (*mvsReqs) Upgrade(m module.Version) (module.Version, error) {
return m, nil
}
func versions(path string) ([]string, error) {
// Note: modfetch.Lookup and repo.Versions are cached,
// so there's no need for us to add extra caching here.
repo, err := modfetch.Lookup(path)
if err != nil {
return nil, err
}
return repo.Versions("")
}
// Previous returns the tagged version of m.Path immediately prior to
// m.Version, or version "none" if no prior version is tagged.
func (*mvsReqs) Previous(m module.Version) (module.Version, error) {
list, err := versions(m.Path)
if err != nil {
return module.Version{}, err
}
i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) >= 0 })
if i > 0 {
return module.Version{Path: m.Path, Version: list[i-1]}, nil
}
return module.Version{Path: m.Path, Version: "none"}, nil
}
// next returns the next version of m.Path after m.Version.
// It is only used by the exclusion processing in the Required method,
// not called directly by MVS.
func (*mvsReqs) next(m module.Version) (module.Version, error) {
list, err := versions(m.Path)
if err != nil {
return module.Version{}, err
}
i := sort.Search(len(list), func(i int) bool { return semver.Compare(list[i], m.Version) > 0 })
if i < len(list) {
return module.Version{Path: m.Path, Version: list[i]}, nil
}
return module.Version{Path: m.Path, Version: "none"}, nil
}
func fetch(mod module.Version) (dir string, err error) {
if r := Replacement(mod); r.Path != "" {
if r.Version == "" {
dir = r.Path
if !filepath.IsAbs(dir) {
dir = filepath.Join(ModRoot, dir)
}
return dir, nil
}
mod = r
}
return modfetch.Download(mod)
}