mirror of https://github.com/hashicorp/consul
310 lines
8.3 KiB
Go
310 lines
8.3 KiB
Go
|
package main
|
||
|
|
||
|
/*
|
||
|
|
||
|
This file holds a direct copy of the import path matching code of
|
||
|
https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
|
||
|
replaced when https://golang.org/issue/8768 is resolved.
|
||
|
|
||
|
It has been updated to follow upstream changes in a few ways.
|
||
|
|
||
|
*/
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"go/build"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"regexp"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
buildContext = build.Default
|
||
|
goroot = filepath.Clean(runtime.GOROOT())
|
||
|
gorootSrc = filepath.Join(goroot, "src")
|
||
|
)
|
||
|
|
||
|
// importPathsNoDotExpansion returns the import paths to use for the given
|
||
|
// command line, but it does no ... expansion.
|
||
|
func importPathsNoDotExpansion(args []string) []string {
|
||
|
if len(args) == 0 {
|
||
|
return []string{"."}
|
||
|
}
|
||
|
var out []string
|
||
|
for _, a := range args {
|
||
|
// Arguments are supposed to be import paths, but
|
||
|
// as a courtesy to Windows developers, rewrite \ to /
|
||
|
// in command-line arguments. Handles .\... and so on.
|
||
|
if filepath.Separator == '\\' {
|
||
|
a = strings.Replace(a, `\`, `/`, -1)
|
||
|
}
|
||
|
|
||
|
// Put argument in canonical form, but preserve leading ./.
|
||
|
if strings.HasPrefix(a, "./") {
|
||
|
a = "./" + path.Clean(a)
|
||
|
if a == "./." {
|
||
|
a = "."
|
||
|
}
|
||
|
} else {
|
||
|
a = path.Clean(a)
|
||
|
}
|
||
|
if a == "all" || a == "std" {
|
||
|
out = append(out, allPackages(a)...)
|
||
|
continue
|
||
|
}
|
||
|
out = append(out, a)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// importPaths returns the import paths to use for the given command line.
|
||
|
func importPaths(args []string) []string {
|
||
|
args = importPathsNoDotExpansion(args)
|
||
|
var out []string
|
||
|
for _, a := range args {
|
||
|
if strings.Contains(a, "...") {
|
||
|
if build.IsLocalImport(a) {
|
||
|
out = append(out, allPackagesInFS(a)...)
|
||
|
} else {
|
||
|
out = append(out, allPackages(a)...)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
out = append(out, a)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// matchPattern(pattern)(name) reports whether
|
||
|
// name matches pattern. Pattern is a limited glob
|
||
|
// pattern in which '...' means 'any string' and there
|
||
|
// is no other special syntax.
|
||
|
func matchPattern(pattern string) func(name string) bool {
|
||
|
re := regexp.QuoteMeta(pattern)
|
||
|
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
|
||
|
// Special case: foo/... matches foo too.
|
||
|
if strings.HasSuffix(re, `/.*`) {
|
||
|
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
|
||
|
}
|
||
|
reg := regexp.MustCompile(`^` + re + `$`)
|
||
|
return func(name string) bool {
|
||
|
return reg.MatchString(name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// hasPathPrefix reports whether the path s begins with the
|
||
|
// elements in prefix.
|
||
|
func hasPathPrefix(s, prefix string) bool {
|
||
|
switch {
|
||
|
default:
|
||
|
return false
|
||
|
case len(s) == len(prefix):
|
||
|
return s == prefix
|
||
|
case len(s) > len(prefix):
|
||
|
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
||
|
return strings.HasPrefix(s, prefix)
|
||
|
}
|
||
|
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// treeCanMatchPattern(pattern)(name) reports whether
|
||
|
// name or children of name can possibly match pattern.
|
||
|
// Pattern is the same limited glob accepted by matchPattern.
|
||
|
func treeCanMatchPattern(pattern string) func(name string) bool {
|
||
|
wildCard := false
|
||
|
if i := strings.Index(pattern, "..."); i >= 0 {
|
||
|
wildCard = true
|
||
|
pattern = pattern[:i]
|
||
|
}
|
||
|
return func(name string) bool {
|
||
|
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
||
|
wildCard && strings.HasPrefix(name, pattern)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// allPackages returns 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)
|
||
|
// or a path including "...".
|
||
|
func allPackages(pattern string) []string {
|
||
|
pkgs := matchPackages(pattern)
|
||
|
if len(pkgs) == 0 {
|
||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||
|
}
|
||
|
return pkgs
|
||
|
}
|
||
|
|
||
|
func matchPackages(pattern string) []string {
|
||
|
match := func(string) bool { return true }
|
||
|
treeCanMatch := func(string) bool { return true }
|
||
|
if pattern != "all" && pattern != "std" {
|
||
|
match = matchPattern(pattern)
|
||
|
treeCanMatch = treeCanMatchPattern(pattern)
|
||
|
}
|
||
|
|
||
|
have := map[string]bool{
|
||
|
"builtin": true, // ignore pseudo-package that exists only for documentation
|
||
|
}
|
||
|
if !buildContext.CgoEnabled {
|
||
|
have["runtime/cgo"] = true // ignore during walk
|
||
|
}
|
||
|
var pkgs []string
|
||
|
|
||
|
// Commands
|
||
|
cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
|
||
|
filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
|
||
|
if err != nil || !fi.IsDir() || path == cmd {
|
||
|
return nil
|
||
|
}
|
||
|
name := path[len(cmd):]
|
||
|
if !treeCanMatch(name) {
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
// Commands are all in cmd/, not in subdirectories.
|
||
|
if strings.Contains(name, string(filepath.Separator)) {
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
|
||
|
// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
|
||
|
name = "cmd/" + name
|
||
|
if have[name] {
|
||
|
return nil
|
||
|
}
|
||
|
have[name] = true
|
||
|
if !match(name) {
|
||
|
return nil
|
||
|
}
|
||
|
_, err = buildContext.ImportDir(path, 0)
|
||
|
if err != nil {
|
||
|
if _, noGo := err.(*build.NoGoError); !noGo {
|
||
|
log.Print(err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
pkgs = append(pkgs, name)
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
for _, src := range buildContext.SrcDirs() {
|
||
|
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
|
||
|
continue
|
||
|
}
|
||
|
src = filepath.Clean(src) + string(filepath.Separator)
|
||
|
root := src
|
||
|
if pattern == "cmd" {
|
||
|
root += "cmd" + string(filepath.Separator)
|
||
|
}
|
||
|
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||
|
if err != nil || !fi.IsDir() || path == src {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Avoid .foo, _foo, and testdata directory trees.
|
||
|
_, elem := filepath.Split(path)
|
||
|
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
|
||
|
name := filepath.ToSlash(path[len(src):])
|
||
|
if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
|
||
|
// The name "std" is only the standard library.
|
||
|
// If the name is cmd, it's the root of the command tree.
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
if !treeCanMatch(name) {
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
if have[name] {
|
||
|
return nil
|
||
|
}
|
||
|
have[name] = true
|
||
|
if !match(name) {
|
||
|
return nil
|
||
|
}
|
||
|
_, err = buildContext.ImportDir(path, 0)
|
||
|
if err != nil {
|
||
|
if _, noGo := err.(*build.NoGoError); noGo {
|
||
|
return nil
|
||
|
}
|
||
|
}
|
||
|
pkgs = append(pkgs, name)
|
||
|
return nil
|
||
|
})
|
||
|
}
|
||
|
return pkgs
|
||
|
}
|
||
|
|
||
|
// allPackagesInFS is like allPackages but is passed a pattern
|
||
|
// beginning ./ or ../, meaning it should scan the tree rooted
|
||
|
// at the given directory. There are ... in the pattern too.
|
||
|
func allPackagesInFS(pattern string) []string {
|
||
|
pkgs := matchPackagesInFS(pattern)
|
||
|
if len(pkgs) == 0 {
|
||
|
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
||
|
}
|
||
|
return pkgs
|
||
|
}
|
||
|
|
||
|
func matchPackagesInFS(pattern string) []string {
|
||
|
// Find directory to begin the scan.
|
||
|
// Could be smarter but this one optimization
|
||
|
// is enough for now, since ... is usually at the
|
||
|
// end of a path.
|
||
|
i := strings.Index(pattern, "...")
|
||
|
dir, _ := path.Split(pattern[:i])
|
||
|
|
||
|
// pattern begins with ./ or ../.
|
||
|
// path.Clean will discard the ./ but not the ../.
|
||
|
// We need to preserve the ./ for pattern matching
|
||
|
// and in the returned import paths.
|
||
|
prefix := ""
|
||
|
if strings.HasPrefix(pattern, "./") {
|
||
|
prefix = "./"
|
||
|
}
|
||
|
match := matchPattern(pattern)
|
||
|
|
||
|
var pkgs []string
|
||
|
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
||
|
if err != nil || !fi.IsDir() {
|
||
|
return nil
|
||
|
}
|
||
|
if path == dir {
|
||
|
// filepath.Walk starts at dir and recurses. For the recursive case,
|
||
|
// the path is the result of filepath.Join, which calls filepath.Clean.
|
||
|
// The initial case is not Cleaned, though, so we do this explicitly.
|
||
|
//
|
||
|
// This converts a path like "./io/" to "io". Without this step, running
|
||
|
// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
|
||
|
// package, because prepending the prefix "./" to the unclean path would
|
||
|
// result in "././io", and match("././io") returns false.
|
||
|
path = filepath.Clean(path)
|
||
|
}
|
||
|
|
||
|
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
||
|
_, elem := filepath.Split(path)
|
||
|
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
||
|
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
||
|
return filepath.SkipDir
|
||
|
}
|
||
|
|
||
|
name := prefix + filepath.ToSlash(path)
|
||
|
if !match(name) {
|
||
|
return nil
|
||
|
}
|
||
|
if _, err = build.ImportDir(path, 0); err != nil {
|
||
|
if _, noGo := err.(*build.NoGoError); !noGo {
|
||
|
log.Print(err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
pkgs = append(pkgs, name)
|
||
|
return nil
|
||
|
})
|
||
|
return pkgs
|
||
|
}
|