github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/search/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 search
     6  
     7  import (
     8  	"cmd/go/internal/base"
     9  	"cmd/go/internal/cfg"
    10  	"fmt"
    11  	"go/build"
    12  	"log"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"regexp"
    17  	"strings"
    18  )
    19  
    20  // A Match represents the result of matching a single package pattern.
    21  type Match struct {
    22  	Pattern string   // the pattern itself
    23  	Literal bool     // whether it is a literal (no wildcards)
    24  	Pkgs    []string // matching packages (dirs or import paths)
    25  }
    26  
    27  // MatchPackages returns all the packages that can be found
    28  // under the $GOPATH directories and $GOROOT matching pattern.
    29  // The pattern is either "all" (all packages), "std" (standard packages),
    30  // "cmd" (standard commands), or a path including "...".
    31  func MatchPackages(pattern string) *Match {
    32  	m := &Match{
    33  		Pattern: pattern,
    34  		Literal: false,
    35  	}
    36  	match := func(string) bool { return true }
    37  	treeCanMatch := func(string) bool { return true }
    38  	if !IsMetaPackage(pattern) {
    39  		match = MatchPattern(pattern)
    40  		treeCanMatch = TreeCanMatchPattern(pattern)
    41  	}
    42  
    43  	have := map[string]bool{
    44  		"builtin": true, // ignore pseudo-package that exists only for documentation
    45  	}
    46  	if !cfg.BuildContext.CgoEnabled {
    47  		have["runtime/cgo"] = true // ignore during walk
    48  	}
    49  
    50  	for _, src := range cfg.BuildContext.SrcDirs() {
    51  		if (pattern == "std" || pattern == "cmd") && src != cfg.GOROOTsrc {
    52  			continue
    53  		}
    54  		src = filepath.Clean(src) + string(filepath.Separator)
    55  		root := src
    56  		if pattern == "cmd" {
    57  			root += "cmd" + string(filepath.Separator)
    58  		}
    59  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
    60  			if err != nil || path == src {
    61  				return nil
    62  			}
    63  
    64  			want := true
    65  			// Avoid .foo, _foo, and testdata directory trees.
    66  			_, elem := filepath.Split(path)
    67  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
    68  				want = false
    69  			}
    70  
    71  			name := filepath.ToSlash(path[len(src):])
    72  			if pattern == "std" && (!IsStandardImportPath(name) || name == "cmd") {
    73  				// The name "std" is only the standard library.
    74  				// If the name is cmd, it's the root of the command tree.
    75  				want = false
    76  			}
    77  			if !treeCanMatch(name) {
    78  				want = false
    79  			}
    80  
    81  			if !fi.IsDir() {
    82  				if fi.Mode()&os.ModeSymlink != 0 && want {
    83  					if target, err := os.Stat(path); err == nil && target.IsDir() {
    84  						fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
    85  					}
    86  				}
    87  				return nil
    88  			}
    89  			if !want {
    90  				return filepath.SkipDir
    91  			}
    92  
    93  			if have[name] {
    94  				return nil
    95  			}
    96  			have[name] = true
    97  			if !match(name) {
    98  				return nil
    99  			}
   100  			pkg, err := cfg.BuildContext.ImportDir(path, 0)
   101  			if err != nil {
   102  				if _, noGo := err.(*build.NoGoError); noGo {
   103  					return nil
   104  				}
   105  			}
   106  
   107  			// If we are expanding "cmd", skip main
   108  			// packages under cmd/vendor. At least as of
   109  			// March, 2017, there is one there for the
   110  			// vendored pprof tool.
   111  			if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
   112  				return nil
   113  			}
   114  
   115  			m.Pkgs = append(m.Pkgs, name)
   116  			return nil
   117  		})
   118  	}
   119  	return m
   120  }
   121  
   122  var modRoot string
   123  
   124  func SetModRoot(dir string) {
   125  	modRoot = dir
   126  }
   127  
   128  // MatchPackagesInFS is like allPackages but is passed a pattern
   129  // beginning ./ or ../, meaning it should scan the tree rooted
   130  // at the given directory. There are ... in the pattern too.
   131  // (See go help packages for pattern syntax.)
   132  func MatchPackagesInFS(pattern string) *Match {
   133  	m := &Match{
   134  		Pattern: pattern,
   135  		Literal: false,
   136  	}
   137  
   138  	// Find directory to begin the scan.
   139  	// Could be smarter but this one optimization
   140  	// is enough for now, since ... is usually at the
   141  	// end of a path.
   142  	i := strings.Index(pattern, "...")
   143  	dir, _ := path.Split(pattern[:i])
   144  
   145  	// pattern begins with ./ or ../.
   146  	// path.Clean will discard the ./ but not the ../.
   147  	// We need to preserve the ./ for pattern matching
   148  	// and in the returned import paths.
   149  	prefix := ""
   150  	if strings.HasPrefix(pattern, "./") {
   151  		prefix = "./"
   152  	}
   153  	match := MatchPattern(pattern)
   154  
   155  	if modRoot != "" {
   156  		abs, err := filepath.Abs(dir)
   157  		if err != nil {
   158  			base.Fatalf("go: %v", err)
   159  		}
   160  		if !hasFilepathPrefix(abs, modRoot) {
   161  			base.Fatalf("go: pattern %s refers to dir %s, outside module root %s", pattern, abs, modRoot)
   162  			return nil
   163  		}
   164  	}
   165  
   166  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   167  		if err != nil || !fi.IsDir() {
   168  			return nil
   169  		}
   170  		top := false
   171  		if path == dir {
   172  			// filepath.Walk starts at dir and recurses. For the recursive case,
   173  			// the path is the result of filepath.Join, which calls filepath.Clean.
   174  			// The initial case is not Cleaned, though, so we do this explicitly.
   175  			//
   176  			// This converts a path like "./io/" to "io". Without this step, running
   177  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   178  			// package, because prepending the prefix "./" to the unclean path would
   179  			// result in "././io", and match("././io") returns false.
   180  			top = true
   181  			path = filepath.Clean(path)
   182  		}
   183  
   184  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   185  		_, elem := filepath.Split(path)
   186  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   187  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   188  			return filepath.SkipDir
   189  		}
   190  
   191  		if !top && cfg.ModulesEnabled {
   192  			// Ignore other modules found in subdirectories.
   193  			if _, err := os.Stat(filepath.Join(path, "go.mod")); err == nil {
   194  				return filepath.SkipDir
   195  			}
   196  		}
   197  
   198  		name := prefix + filepath.ToSlash(path)
   199  		if !match(name) {
   200  			return nil
   201  		}
   202  
   203  		// We keep the directory if we can import it, or if we can't import it
   204  		// due to invalid Go source files. This means that directories containing
   205  		// parse errors will be built (and fail) instead of being silently skipped
   206  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   207  		// behavior means people miss serious mistakes.
   208  		// See golang.org/issue/11407.
   209  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   210  			if _, noGo := err.(*build.NoGoError); !noGo {
   211  				log.Print(err)
   212  			}
   213  			return nil
   214  		}
   215  		m.Pkgs = append(m.Pkgs, name)
   216  		return nil
   217  	})
   218  	return m
   219  }
   220  
   221  // TreeCanMatchPattern(pattern)(name) reports whether
   222  // name or children of name can possibly match pattern.
   223  // Pattern is the same limited glob accepted by matchPattern.
   224  func TreeCanMatchPattern(pattern string) func(name string) bool {
   225  	wildCard := false
   226  	if i := strings.Index(pattern, "..."); i >= 0 {
   227  		wildCard = true
   228  		pattern = pattern[:i]
   229  	}
   230  	return func(name string) bool {
   231  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   232  			wildCard && strings.HasPrefix(name, pattern)
   233  	}
   234  }
   235  
   236  // MatchPattern(pattern)(name) reports whether
   237  // name matches pattern. Pattern is a limited glob
   238  // pattern in which '...' means 'any string' and there
   239  // is no other special syntax.
   240  // Unfortunately, there are two special cases. Quoting "go help packages":
   241  //
   242  // First, /... at the end of the pattern can match an empty string,
   243  // so that net/... matches both net and packages in its subdirectories, like net/http.
   244  // Second, any slash-separted pattern element containing a wildcard never
   245  // participates in a match of the "vendor" element in the path of a vendored
   246  // package, so that ./... does not match packages in subdirectories of
   247  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
   248  // Note, however, that a directory named vendor that itself contains code
   249  // is not a vendored package: cmd/vendor would be a command named vendor,
   250  // and the pattern cmd/... matches it.
   251  func MatchPattern(pattern string) func(name string) bool {
   252  	// Convert pattern to regular expression.
   253  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
   254  	// The strategy for the vendor exclusion is to change the unmatchable
   255  	// vendor strings to a disallowed code point (vendorChar) and to use
   256  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
   257  	// This is a bit complicated but the obvious alternative,
   258  	// namely a hand-written search like in most shell glob matchers,
   259  	// is too easy to make accidentally exponential.
   260  	// Using package regexp guarantees linear-time matching.
   261  
   262  	const vendorChar = "\x00"
   263  
   264  	if strings.Contains(pattern, vendorChar) {
   265  		return func(name string) bool { return false }
   266  	}
   267  
   268  	re := regexp.QuoteMeta(pattern)
   269  	re = replaceVendor(re, vendorChar)
   270  	switch {
   271  	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
   272  		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
   273  	case re == vendorChar+`/\.\.\.`:
   274  		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
   275  	case strings.HasSuffix(re, `/\.\.\.`):
   276  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
   277  	}
   278  	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
   279  
   280  	reg := regexp.MustCompile(`^` + re + `$`)
   281  
   282  	return func(name string) bool {
   283  		if strings.Contains(name, vendorChar) {
   284  			return false
   285  		}
   286  		return reg.MatchString(replaceVendor(name, vendorChar))
   287  	}
   288  }
   289  
   290  // replaceVendor returns the result of replacing
   291  // non-trailing vendor path elements in x with repl.
   292  func replaceVendor(x, repl string) string {
   293  	if !strings.Contains(x, "vendor") {
   294  		return x
   295  	}
   296  	elem := strings.Split(x, "/")
   297  	for i := 0; i < len(elem)-1; i++ {
   298  		if elem[i] == "vendor" {
   299  			elem[i] = repl
   300  		}
   301  	}
   302  	return strings.Join(elem, "/")
   303  }
   304  
   305  // WarnUnmatched warns about patterns that didn't match any packages.
   306  func WarnUnmatched(matches []*Match) {
   307  	for _, m := range matches {
   308  		if len(m.Pkgs) == 0 {
   309  			fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.Pattern)
   310  		}
   311  	}
   312  }
   313  
   314  // ImportPaths returns the matching paths to use for the given command line.
   315  // It calls ImportPathsQuiet and then WarnUnmatched.
   316  func ImportPaths(patterns []string) []*Match {
   317  	matches := ImportPathsQuiet(patterns)
   318  	WarnUnmatched(matches)
   319  	return matches
   320  }
   321  
   322  // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
   323  func ImportPathsQuiet(patterns []string) []*Match {
   324  	var out []*Match
   325  	for _, a := range CleanPatterns(patterns) {
   326  		if IsMetaPackage(a) {
   327  			out = append(out, MatchPackages(a))
   328  			continue
   329  		}
   330  		if strings.Contains(a, "...") {
   331  			if build.IsLocalImport(a) {
   332  				out = append(out, MatchPackagesInFS(a))
   333  			} else {
   334  				out = append(out, MatchPackages(a))
   335  			}
   336  			continue
   337  		}
   338  		out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}})
   339  	}
   340  	return out
   341  }
   342  
   343  // CleanPatterns returns the patterns to use for the given
   344  // command line. It canonicalizes the patterns but does not
   345  // evaluate any matches.
   346  func CleanPatterns(patterns []string) []string {
   347  	if len(patterns) == 0 {
   348  		return []string{"."}
   349  	}
   350  	var out []string
   351  	for _, a := range patterns {
   352  		// Arguments are supposed to be import paths, but
   353  		// as a courtesy to Windows developers, rewrite \ to /
   354  		// in command-line arguments. Handles .\... and so on.
   355  		if filepath.Separator == '\\' {
   356  			a = strings.ReplaceAll(a, `\`, `/`)
   357  		}
   358  
   359  		// Put argument in canonical form, but preserve leading ./.
   360  		if strings.HasPrefix(a, "./") {
   361  			a = "./" + path.Clean(a)
   362  			if a == "./." {
   363  				a = "."
   364  			}
   365  		} else {
   366  			a = path.Clean(a)
   367  		}
   368  		out = append(out, a)
   369  	}
   370  	return out
   371  }
   372  
   373  // IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
   374  func IsMetaPackage(name string) bool {
   375  	return name == "std" || name == "cmd" || name == "all"
   376  }
   377  
   378  // hasPathPrefix reports whether the path s begins with the
   379  // elements in prefix.
   380  func hasPathPrefix(s, prefix string) bool {
   381  	switch {
   382  	default:
   383  		return false
   384  	case len(s) == len(prefix):
   385  		return s == prefix
   386  	case len(s) > len(prefix):
   387  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   388  			return strings.HasPrefix(s, prefix)
   389  		}
   390  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   391  	}
   392  }
   393  
   394  // hasFilepathPrefix reports whether the path s begins with the
   395  // elements in prefix.
   396  func hasFilepathPrefix(s, prefix string) bool {
   397  	switch {
   398  	default:
   399  		return false
   400  	case len(s) == len(prefix):
   401  		return s == prefix
   402  	case len(s) > len(prefix):
   403  		if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
   404  			return strings.HasPrefix(s, prefix)
   405  		}
   406  		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
   407  	}
   408  }
   409  
   410  // IsStandardImportPath reports whether $GOROOT/src/path should be considered
   411  // part of the standard distribution. For historical reasons we allow people to add
   412  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   413  // code will start with a domain name (dot in the first element).
   414  //
   415  // Note that this function is meant to evaluate whether a directory found in GOROOT
   416  // should be treated as part of the standard library. It should not be used to decide
   417  // that a directory found in GOPATH should be rejected: directories in GOPATH
   418  // need not have dots in the first element, and they just take their chances
   419  // with future collisions in the standard library.
   420  func IsStandardImportPath(path string) bool {
   421  	i := strings.Index(path, "/")
   422  	if i < 0 {
   423  		i = len(path)
   424  	}
   425  	elem := path[:i]
   426  	return !strings.Contains(elem, ".")
   427  }
   428  
   429  // IsRelativePath reports whether pattern should be interpreted as a directory
   430  // path relative to the current directory, as opposed to a pattern matching
   431  // import paths.
   432  func IsRelativePath(pattern string) bool {
   433  	return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
   434  }
   435  
   436  // InDir checks whether path is in the file tree rooted at dir.
   437  // If so, InDir returns an equivalent path relative to dir.
   438  // If not, InDir returns an empty string.
   439  // InDir makes some effort to succeed even in the presence of symbolic links.
   440  // TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
   441  func InDir(path, dir string) string {
   442  	if rel := inDirLex(path, dir); rel != "" {
   443  		return rel
   444  	}
   445  	xpath, err := filepath.EvalSymlinks(path)
   446  	if err != nil || xpath == path {
   447  		xpath = ""
   448  	} else {
   449  		if rel := inDirLex(xpath, dir); rel != "" {
   450  			return rel
   451  		}
   452  	}
   453  
   454  	xdir, err := filepath.EvalSymlinks(dir)
   455  	if err == nil && xdir != dir {
   456  		if rel := inDirLex(path, xdir); rel != "" {
   457  			return rel
   458  		}
   459  		if xpath != "" {
   460  			if rel := inDirLex(xpath, xdir); rel != "" {
   461  				return rel
   462  			}
   463  		}
   464  	}
   465  	return ""
   466  }
   467  
   468  // inDirLex is like inDir but only checks the lexical form of the file names.
   469  // It does not consider symbolic links.
   470  // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
   471  // return the suffix. Most uses of str.HasFilePathPrefix should probably
   472  // be calling InDir instead.
   473  func inDirLex(path, dir string) string {
   474  	pv := strings.ToUpper(filepath.VolumeName(path))
   475  	dv := strings.ToUpper(filepath.VolumeName(dir))
   476  	path = path[len(pv):]
   477  	dir = dir[len(dv):]
   478  	switch {
   479  	default:
   480  		return ""
   481  	case pv != dv:
   482  		return ""
   483  	case len(path) == len(dir):
   484  		if path == dir {
   485  			return "."
   486  		}
   487  		return ""
   488  	case dir == "":
   489  		return path
   490  	case len(path) > len(dir):
   491  		if dir[len(dir)-1] == filepath.Separator {
   492  			if path[:len(dir)] == dir {
   493  				return path[len(dir):]
   494  			}
   495  			return ""
   496  		}
   497  		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
   498  			if len(path) == len(dir)+1 {
   499  				return "."
   500  			}
   501  			return path[len(dir)+1:]
   502  		}
   503  		return ""
   504  	}
   505  }