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 }