234 lines
7.0 KiB
Go
234 lines
7.0 KiB
Go
package pkgutil
|
|
|
|
import (
|
|
"go/build"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
//var go15VendorExperiment = os.Getenv("GO15VENDOREXPERIMENT") == "1"
|
|
|
|
func IsVendorExperiment() bool {
|
|
return true
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// hasFilePathPrefix reports whether the filesystem path s begins with the
|
|
// elements in prefix.
|
|
func hasFilePathPrefix(s, prefix string) bool {
|
|
sv := strings.ToUpper(filepath.VolumeName(s))
|
|
pv := strings.ToUpper(filepath.VolumeName(prefix))
|
|
s = s[len(sv):]
|
|
prefix = prefix[len(pv):]
|
|
switch {
|
|
default:
|
|
return false
|
|
case sv != pv:
|
|
return false
|
|
case len(s) == len(prefix):
|
|
return s == prefix
|
|
case len(s) > len(prefix):
|
|
if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
|
|
return strings.HasPrefix(s, prefix)
|
|
}
|
|
return s[len(prefix)] == filepath.Separator && 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)
|
|
}
|
|
}
|
|
|
|
var isDirCache = map[string]bool{}
|
|
|
|
func isDir(path string) bool {
|
|
result, ok := isDirCache[path]
|
|
if ok {
|
|
return result
|
|
}
|
|
|
|
fi, err := os.Stat(path)
|
|
result = err == nil && fi.IsDir()
|
|
isDirCache[path] = result
|
|
return result
|
|
}
|
|
|
|
type Package struct {
|
|
Root string
|
|
Dir string
|
|
ImportPath string
|
|
}
|
|
|
|
func ImportFile(fileName string) *Package {
|
|
return ImportDir(filepath.Dir(fileName))
|
|
}
|
|
|
|
func ImportDir(dir string) *Package {
|
|
pkg, err := build.ImportDir(dir, build.FindOnly)
|
|
if err != nil {
|
|
return &Package{"", dir, ""}
|
|
}
|
|
return &Package{pkg.Root, pkg.Dir, pkg.ImportPath}
|
|
}
|
|
|
|
// expandPath returns the symlink-expanded form of path.
|
|
func expandPath(p string) string {
|
|
x, err := filepath.EvalSymlinks(p)
|
|
if err == nil {
|
|
return x
|
|
}
|
|
return p
|
|
}
|
|
|
|
// vendoredImportPath returns the expansion of path when it appears in parent.
|
|
// If parent is x/y/z, then path might expand to x/y/z/vendor/path, x/y/vendor/path,
|
|
// x/vendor/path, vendor/path, or else stay path if none of those exist.
|
|
// vendoredImportPath returns the expanded path or, if no expansion is found, the original.
|
|
func VendoredImportPath(parent *Package, path string) (found string) {
|
|
if parent == nil || parent.Root == "" {
|
|
return path
|
|
}
|
|
|
|
dir := filepath.Clean(parent.Dir)
|
|
root := filepath.Join(parent.Root, "src")
|
|
if !hasFilePathPrefix(dir, root) {
|
|
// Look for symlinks before reporting error.
|
|
dir = expandPath(dir)
|
|
root = expandPath(root)
|
|
}
|
|
if !hasFilePathPrefix(dir, root) || len(dir) <= len(root) || dir[len(root)] != filepath.Separator {
|
|
log.Println("invalid vendoredImportPath: dir=%q root=%q separator=%q", dir, root, string(filepath.Separator))
|
|
return ""
|
|
}
|
|
|
|
vpath := "vendor/" + path
|
|
for i := len(dir); i >= len(root); i-- {
|
|
if i < len(dir) && dir[i] != filepath.Separator {
|
|
continue
|
|
}
|
|
// Note: checking for the vendor directory before checking
|
|
// for the vendor/path directory helps us hit the
|
|
// isDir cache more often. It also helps us prepare a more useful
|
|
// list of places we looked, to report when an import is not found.
|
|
if !isDir(filepath.Join(dir[:i], "vendor")) {
|
|
continue
|
|
}
|
|
targ := filepath.Join(dir[:i], vpath)
|
|
if isDir(targ) && hasGoFiles(targ) {
|
|
importPath := parent.ImportPath
|
|
if importPath == "command-line-arguments" {
|
|
// If parent.ImportPath is 'command-line-arguments'.
|
|
// set to relative directory to root (also chopped root directory)
|
|
importPath = dir[len(root)+1:]
|
|
}
|
|
// We started with parent's dir c:\gopath\src\foo\bar\baz\quux\xyzzy.
|
|
// We know the import path for parent's dir.
|
|
// We chopped off some number of path elements and
|
|
// added vendor\path to produce c:\gopath\src\foo\bar\baz\vendor\path.
|
|
// Now we want to know the import path for that directory.
|
|
// Construct it by chopping the same number of path elements
|
|
// (actually the same number of bytes) from parent's import path
|
|
// and then append /vendor/path.
|
|
chopped := len(dir) - i
|
|
if chopped == len(importPath)+1 {
|
|
// We walked up from c:\gopath\src\foo\bar
|
|
// and found c:\gopath\src\vendor\path.
|
|
// We chopped \foo\bar (length 8) but the import path is "foo/bar" (length 7).
|
|
// Use "vendor/path" without any prefix.
|
|
return vpath
|
|
}
|
|
return importPath[:len(importPath)-chopped] + "/" + vpath
|
|
}
|
|
}
|
|
return path
|
|
}
|
|
|
|
// hasGoFiles reports whether dir contains any files with names ending in .go.
|
|
// For a vendor check we must exclude directories that contain no .go files.
|
|
// Otherwise it is not possible to vendor just a/b/c and still import the
|
|
// non-vendored a/b. See golang.org/issue/13832.
|
|
func hasGoFiles(dir string) bool {
|
|
fis, _ := ioutil.ReadDir(dir)
|
|
for _, fi := range fis {
|
|
if !fi.IsDir() && strings.HasSuffix(fi.Name(), ".go") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// findVendor looks for the last non-terminating "vendor" path element in the given import path.
|
|
// If there isn't one, findVendor returns ok=false.
|
|
// Otherwise, findVendor returns ok=true and the index of the "vendor".
|
|
//
|
|
// Note that terminating "vendor" elements don't count: "x/vendor" is its own package,
|
|
// not the vendored copy of an import "" (the empty import path).
|
|
// This will allow people to have packages or commands named vendor.
|
|
// This may help reduce breakage, or it may just be confusing. We'll see.
|
|
func findVendor(path string) (index int, ok bool) {
|
|
// Two cases, depending on internal at start of string or not.
|
|
// The order matters: we must return the index of the final element,
|
|
// because the final one is where the effective import path starts.
|
|
switch {
|
|
case strings.Contains(path, "/vendor/"):
|
|
return strings.LastIndex(path, "/vendor/") + 1, true
|
|
case strings.HasPrefix(path, "vendor/"):
|
|
return 0, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func VendorPathToImportPath(path string) string {
|
|
if i, ok := findVendor(path); ok {
|
|
return path[i+len("vendor/"):]
|
|
}
|
|
return path
|
|
}
|