// Copyright (c) 2009 The Go Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +build !go1.9 package gotool import ( "fmt" "go/build" "log" "os" "path" "path/filepath" "regexp" "strings" ) // This file contains code from the Go distribution. // 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 reg.MatchString } // 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 != 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" && (!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. return filepath.SkipDir } if !treeCanMatch(name) { return filepath.SkipDir } if have[name] { return nil } have[name] = true if !match(name) { return nil } _, err = c.BuildContext.ImportDir(path, 0) if err != nil { if _, noGo := err.(*build.NoGoError); noGo { return nil } } pkgs = append(pkgs, name) return nil }) } return pkgs } // 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 } // 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 } // 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 } // 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 && shouldIgnoreImport(p) { if _, noGo := err.(*build.NoGoError); !noGo { log.Print(err) } return nil } pkgs = append(pkgs, name) return nil }) return pkgs } // 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" } // isStandardImportPath reports whether $GOROOT/src/path should be considered // part of the standard distribution. For historical reasons we allow people to add // their own code to $GOROOT instead of using $GOPATH, but we assume that // code will start with a domain name (dot in the first element). func isStandardImportPath(path string) bool { i := strings.Index(path, "/") if i < 0 { i = len(path) } elem := path[:i] return !strings.Contains(elem, ".") } // 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) } }