github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/cmd/go/internal/load/search.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package load
     6  
     7  import (
     8  	"cmd/go/internal/cfg"
     9  	"fmt"
    10  	"go/build"
    11  	"log"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  )
    18  
    19  // allPackages returns all the packages that can be found
    20  // under the $GOPATH directories and $GOROOT matching pattern.
    21  // The pattern is either "all" (all packages), "std" (standard packages),
    22  // "cmd" (standard commands), or a path including "...".
    23  func allPackages(pattern string) []string {
    24  	pkgs := MatchPackages(pattern)
    25  	if len(pkgs) == 0 {
    26  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
    27  	}
    28  	return pkgs
    29  }
    30  
    31  // allPackagesInFS is like allPackages but is passed a pattern
    32  // beginning ./ or ../, meaning it should scan the tree rooted
    33  // at the given directory. There are ... in the pattern too.
    34  func allPackagesInFS(pattern string) []string {
    35  	pkgs := MatchPackagesInFS(pattern)
    36  	if len(pkgs) == 0 {
    37  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
    38  	}
    39  	return pkgs
    40  }
    41  
    42  // MatchPackages returns a list of package paths matching pattern
    43  // (see go help packages for pattern syntax).
    44  func MatchPackages(pattern string) []string {
    45  	match := func(string) bool { return true }
    46  	treeCanMatch := func(string) bool { return true }
    47  	if !IsMetaPackage(pattern) {
    48  		match = matchPattern(pattern)
    49  		treeCanMatch = treeCanMatchPattern(pattern)
    50  	}
    51  
    52  	have := map[string]bool{
    53  		"builtin": true, // ignore pseudo-package that exists only for documentation
    54  	}
    55  	if !cfg.BuildContext.CgoEnabled {
    56  		have["runtime/cgo"] = true // ignore during walk
    57  	}
    58  	var pkgs []string
    59  
    60  	for _, src := range cfg.BuildContext.SrcDirs() {
    61  		if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc {
    62  			continue
    63  		}
    64  		src = filepath.Clean(src) + string(filepath.Separator)
    65  		root := src
    66  		if pattern == "cmd" {
    67  			root += "cmd" + string(filepath.Separator)
    68  		}
    69  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    70  			if err != nil || !fi.IsDir() || path == src {
    71  				return nil
    72  			}
    73  
    74  			// Avoid .foo, _foo, and testdata directory trees.
    75  			_, elem := filepath.Split(path)
    76  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    77  				return filepath.SkipDir
    78  			}
    79  
    80  			name := filepath.ToSlash(path[len(src):])
    81  			if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
    82  				// The name "std" is only the standard library.
    83  				// If the name is cmd, it's the root of the command tree.
    84  				return filepath.SkipDir
    85  			}
    86  			if !treeCanMatch(name) {
    87  				return filepath.SkipDir
    88  			}
    89  			if have[name] {
    90  				return nil
    91  			}
    92  			have[name] = true
    93  			if !match(name) {
    94  				return nil
    95  			}
    96  			pkg, err := cfg.BuildContext.ImportDir(path, 0)
    97  			if err != nil {
    98  				if _, noGo := err.(*build.NoGoError); noGo {
    99  					return nil
   100  				}
   101  			}
   102  
   103  			// If we are expanding "cmd", skip main
   104  			// packages under cmd/vendor. At least as of
   105  			// March, 2017, there is one there for the
   106  			// vendored pprof tool.
   107  			if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
   108  				return nil
   109  			}
   110  
   111  			pkgs = append(pkgs, name)
   112  			return nil
   113  		})
   114  	}
   115  	return pkgs
   116  }
   117  
   118  // MatchPackagesInFS returns a list of package paths matching pattern,
   119  // which must begin with ./ or ../
   120  // (see go help packages for pattern syntax).
   121  func MatchPackagesInFS(pattern string) []string {
   122  	// Find directory to begin the scan.
   123  	// Could be smarter but this one optimization
   124  	// is enough for now, since ... is usually at the
   125  	// end of a path.
   126  	i := strings.Index(pattern, "...")
   127  	dir, _ := path.Split(pattern[:i])
   128  
   129  	// pattern begins with ./ or ../.
   130  	// path.Clean will discard the ./ but not the ../.
   131  	// We need to preserve the ./ for pattern matching
   132  	// and in the returned import paths.
   133  	prefix := ""
   134  	if strings.HasPrefix(pattern, "./") {
   135  		prefix = "./"
   136  	}
   137  	match := matchPattern(pattern)
   138  
   139  	var pkgs []string
   140  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   141  		if err != nil || !fi.IsDir() {
   142  			return nil
   143  		}
   144  		if path == dir {
   145  			// filepath.Walk starts at dir and recurses. For the recursive case,
   146  			// the path is the result of filepath.Join, which calls filepath.Clean.
   147  			// The initial case is not Cleaned, though, so we do this explicitly.
   148  			//
   149  			// This converts a path like "./io/" to "io". Without this step, running
   150  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   151  			// package, because prepending the prefix "./" to the unclean path would
   152  			// result in "././io", and match("././io") returns false.
   153  			path = filepath.Clean(path)
   154  		}
   155  
   156  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   157  		_, elem := filepath.Split(path)
   158  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   159  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   160  			return filepath.SkipDir
   161  		}
   162  
   163  		name := prefix + filepath.ToSlash(path)
   164  		if !match(name) {
   165  			return nil
   166  		}
   167  
   168  		// We keep the directory if we can import it, or if we can't import it
   169  		// due to invalid Go source files. This means that directories containing
   170  		// parse errors will be built (and fail) instead of being silently skipped
   171  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   172  		// behavior means people miss serious mistakes.
   173  		// See golang.org/issue/11407.
   174  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   175  			if _, noGo := err.(*build.NoGoError); !noGo {
   176  				log.Print(err)
   177  			}
   178  			return nil
   179  		}
   180  		pkgs = append(pkgs, name)
   181  		return nil
   182  	})
   183  	return pkgs
   184  }
   185  
   186  // treeCanMatchPattern(pattern)(name) reports whether
   187  // name or children of name can possibly match pattern.
   188  // Pattern is the same limited glob accepted by matchPattern.
   189  func treeCanMatchPattern(pattern string) func(name string) bool {
   190  	wildCard := false
   191  	if i := strings.Index(pattern, "..."); i >= 0 {
   192  		wildCard = true
   193  		pattern = pattern[:i]
   194  	}
   195  	return func(name string) bool {
   196  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   197  			wildCard && strings.HasPrefix(name, pattern)
   198  	}
   199  }
   200  
   201  // matchPattern(pattern)(name) reports whether
   202  // name matches pattern. Pattern is a limited glob
   203  // pattern in which '...' means 'any string' and there
   204  // is no other special syntax.
   205  // Unfortunately, there are two special cases. Quoting "go help packages":
   206  //
   207  // First, /... at the end of the pattern can match an empty string,
   208  // so that net/... matches both net and packages in its subdirectories, like net/http.
   209  // Second, any slash-separted pattern element containing a wildcard never
   210  // participates in a match of the "vendor" element in the path of a vendored
   211  // package, so that ./... does not match packages in subdirectories of
   212  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
   213  // Note, however, that a directory named vendor that itself contains code
   214  // is not a vendored package: cmd/vendor would be a command named vendor,
   215  // and the pattern cmd/... matches it.
   216  func matchPattern(pattern string) func(name string) bool {
   217  	// Convert pattern to regular expression.
   218  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
   219  	// The strategy for the vendor exclusion is to change the unmatchable
   220  	// vendor strings to a disallowed code point (vendorChar) and to use
   221  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
   222  	// This is a bit complicated but the obvious alternative,
   223  	// namely a hand-written search like in most shell glob matchers,
   224  	// is too easy to make accidentally exponential.
   225  	// Using package regexp guarantees linear-time matching.
   226  
   227  	const vendorChar = "\x00"
   228  
   229  	if strings.Contains(pattern, vendorChar) {
   230  		return func(name string) bool { return false }
   231  	}
   232  
   233  	re := regexp.QuoteMeta(pattern)
   234  	re = replaceVendor(re, vendorChar)
   235  	switch {
   236  	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
   237  		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
   238  	case re == vendorChar+`/\.\.\.`:
   239  		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
   240  	case strings.HasSuffix(re, `/\.\.\.`):
   241  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
   242  	}
   243  	re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
   244  
   245  	reg := regexp.MustCompile(`^` + re + `$`)
   246  
   247  	return func(name string) bool {
   248  		if strings.Contains(name, vendorChar) {
   249  			return false
   250  		}
   251  		return reg.MatchString(replaceVendor(name, vendorChar))
   252  	}
   253  }
   254  
   255  // replaceVendor returns the result of replacing
   256  // non-trailing vendor path elements in x with repl.
   257  func replaceVendor(x, repl string) string {
   258  	if !strings.Contains(x, "vendor") {
   259  		return x
   260  	}
   261  	elem := strings.Split(x, "/")
   262  	for i := 0; i < len(elem)-1; i++ {
   263  		if elem[i] == "vendor" {
   264  			elem[i] = repl
   265  		}
   266  	}
   267  	return strings.Join(elem, "/")
   268  }
   269  
   270  // ImportPaths returns the import paths to use for the given command line.
   271  func ImportPaths(args []string) []string {
   272  	args = ImportPathsNoDotExpansion(args)
   273  	var out []string
   274  	for _, a := range args {
   275  		if strings.Contains(a, "...") {
   276  			if build.IsLocalImport(a) {
   277  				out = append(out, allPackagesInFS(a)...)
   278  			} else {
   279  				out = append(out, allPackages(a)...)
   280  			}
   281  			continue
   282  		}
   283  		out = append(out, a)
   284  	}
   285  	return out
   286  }
   287  
   288  // ImportPathsNoDotExpansion returns the import paths to use for the given
   289  // command line, but it does no ... expansion.
   290  func ImportPathsNoDotExpansion(args []string) []string {
   291  	if len(args) == 0 {
   292  		return []string{"."}
   293  	}
   294  	var out []string
   295  	for _, a := range args {
   296  		// Arguments are supposed to be import paths, but
   297  		// as a courtesy to Windows developers, rewrite \ to /
   298  		// in command-line arguments. Handles .\... and so on.
   299  		if filepath.Separator == '\\' {
   300  			a = strings.Replace(a, `\`, `/`, -1)
   301  		}
   302  
   303  		// Put argument in canonical form, but preserve leading ./.
   304  		if strings.HasPrefix(a, "./") {
   305  			a = "./" + path.Clean(a)
   306  			if a == "./." {
   307  				a = "."
   308  			}
   309  		} else {
   310  			a = path.Clean(a)
   311  		}
   312  		if IsMetaPackage(a) {
   313  			out = append(out, allPackages(a)...)
   314  			continue
   315  		}
   316  		out = append(out, a)
   317  	}
   318  	return out
   319  }
   320  
   321  // isMetaPackage checks if name is a reserved package name that expands to multiple packages.
   322  func IsMetaPackage(name string) bool {
   323  	return name == "std" || name == "cmd" || name == "all"
   324  }