github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/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 || path == src {
    71  				return nil
    72  			}
    73  
    74  			want := true
    75  			// Avoid .foo, _foo, and testdata directory trees.
    76  			_, elem := filepath.Split(path)
    77  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    78  				want = false
    79  			}
    80  
    81  			name := filepath.ToSlash(path[len(src):])
    82  			if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
    83  				// The name "std" is only the standard library.
    84  				// If the name is cmd, it's the root of the command tree.
    85  				want = false
    86  			}
    87  			if !treeCanMatch(name) {
    88  				want = false
    89  			}
    90  
    91  			if !fi.IsDir() {
    92  				if fi.Mode()&os.ModeSymlink != 0 && want {
    93  					if target, err := os.Stat(path); err == nil && target.IsDir() {
    94  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
    95  					}
    96  				}
    97  				return nil
    98  			}
    99  			if !want {
   100  				return filepath.SkipDir
   101  			}
   102  
   103  			if have[name] {
   104  				return nil
   105  			}
   106  			have[name] = true
   107  			if !match(name) {
   108  				return nil
   109  			}
   110  			pkg, err := cfg.BuildContext.ImportDir(path, 0)
   111  			if err != nil {
   112  				if _, noGo := err.(*build.NoGoError); noGo {
   113  					return nil
   114  				}
   115  			}
   116  
   117  			// If we are expanding "cmd", skip main
   118  			// packages under cmd/vendor. At least as of
   119  			// March, 2017, there is one there for the
   120  			// vendored pprof tool.
   121  			if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
   122  				return nil
   123  			}
   124  
   125  			pkgs = append(pkgs, name)
   126  			return nil
   127  		})
   128  	}
   129  	return pkgs
   130  }
   131  
   132  // MatchPackagesInFS returns a list of package paths matching pattern,
   133  // which must begin with ./ or ../
   134  // (see go help packages for pattern syntax).
   135  func MatchPackagesInFS(pattern string) []string {
   136  	// Find directory to begin the scan.
   137  	// Could be smarter but this one optimization
   138  	// is enough for now, since ... is usually at the
   139  	// end of a path.
   140  	i := strings.Index(pattern, "...")
   141  	dir, _ := path.Split(pattern[:i])
   142  
   143  	// pattern begins with ./ or ../.
   144  	// path.Clean will discard the ./ but not the ../.
   145  	// We need to preserve the ./ for pattern matching
   146  	// and in the returned import paths.
   147  	prefix := ""
   148  	if strings.HasPrefix(pattern, "./") {
   149  		prefix = "./"
   150  	}
   151  	match := matchPattern(pattern)
   152  
   153  	var pkgs []string
   154  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   155  		if err != nil || !fi.IsDir() {
   156  			return nil
   157  		}
   158  		if path == dir {
   159  			// filepath.Walk starts at dir and recurses. For the recursive case,
   160  			// the path is the result of filepath.Join, which calls filepath.Clean.
   161  			// The initial case is not Cleaned, though, so we do this explicitly.
   162  			//
   163  			// This converts a path like "./io/" to "io". Without this step, running
   164  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   165  			// package, because prepending the prefix "./" to the unclean path would
   166  			// result in "././io", and match("././io") returns false.
   167  			path = filepath.Clean(path)
   168  		}
   169  
   170  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   171  		_, elem := filepath.Split(path)
   172  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   173  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   174  			return filepath.SkipDir
   175  		}
   176  
   177  		name := prefix + filepath.ToSlash(path)
   178  		if !match(name) {
   179  			return nil
   180  		}
   181  
   182  		// We keep the directory if we can import it, or if we can't import it
   183  		// due to invalid Go source files. This means that directories containing
   184  		// parse errors will be built (and fail) instead of being silently skipped
   185  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   186  		// behavior means people miss serious mistakes.
   187  		// See golang.org/issue/11407.
   188  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   189  			if _, noGo := err.(*build.NoGoError); !noGo {
   190  				log.Print(err)
   191  			}
   192  			return nil
   193  		}
   194  		pkgs = append(pkgs, name)
   195  		return nil
   196  	})
   197  	return pkgs
   198  }
   199  
   200  // treeCanMatchPattern(pattern)(name) reports whether
   201  // name or children of name can possibly match pattern.
   202  // Pattern is the same limited glob accepted by matchPattern.
   203  func treeCanMatchPattern(pattern string) func(name string) bool {
   204  	wildCard := false
   205  	if i := strings.Index(pattern, "..."); i >= 0 {
   206  		wildCard = true
   207  		pattern = pattern[:i]
   208  	}
   209  	return func(name string) bool {
   210  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   211  			wildCard && strings.HasPrefix(name, pattern)
   212  	}
   213  }
   214  
   215  // matchPattern(pattern)(name) reports whether
   216  // name matches pattern. Pattern is a limited glob
   217  // pattern in which '...' means 'any string' and there
   218  // is no other special syntax.
   219  // Unfortunately, there are two special cases. Quoting "go help packages":
   220  //
   221  // First, /... at the end of the pattern can match an empty string,
   222  // so that net/... matches both net and packages in its subdirectories, like net/http.
   223  // Second, any slash-separted pattern element containing a wildcard never
   224  // participates in a match of the "vendor" element in the path of a vendored
   225  // package, so that ./... does not match packages in subdirectories of
   226  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
   227  // Note, however, that a directory named vendor that itself contains code
   228  // is not a vendored package: cmd/vendor would be a command named vendor,
   229  // and the pattern cmd/... matches it.
   230  func matchPattern(pattern string) func(name string) bool {
   231  	// Convert pattern to regular expression.
   232  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
   233  	// The strategy for the vendor exclusion is to change the unmatchable
   234  	// vendor strings to a disallowed code point (vendorChar) and to use
   235  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
   236  	// This is a bit complicated but the obvious alternative,
   237  	// namely a hand-written search like in most shell glob matchers,
   238  	// is too easy to make accidentally exponential.
   239  	// Using package regexp guarantees linear-time matching.
   240  
   241  	const vendorChar = "\x00"
   242  
   243  	if strings.Contains(pattern, vendorChar) {
   244  		return func(name string) bool { return false }
   245  	}
   246  
   247  	re := regexp.QuoteMeta(pattern)
   248  	re = replaceVendor(re, vendorChar)
   249  	switch {
   250  	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
   251  		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
   252  	case re == vendorChar+`/\.\.\.`:
   253  		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
   254  	case strings.HasSuffix(re, `/\.\.\.`):
   255  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
   256  	}
   257  	re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
   258  
   259  	reg := regexp.MustCompile(`^` + re + `$`)
   260  
   261  	return func(name string) bool {
   262  		if strings.Contains(name, vendorChar) {
   263  			return false
   264  		}
   265  		return reg.MatchString(replaceVendor(name, vendorChar))
   266  	}
   267  }
   268  
   269  // MatchPackage(pattern, cwd)(p) reports whether package p matches pattern in the working directory cwd.
   270  func MatchPackage(pattern, cwd string) func(*Package) bool {
   271  	switch {
   272  	case strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == "..":
   273  		// Split pattern into leading pattern-free directory path
   274  		// (including all . and .. elements) and the final pattern.
   275  		var dir string
   276  		i := strings.Index(pattern, "...")
   277  		if i < 0 {
   278  			dir, pattern = pattern, ""
   279  		} else {
   280  			j := strings.LastIndex(pattern[:i], "/")
   281  			dir, pattern = pattern[:j], pattern[j+1:]
   282  		}
   283  		dir = filepath.Join(cwd, dir)
   284  		if pattern == "" {
   285  			return func(p *Package) bool { return p.Dir == dir }
   286  		}
   287  		matchPath := matchPattern(pattern)
   288  		return func(p *Package) bool {
   289  			// Compute relative path to dir and see if it matches the pattern.
   290  			rel, err := filepath.Rel(dir, p.Dir)
   291  			if err != nil {
   292  				// Cannot make relative - e.g. different drive letters on Windows.
   293  				return false
   294  			}
   295  			rel = filepath.ToSlash(rel)
   296  			if rel == ".." || strings.HasPrefix(rel, "../") {
   297  				return false
   298  			}
   299  			return matchPath(rel)
   300  		}
   301  	case pattern == "all":
   302  		return func(p *Package) bool { return true }
   303  	case pattern == "std":
   304  		return func(p *Package) bool { return p.Standard }
   305  	case pattern == "cmd":
   306  		return func(p *Package) bool { return p.Standard && strings.HasPrefix(p.ImportPath, "cmd/") }
   307  	default:
   308  		matchPath := matchPattern(pattern)
   309  		return func(p *Package) bool { return matchPath(p.ImportPath) }
   310  	}
   311  }
   312  
   313  // replaceVendor returns the result of replacing
   314  // non-trailing vendor path elements in x with repl.
   315  func replaceVendor(x, repl string) string {
   316  	if !strings.Contains(x, "vendor") {
   317  		return x
   318  	}
   319  	elem := strings.Split(x, "/")
   320  	for i := 0; i < len(elem)-1; i++ {
   321  		if elem[i] == "vendor" {
   322  			elem[i] = repl
   323  		}
   324  	}
   325  	return strings.Join(elem, "/")
   326  }
   327  
   328  // ImportPaths returns the import paths to use for the given command line.
   329  func ImportPaths(args []string) []string {
   330  	args = ImportPathsNoDotExpansion(args)
   331  	var out []string
   332  	for _, a := range args {
   333  		if strings.Contains(a, "...") {
   334  			if build.IsLocalImport(a) {
   335  				out = append(out, allPackagesInFS(a)...)
   336  			} else {
   337  				out = append(out, allPackages(a)...)
   338  			}
   339  			continue
   340  		}
   341  		out = append(out, a)
   342  	}
   343  	return out
   344  }
   345  
   346  // ImportPathsNoDotExpansion returns the import paths to use for the given
   347  // command line, but it does no ... expansion.
   348  func ImportPathsNoDotExpansion(args []string) []string {
   349  	if cmdlineMatchers == nil {
   350  		SetCmdlinePatterns(args)
   351  	}
   352  	if len(args) == 0 {
   353  		return []string{"."}
   354  	}
   355  	var out []string
   356  	for _, a := range args {
   357  		// Arguments are supposed to be import paths, but
   358  		// as a courtesy to Windows developers, rewrite \ to /
   359  		// in command-line arguments. Handles .\... and so on.
   360  		if filepath.Separator == '\\' {
   361  			a = strings.Replace(a, `\`, `/`, -1)
   362  		}
   363  
   364  		// Put argument in canonical form, but preserve leading ./.
   365  		if strings.HasPrefix(a, "./") {
   366  			a = "./" + path.Clean(a)
   367  			if a == "./." {
   368  				a = "."
   369  			}
   370  		} else {
   371  			a = path.Clean(a)
   372  		}
   373  		if IsMetaPackage(a) {
   374  			out = append(out, allPackages(a)...)
   375  			continue
   376  		}
   377  		out = append(out, a)
   378  	}
   379  	return out
   380  }
   381  
   382  // IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
   383  func IsMetaPackage(name string) bool {
   384  	return name == "std" || name == "cmd" || name == "all"
   385  }