updateMatches := func(matches []*search.Match, iterating bool) {
for i, m := range matches {
switch {
- case build.IsLocalImport(m.Pattern) || filepath.IsAbs(m.Pattern):
+ case m.IsLocal():
// Evaluate list of file system directories on first iteration.
if fsDirs == nil {
fsDirs = make([][]string, len(matches))
}
if fsDirs[i] == nil {
- var dirs []string
- if m.Literal {
- dirs = []string{m.Pattern}
+ if m.IsLiteral() {
+ fsDirs[i] = []string{m.Pattern()}
} else {
- match := search.MatchPackagesInFS(m.Pattern)
- dirs = match.Pkgs
- m.Errs = match.Errs
+ m.MatchPackagesInFS()
+ // Pull out the matching directories: we are going to resolve them
+ // to package paths below.
+ fsDirs[i], m.Pkgs = m.Pkgs, nil
}
- fsDirs[i] = dirs
}
// Make a copy of the directory list and translate to import paths.
// from not being in the build list to being in it and back as
// the exact version of a particular module increases during
// the loader iterations.
- m.Pkgs = str.StringList(fsDirs[i])
- pkgs := m.Pkgs
- m.Pkgs = m.Pkgs[:0]
+ pkgs := str.StringList(fsDirs[i])
+ m.Pkgs = pkgs[:0]
for _, pkg := range pkgs {
var dir string
if !filepath.IsAbs(pkg) {
m.Pkgs = append(m.Pkgs, pkg)
}
- case strings.Contains(m.Pattern, "..."):
- m.Pkgs = matchPackages(m.Pattern, loaded.tags, true, buildList)
+ case m.IsLiteral():
+ m.Pkgs = []string{m.Pattern()}
- case m.Pattern == "all":
+ case strings.Contains(m.Pattern(), "..."):
+ m.Pkgs = matchPackages(m.Pattern(), loaded.tags, true, buildList)
+
+ case m.Pattern() == "all":
loaded.testAll = true
if iterating {
// Enumerate the packages in the main module.
m.Pkgs = loaded.computePatternAll(m.Pkgs)
}
- case search.IsMetaPackage(m.Pattern): // std, cmd
+ case m.Pattern() == "std" || m.Pattern() == "cmd":
if len(m.Pkgs) == 0 {
- match := search.MatchPackages(m.Pattern)
- m.Pkgs = match.Pkgs
- m.Errs = match.Errs
+ m.MatchPackages() // Locate the packages within GOROOT/src.
}
default:
- m.Pkgs = []string{m.Pattern}
+ panic(fmt.Sprintf("internal error: modload missing case for pattern %s", m.Pattern()))
}
}
}
var matches []*search.Match
for _, pattern := range search.CleanPatterns(patterns) {
- matches = append(matches, &search.Match{
- Pattern: pattern,
- Literal: !strings.Contains(pattern, "...") && !search.IsMetaPackage(pattern),
- })
+ matches = append(matches, search.NewMatch(pattern))
}
loaded = newLoader(tags)
// A Match represents the result of matching a single package pattern.
type Match struct {
- Pattern string // the pattern itself
- Literal bool // whether it is a literal (no wildcards)
+ pattern string // the pattern itself
Pkgs []string // matching packages (dirs or import paths)
Errs []error // errors matching the patterns to packages, NOT errors loading those packages
// match any packages.
}
+// NewMatch returns a Match describing the given pattern,
+// without resolving its packages or errors.
+func NewMatch(pattern string) *Match {
+ return &Match{pattern: pattern}
+}
+
+// Pattern returns the pattern to be matched.
+func (m *Match) Pattern() string { return m.pattern }
+
// AddError appends a MatchError wrapping err to m.Errs.
func (m *Match) AddError(err error) {
m.Errs = append(m.Errs, &MatchError{Match: m, Err: err})
}
+// Literal reports whether the pattern is free of wildcards and meta-patterns.
+//
+// A literal pattern must match at most one package.
+func (m *Match) IsLiteral() bool {
+ return !strings.Contains(m.pattern, "...") && !m.IsMeta()
+}
+
+// Local reports whether the pattern must be resolved from a specific root or
+// directory, such as a filesystem path or a single module.
+func (m *Match) IsLocal() bool {
+ return build.IsLocalImport(m.pattern) || filepath.IsAbs(m.pattern)
+}
+
+// Meta reports whether the pattern is a “meta-package” keyword that represents
+// multiple packages, such as "std", "cmd", or "all".
+func (m *Match) IsMeta() bool {
+ return IsMetaPackage(m.pattern)
+}
+
+// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
+func IsMetaPackage(name string) bool {
+ return name == "std" || name == "cmd" || name == "all"
+}
+
// A MatchError indicates an error that occurred while attempting to match a
// pattern.
type MatchError struct {
- *Match
- Err error
+ Match *Match
+ Err error
}
func (e *MatchError) Error() string {
- if e.Literal {
- return fmt.Sprintf("matching %s: %v", e.Pattern, e.Err)
+ if e.Match.IsLiteral() {
+ return fmt.Sprintf("%s: %v", e.Match.Pattern(), e.Err)
}
- return fmt.Sprintf("pattern %s: %v", e.Pattern, e.Err)
+ return fmt.Sprintf("pattern %s: %v", e.Match.Pattern(), e.Err)
}
func (e *MatchError) Unwrap() error {
return e.Err
}
-// MatchPackages returns all the packages that can be found
+// MatchPackages sets m.Pkgs to contain all the packages that can be found
// under the $GOPATH directories and $GOROOT matching pattern.
// The pattern is either "all" (all packages), "std" (standard packages),
// "cmd" (standard commands), or a path including "...".
-func MatchPackages(pattern string) *Match {
- m := &Match{
- Pattern: pattern,
- Literal: false,
+//
+// MatchPackages sets m.Errs to contain any errors encountered while processing
+// the match.
+func (m *Match) MatchPackages() {
+ m.Pkgs, m.Errs = nil, nil
+ if m.IsLocal() {
+ m.AddError(fmt.Errorf("internal error: MatchPackages: %s is not a valid package pattern", m.pattern))
+ return
}
+
match := func(string) bool { return true }
treeCanMatch := func(string) bool { return true }
- if !IsMetaPackage(pattern) {
- match = MatchPattern(pattern)
- treeCanMatch = TreeCanMatchPattern(pattern)
+ if !m.IsMeta() {
+ match = MatchPattern(m.pattern)
+ treeCanMatch = TreeCanMatchPattern(m.pattern)
}
have := map[string]bool{
}
for _, src := range cfg.BuildContext.SrcDirs() {
- if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc {
+ if (m.pattern == "std" || m.pattern == "cmd") && src != cfg.GOROOTsrc {
continue
}
src = filepath.Clean(src) + string(filepath.Separator)
root := src
- if pattern == "cmd" {
+ if m.pattern == "cmd" {
root += "cmd" + string(filepath.Separator)
}
err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
}
name := filepath.ToSlash(path[len(src):])
- if pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
+ if m.pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
// The name "std" is only the standard library.
// If the name is cmd, it's the root of the command tree.
want = false
// packages under cmd/vendor. At least as of
// March, 2017, there is one there for the
// vendored pprof tool.
- if pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
+ if m.pattern == "cmd" && pkg != nil && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
return nil
}
m.AddError(err)
}
}
- return m
}
var modRoot string
// use slash or backslash separators or a mix of both.
//
// MatchPackagesInFS scans the tree rooted at the directory that contains the
-// first "..." wildcard and returns a match with packages that
-func MatchPackagesInFS(pattern string) *Match {
- m := &Match{
- Pattern: pattern,
- Literal: false,
+// first "..." wildcard.
+func (m *Match) MatchPackagesInFS() {
+ m.Pkgs, m.Errs = nil, nil
+ if !m.IsLocal() {
+ m.AddError(fmt.Errorf("internal error: MatchPackagesInFS: %s is not a valid filesystem pattern", m.pattern))
+ return
}
// Clean the path and create a matching predicate.
// filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to
// preserve these, since they are meaningful in MatchPattern and in
// returned import paths.
- cleanPattern := filepath.Clean(pattern)
- isLocal := strings.HasPrefix(pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(pattern, `.\`))
+ cleanPattern := filepath.Clean(m.pattern)
+ isLocal := strings.HasPrefix(m.pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(m.pattern, `.\`))
prefix := ""
if cleanPattern != "." && isLocal {
prefix = "./"
abs, err := filepath.Abs(dir)
if err != nil {
m.AddError(err)
- return m
+ return
}
if !hasFilepathPrefix(abs, modRoot) {
m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot))
- return m
+ return
}
}
if err != nil {
m.AddError(err)
}
- return m
}
// TreeCanMatchPattern(pattern)(name) reports whether
func WarnUnmatched(matches []*Match) {
for _, m := range matches {
if len(m.Pkgs) == 0 && len(m.Errs) == 0 {
- fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.Pattern)
+ fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.pattern)
}
}
}
func ImportPathsQuiet(patterns []string) []*Match {
var out []*Match
for _, a := range CleanPatterns(patterns) {
- if IsMetaPackage(a) {
- out = append(out, MatchPackages(a))
- continue
- }
-
- if build.IsLocalImport(a) || filepath.IsAbs(a) {
- var m *Match
- if strings.Contains(a, "...") {
- m = MatchPackagesInFS(a)
+ m := NewMatch(a)
+ if m.IsLocal() {
+ if m.IsLiteral() {
+ m.Pkgs = []string{a}
} else {
- m = &Match{Pattern: a, Literal: true, Pkgs: []string{a}}
+ m.MatchPackagesInFS()
}
// Change the file import path to a regular import path if the package
m.Pkgs[i] = bp.ImportPath
}
}
- out = append(out, m)
- continue
- }
-
- if strings.Contains(a, "...") {
- out = append(out, MatchPackages(a))
- continue
+ } else if m.IsLiteral() {
+ m.Pkgs = []string{a}
+ } else {
+ m.MatchPackages()
}
- out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}})
+ out = append(out, m)
}
return out
}
return out
}
-// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
-func IsMetaPackage(name string) bool {
- return name == "std" || name == "cmd" || name == "all"
-}
-
// hasPathPrefix reports whether the path s begins with the
// elements in prefix.
func hasPathPrefix(s, prefix string) bool {