355 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			355 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017 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.
 | 
						|
 | 
						|
// +build go1.9
 | 
						|
 | 
						|
package load
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"go/build"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// Context specifies values for operation of ImportPaths that would
 | 
						|
// otherwise come from cmd/go/internal/cfg package.
 | 
						|
//
 | 
						|
// This is a construct added for gotool purposes and doesn't have
 | 
						|
// an equivalent upstream in cmd/go.
 | 
						|
type Context struct {
 | 
						|
	// BuildContext is the build context to use.
 | 
						|
	BuildContext build.Context
 | 
						|
 | 
						|
	// GOROOTsrc is the location of the src directory in GOROOT.
 | 
						|
	// At this time, it's used only in MatchPackages to skip
 | 
						|
	// GOOROOT/src entry from BuildContext.SrcDirs output.
 | 
						|
	GOROOTsrc string
 | 
						|
}
 | 
						|
 | 
						|
// 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),
 | 
						|
// "cmd" (standard commands), or a path including "...".
 | 
						|
func (c *Context) allPackages(pattern string) []string {
 | 
						|
	pkgs := c.MatchPackages(pattern)
 | 
						|
	if len(pkgs) == 0 {
 | 
						|
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
 | 
						|
	}
 | 
						|
	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 (c *Context) allPackagesInFS(pattern string) []string {
 | 
						|
	pkgs := c.MatchPackagesInFS(pattern)
 | 
						|
	if len(pkgs) == 0 {
 | 
						|
		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
 | 
						|
	}
 | 
						|
	return pkgs
 | 
						|
}
 | 
						|
 | 
						|
// MatchPackages returns a list of package paths matching pattern
 | 
						|
// (see go help packages for pattern syntax).
 | 
						|
func (c *Context) MatchPackages(pattern string) []string {
 | 
						|
	match := func(string) bool { return true }
 | 
						|
	treeCanMatch := func(string) bool { return true }
 | 
						|
	if !IsMetaPackage(pattern) {
 | 
						|
		match = matchPattern(pattern)
 | 
						|
		treeCanMatch = treeCanMatchPattern(pattern)
 | 
						|
	}
 | 
						|
 | 
						|
	have := map[string]bool{
 | 
						|
		"builtin": true, // ignore pseudo-package that exists only for documentation
 | 
						|
	}
 | 
						|
	if !c.BuildContext.CgoEnabled {
 | 
						|
		have["runtime/cgo"] = true // ignore during walk
 | 
						|
	}
 | 
						|
	var pkgs []string
 | 
						|
 | 
						|
	for _, src := range c.BuildContext.SrcDirs() {
 | 
						|
		if (pattern == "std" || pattern == "cmd") && src != c.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 || path == src {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
 | 
						|
			want := true
 | 
						|
			// Avoid .foo, _foo, and testdata directory trees.
 | 
						|
			_, elem := filepath.Split(path)
 | 
						|
			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
 | 
						|
				want = false
 | 
						|
			}
 | 
						|
 | 
						|
			name := filepath.ToSlash(path[len(src):])
 | 
						|
			if 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
 | 
						|
			}
 | 
						|
			if !treeCanMatch(name) {
 | 
						|
				want = false
 | 
						|
			}
 | 
						|
 | 
						|
			if !fi.IsDir() {
 | 
						|
				if fi.Mode()&os.ModeSymlink != 0 && want {
 | 
						|
					if target, err := os.Stat(path); err == nil && target.IsDir() {
 | 
						|
						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
 | 
						|
					}
 | 
						|
				}
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			if !want {
 | 
						|
				return filepath.SkipDir
 | 
						|
			}
 | 
						|
 | 
						|
			if have[name] {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			have[name] = true
 | 
						|
			if !match(name) {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			pkg, err := c.BuildContext.ImportDir(path, 0)
 | 
						|
			if err != nil {
 | 
						|
				if _, noGo := err.(*build.NoGoError); noGo {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// If we are expanding "cmd", skip main
 | 
						|
			// packages under cmd/vendor. At least as of
 | 
						|
			// March, 2017, there is one there for the
 | 
						|
			// vendored pprof tool.
 | 
						|
			if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
 | 
						|
			pkgs = append(pkgs, name)
 | 
						|
			return nil
 | 
						|
		})
 | 
						|
	}
 | 
						|
	return pkgs
 | 
						|
}
 | 
						|
 | 
						|
// MatchPackagesInFS returns a list of package paths matching pattern,
 | 
						|
// which must begin with ./ or ../
 | 
						|
// (see go help packages for pattern syntax).
 | 
						|
func (c *Context) 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; 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
 | 
						|
		}
 | 
						|
 | 
						|
		// We keep the directory if we can import it, or if we can't import it
 | 
						|
		// due to invalid Go source files. This means that directories containing
 | 
						|
		// parse errors will be built (and fail) instead of being silently skipped
 | 
						|
		// as not matching the pattern. Go 1.5 and earlier skipped, but that
 | 
						|
		// behavior means people miss serious mistakes.
 | 
						|
		// See golang.org/issue/11407.
 | 
						|
		if p, err := c.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
 | 
						|
			if _, noGo := err.(*build.NoGoError); !noGo {
 | 
						|
				log.Print(err)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		pkgs = append(pkgs, name)
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
	return pkgs
 | 
						|
}
 | 
						|
 | 
						|
// 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)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// 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.
 | 
						|
// Unfortunately, there are two special cases. Quoting "go help packages":
 | 
						|
//
 | 
						|
// First, /... at the end of the pattern can match an empty string,
 | 
						|
// so that net/... matches both net and packages in its subdirectories, like net/http.
 | 
						|
// Second, any slash-separted pattern element containing a wildcard never
 | 
						|
// participates in a match of the "vendor" element in the path of a vendored
 | 
						|
// package, so that ./... does not match packages in subdirectories of
 | 
						|
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
 | 
						|
// Note, however, that a directory named vendor that itself contains code
 | 
						|
// is not a vendored package: cmd/vendor would be a command named vendor,
 | 
						|
// and the pattern cmd/... matches it.
 | 
						|
func matchPattern(pattern string) func(name string) bool {
 | 
						|
	// Convert pattern to regular expression.
 | 
						|
	// The strategy for the trailing /... is to nest it in an explicit ? expression.
 | 
						|
	// The strategy for the vendor exclusion is to change the unmatchable
 | 
						|
	// vendor strings to a disallowed code point (vendorChar) and to use
 | 
						|
	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
 | 
						|
	// This is a bit complicated but the obvious alternative,
 | 
						|
	// namely a hand-written search like in most shell glob matchers,
 | 
						|
	// is too easy to make accidentally exponential.
 | 
						|
	// Using package regexp guarantees linear-time matching.
 | 
						|
 | 
						|
	const vendorChar = "\x00"
 | 
						|
 | 
						|
	if strings.Contains(pattern, vendorChar) {
 | 
						|
		return func(name string) bool { return false }
 | 
						|
	}
 | 
						|
 | 
						|
	re := regexp.QuoteMeta(pattern)
 | 
						|
	re = replaceVendor(re, vendorChar)
 | 
						|
	switch {
 | 
						|
	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
 | 
						|
		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
 | 
						|
	case re == vendorChar+`/\.\.\.`:
 | 
						|
		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
 | 
						|
	case strings.HasSuffix(re, `/\.\.\.`):
 | 
						|
		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
 | 
						|
	}
 | 
						|
	re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
 | 
						|
 | 
						|
	reg := regexp.MustCompile(`^` + re + `$`)
 | 
						|
 | 
						|
	return func(name string) bool {
 | 
						|
		if strings.Contains(name, vendorChar) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		return reg.MatchString(replaceVendor(name, vendorChar))
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// replaceVendor returns the result of replacing
 | 
						|
// non-trailing vendor path elements in x with repl.
 | 
						|
func replaceVendor(x, repl string) string {
 | 
						|
	if !strings.Contains(x, "vendor") {
 | 
						|
		return x
 | 
						|
	}
 | 
						|
	elem := strings.Split(x, "/")
 | 
						|
	for i := 0; i < len(elem)-1; i++ {
 | 
						|
		if elem[i] == "vendor" {
 | 
						|
			elem[i] = repl
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return strings.Join(elem, "/")
 | 
						|
}
 | 
						|
 | 
						|
// ImportPaths returns the import paths to use for the given command line.
 | 
						|
func (c *Context) ImportPaths(args []string) []string {
 | 
						|
	args = c.ImportPathsNoDotExpansion(args)
 | 
						|
	var out []string
 | 
						|
	for _, a := range args {
 | 
						|
		if strings.Contains(a, "...") {
 | 
						|
			if build.IsLocalImport(a) {
 | 
						|
				out = append(out, c.allPackagesInFS(a)...)
 | 
						|
			} else {
 | 
						|
				out = append(out, c.allPackages(a)...)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		out = append(out, a)
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// ImportPathsNoDotExpansion returns the import paths to use for the given
 | 
						|
// command line, but it does no ... expansion.
 | 
						|
func (c *Context) 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 IsMetaPackage(a) {
 | 
						|
			out = append(out, c.allPackages(a)...)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		out = append(out, a)
 | 
						|
	}
 | 
						|
	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"
 | 
						|
}
 |