| // 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) |
| } |
| } |