blob: 9400793bcb253b3e7547ea7e6773b22f9d3c4bfb [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 (
"errors"
"fmt"
"os"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/modinfo"
"cmd/go/internal/par"
"cmd/go/internal/search"
"golang.org/x/mod/module"
)
func ListModules(args []string, listU, listVersions bool) []*modinfo.ModulePublic {
mods := listModules(args, listVersions)
if listU || listVersions {
var work par.Work
for _, m := range mods {
work.Add(m)
if m.Replace != nil {
work.Add(m.Replace)
}
}
work.Do(10, func(item interface{}) {
m := item.(*modinfo.ModulePublic)
if listU {
addUpdate(m)
}
if listVersions {
addVersions(m)
}
})
}
return mods
}
func listModules(args []string, listVersions bool) []*modinfo.ModulePublic {
LoadBuildList()
if len(args) == 0 {
return []*modinfo.ModulePublic{moduleInfo(buildList[0], true)}
}
var mods []*modinfo.ModulePublic
matchedBuildList := make([]bool, len(buildList))
for _, arg := range args {
if strings.Contains(arg, `\`) {
base.Fatalf("go: module paths never use backslash")
}
if search.IsRelativePath(arg) {
base.Fatalf("go: cannot use relative path %s to specify module", arg)
}
if !HasModRoot() && (arg == "all" || strings.Contains(arg, "...")) {
base.Fatalf("go: cannot match %q: working directory is not part of a module", arg)
}
if i := strings.Index(arg, "@"); i >= 0 {
path := arg[:i]
vers := arg[i+1:]
var current string
for _, m := range buildList {
if m.Path == path {
current = m.Version
break
}
}
info, err := Query(path, vers, current, nil)
if err != nil {
mods = append(mods, &modinfo.ModulePublic{
Path: path,
Version: vers,
Error: modinfoError(path, vers, err),
})
continue
}
mods = append(mods, moduleInfo(module.Version{Path: path, Version: info.Version}, false))
continue
}
// Module path or pattern.
var match func(string) bool
var literal bool
if arg == "all" {
match = func(string) bool { return true }
} else if strings.Contains(arg, "...") {
match = search.MatchPattern(arg)
} else {
match = func(p string) bool { return arg == p }
literal = true
}
matched := false
for i, m := range buildList {
if i == 0 && !HasModRoot() {
// The root module doesn't actually exist: omit it.
continue
}
if match(m.Path) {
matched = true
if !matchedBuildList[i] {
matchedBuildList[i] = true
mods = append(mods, moduleInfo(m, true))
}
}
}
if !matched {
if literal {
if listVersions {
// Don't make the user provide an explicit '@latest' when they're
// explicitly asking what the available versions are.
// Instead, resolve the module, even if it isn't an existing dependency.
info, err := Query(arg, "latest", "", nil)
if err == nil {
mods = append(mods, moduleInfo(module.Version{Path: arg, Version: info.Version}, false))
} else {
mods = append(mods, &modinfo.ModulePublic{
Path: arg,
Error: modinfoError(arg, "", err),
})
}
continue
}
if cfg.BuildMod == "vendor" {
// In vendor mode, we can't determine whether a missing module is “a
// known dependency” because the module graph is incomplete.
// Give a more explicit error message.
mods = append(mods, &modinfo.ModulePublic{
Path: arg,
Error: modinfoError(arg, "", errors.New("can't resolve module using the vendor directory\n\t(Use -mod=mod or -mod=readonly to bypass.)")),
})
} else {
mods = append(mods, &modinfo.ModulePublic{
Path: arg,
Error: modinfoError(arg, "", errors.New("not a known dependency")),
})
}
} else {
fmt.Fprintf(os.Stderr, "warning: pattern %q matched no module dependencies\n", arg)
}
}
}
return mods
}
// modinfoError wraps an error to create an error message in
// modinfo.ModuleError with minimal redundancy.
func modinfoError(path, vers string, err error) *modinfo.ModuleError {
var nerr *NoMatchingVersionError
var merr *module.ModuleError
if errors.As(err, &nerr) {
// NoMatchingVersionError contains the query, so we don't mention the
// query again in ModuleError.
err = &module.ModuleError{Path: path, Err: err}
} else if !errors.As(err, &merr) {
// If the error does not contain path and version, wrap it in a
// module.ModuleError.
err = &module.ModuleError{Path: path, Version: vers, Err: err}
}
return &modinfo.ModuleError{Err: err.Error()}
}