github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-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  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/base"
     9  	"github.com/gagliardetto/golang-go/cmd/go/not-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, "github.com/gagliardetto/golang-go/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 MatchPackages but is passed a pattern that
   129  // begins with an absolute path or "./" or "../". On Windows, the pattern may
   130  // use slash or backslash separators or a mix of both.
   131  //
   132  // MatchPackagesInFS scans the tree rooted at the directory that contains the
   133  // first "..." wildcard and returns a match with packages that
   134  func MatchPackagesInFS(pattern string) *Match {
   135  	m := &Match{
   136  		Pattern: pattern,
   137  		Literal: false,
   138  	}
   139  
   140  	// Clean the path and create a matching predicate.
   141  	// filepath.Clean removes "./" prefixes (and ".\" on Windows). We need to
   142  	// preserve these, since they are meaningful in MatchPattern and in
   143  	// returned import paths.
   144  	cleanPattern := filepath.Clean(pattern)
   145  	isLocal := strings.HasPrefix(pattern, "./") || (os.PathSeparator == '\\' && strings.HasPrefix(pattern, `.\`))
   146  	prefix := ""
   147  	if cleanPattern != "." && isLocal {
   148  		prefix = "./"
   149  		cleanPattern = "." + string(os.PathSeparator) + cleanPattern
   150  	}
   151  	slashPattern := filepath.ToSlash(cleanPattern)
   152  	match := MatchPattern(slashPattern)
   153  
   154  	// Find directory to begin the scan.
   155  	// Could be smarter but this one optimization
   156  	// is enough for now, since ... is usually at the
   157  	// end of a path.
   158  	i := strings.Index(cleanPattern, "...")
   159  	dir, _ := filepath.Split(cleanPattern[:i])
   160  
   161  	// pattern begins with ./ or ../.
   162  	// path.Clean will discard the ./ but not the ../.
   163  	// We need to preserve the ./ for pattern matching
   164  	// and in the returned import paths.
   165  
   166  	if modRoot != "" {
   167  		abs, err := filepath.Abs(dir)
   168  		if err != nil {
   169  			base.Fatalf("go: %v", err)
   170  		}
   171  		if !hasFilepathPrefix(abs, modRoot) {
   172  			base.Fatalf("go: pattern %s refers to dir %s, outside module root %s", pattern, abs, modRoot)
   173  			return nil
   174  		}
   175  	}
   176  
   177  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   178  		if err != nil || !fi.IsDir() {
   179  			return nil
   180  		}
   181  		top := false
   182  		if path == dir {
   183  			// filepath.Walk starts at dir and recurses. For the recursive case,
   184  			// the path is the result of filepath.Join, which calls filepath.Clean.
   185  			// The initial case is not Cleaned, though, so we do this explicitly.
   186  			//
   187  			// This converts a path like "./io/" to "io". Without this step, running
   188  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   189  			// package, because prepending the prefix "./" to the unclean path would
   190  			// result in "././io", and match("././io") returns false.
   191  			top = true
   192  			path = filepath.Clean(path)
   193  		}
   194  
   195  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   196  		_, elem := filepath.Split(path)
   197  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   198  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   199  			return filepath.SkipDir
   200  		}
   201  
   202  		if !top && cfg.ModulesEnabled {
   203  			// Ignore other modules found in subdirectories.
   204  			if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
   205  				return filepath.SkipDir
   206  			}
   207  		}
   208  
   209  		name := prefix + filepath.ToSlash(path)
   210  		if !match(name) {
   211  			return nil
   212  		}
   213  
   214  		// We keep the directory if we can import it, or if we can't import it
   215  		// due to invalid Go source files. This means that directories containing
   216  		// parse errors will be built (and fail) instead of being silently skipped
   217  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   218  		// behavior means people miss serious mistakes.
   219  		// See golang.org/issue/11407.
   220  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   221  			if _, noGo := err.(*build.NoGoError); !noGo {
   222  				log.Print(err)
   223  			}
   224  			return nil
   225  		}
   226  		m.Pkgs = append(m.Pkgs, name)
   227  		return nil
   228  	})
   229  	return m
   230  }
   231  
   232  // TreeCanMatchPattern(pattern)(name) reports whether
   233  // name or children of name can possibly match pattern.
   234  // Pattern is the same limited glob accepted by matchPattern.
   235  func TreeCanMatchPattern(pattern string) func(name string) bool {
   236  	wildCard := false
   237  	if i := strings.Index(pattern, "..."); i >= 0 {
   238  		wildCard = true
   239  		pattern = pattern[:i]
   240  	}
   241  	return func(name string) bool {
   242  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   243  			wildCard && strings.HasPrefix(name, pattern)
   244  	}
   245  }
   246  
   247  // MatchPattern(pattern)(name) reports whether
   248  // name matches pattern. Pattern is a limited glob
   249  // pattern in which '...' means 'any string' and there
   250  // is no other special syntax.
   251  // Unfortunately, there are two special cases. Quoting "go help packages":
   252  //
   253  // First, /... at the end of the pattern can match an empty string,
   254  // so that net/... matches both net and packages in its subdirectories, like net/http.
   255  // Second, any slash-separated pattern element containing a wildcard never
   256  // participates in a match of the "vendor" element in the path of a vendored
   257  // package, so that ./... does not match packages in subdirectories of
   258  // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
   259  // Note, however, that a directory named vendor that itself contains code
   260  // is not a vendored package: cmd/vendor would be a command named vendor,
   261  // and the pattern cmd/... matches it.
   262  func MatchPattern(pattern string) func(name string) bool {
   263  	// Convert pattern to regular expression.
   264  	// The strategy for the trailing /... is to nest it in an explicit ? expression.
   265  	// The strategy for the vendor exclusion is to change the unmatchable
   266  	// vendor strings to a disallowed code point (vendorChar) and to use
   267  	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
   268  	// This is a bit complicated but the obvious alternative,
   269  	// namely a hand-written search like in most shell glob matchers,
   270  	// is too easy to make accidentally exponential.
   271  	// Using package regexp guarantees linear-time matching.
   272  
   273  	const vendorChar = "\x00"
   274  
   275  	if strings.Contains(pattern, vendorChar) {
   276  		return func(name string) bool { return false }
   277  	}
   278  
   279  	re := regexp.QuoteMeta(pattern)
   280  	re = replaceVendor(re, vendorChar)
   281  	switch {
   282  	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
   283  		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
   284  	case re == vendorChar+`/\.\.\.`:
   285  		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
   286  	case strings.HasSuffix(re, `/\.\.\.`):
   287  		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
   288  	}
   289  	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
   290  
   291  	reg := regexp.MustCompile(`^` + re + `$`)
   292  
   293  	return func(name string) bool {
   294  		if strings.Contains(name, vendorChar) {
   295  			return false
   296  		}
   297  		return reg.MatchString(replaceVendor(name, vendorChar))
   298  	}
   299  }
   300  
   301  // replaceVendor returns the result of replacing
   302  // non-trailing vendor path elements in x with repl.
   303  func replaceVendor(x, repl string) string {
   304  	if !strings.Contains(x, "vendor") {
   305  		return x
   306  	}
   307  	elem := strings.Split(x, "/")
   308  	for i := 0; i < len(elem)-1; i++ {
   309  		if elem[i] == "vendor" {
   310  			elem[i] = repl
   311  		}
   312  	}
   313  	return strings.Join(elem, "/")
   314  }
   315  
   316  // WarnUnmatched warns about patterns that didn't match any packages.
   317  func WarnUnmatched(matches []*Match) {
   318  	for _, m := range matches {
   319  		if len(m.Pkgs) == 0 {
   320  			fmt.Fprintf(os.Stderr, "go: warning: %q matched no packages\n", m.Pattern)
   321  		}
   322  	}
   323  }
   324  
   325  // ImportPaths returns the matching paths to use for the given command line.
   326  // It calls ImportPathsQuiet and then WarnUnmatched.
   327  func ImportPaths(patterns []string) []*Match {
   328  	matches := ImportPathsQuiet(patterns)
   329  	WarnUnmatched(matches)
   330  	return matches
   331  }
   332  
   333  // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
   334  func ImportPathsQuiet(patterns []string) []*Match {
   335  	var out []*Match
   336  	for _, a := range CleanPatterns(patterns) {
   337  		if IsMetaPackage(a) {
   338  			out = append(out, MatchPackages(a))
   339  			continue
   340  		}
   341  
   342  		if build.IsLocalImport(a) || filepath.IsAbs(a) {
   343  			var m *Match
   344  			if strings.Contains(a, "...") {
   345  				m = MatchPackagesInFS(a)
   346  			} else {
   347  				m = &Match{Pattern: a, Literal: true, Pkgs: []string{a}}
   348  			}
   349  
   350  			// Change the file import path to a regular import path if the package
   351  			// is in GOPATH or GOROOT. We don't report errors here; LoadImport
   352  			// (or something similar) will report them later.
   353  			for i, dir := range m.Pkgs {
   354  				if !filepath.IsAbs(dir) {
   355  					dir = filepath.Join(base.Cwd, dir)
   356  				}
   357  				if bp, _ := cfg.BuildContext.ImportDir(dir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
   358  					m.Pkgs[i] = bp.ImportPath
   359  				}
   360  			}
   361  			out = append(out, m)
   362  			continue
   363  		}
   364  
   365  		if strings.Contains(a, "...") {
   366  			out = append(out, MatchPackages(a))
   367  			continue
   368  		}
   369  
   370  		out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}})
   371  	}
   372  	return out
   373  }
   374  
   375  // CleanPatterns returns the patterns to use for the given command line. It
   376  // canonicalizes the patterns but does not evaluate any matches. For patterns
   377  // that are not local or absolute paths, it preserves text after '@' to avoid
   378  // modifying version queries.
   379  func CleanPatterns(patterns []string) []string {
   380  	if len(patterns) == 0 {
   381  		return []string{"."}
   382  	}
   383  	var out []string
   384  	for _, a := range patterns {
   385  		var p, v string
   386  		if build.IsLocalImport(a) || filepath.IsAbs(a) {
   387  			p = a
   388  		} else if i := strings.IndexByte(a, '@'); i < 0 {
   389  			p = a
   390  		} else {
   391  			p = a[:i]
   392  			v = a[i:]
   393  		}
   394  
   395  		// Arguments may be either file paths or import paths.
   396  		// As a courtesy to Windows developers, rewrite \ to /
   397  		// in arguments that look like import paths.
   398  		// Don't replace slashes in absolute paths.
   399  		if filepath.IsAbs(p) {
   400  			p = filepath.Clean(p)
   401  		} else {
   402  			if filepath.Separator == '\\' {
   403  				p = strings.ReplaceAll(p, `\`, `/`)
   404  			}
   405  
   406  			// Put argument in canonical form, but preserve leading ./.
   407  			if strings.HasPrefix(p, "./") {
   408  				p = "./" + path.Clean(p)
   409  				if p == "./." {
   410  					p = "."
   411  				}
   412  			} else {
   413  				p = path.Clean(p)
   414  			}
   415  		}
   416  
   417  		out = append(out, p+v)
   418  	}
   419  	return out
   420  }
   421  
   422  // IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
   423  func IsMetaPackage(name string) bool {
   424  	return name == "std" || name == "cmd" || name == "all"
   425  }
   426  
   427  // hasPathPrefix reports whether the path s begins with the
   428  // elements in prefix.
   429  func hasPathPrefix(s, prefix string) bool {
   430  	switch {
   431  	default:
   432  		return false
   433  	case len(s) == len(prefix):
   434  		return s == prefix
   435  	case len(s) > len(prefix):
   436  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   437  			return strings.HasPrefix(s, prefix)
   438  		}
   439  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   440  	}
   441  }
   442  
   443  // hasFilepathPrefix reports whether the path s begins with the
   444  // elements in prefix.
   445  func hasFilepathPrefix(s, prefix string) bool {
   446  	switch {
   447  	default:
   448  		return false
   449  	case len(s) == len(prefix):
   450  		return s == prefix
   451  	case len(s) > len(prefix):
   452  		if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
   453  			return strings.HasPrefix(s, prefix)
   454  		}
   455  		return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
   456  	}
   457  }
   458  
   459  // IsStandardImportPath reports whether $GOROOT/src/path should be considered
   460  // part of the standard distribution. For historical reasons we allow people to add
   461  // their own code to $GOROOT instead of using $GOPATH, but we assume that
   462  // code will start with a domain name (dot in the first element).
   463  //
   464  // Note that this function is meant to evaluate whether a directory found in GOROOT
   465  // should be treated as part of the standard library. It should not be used to decide
   466  // that a directory found in GOPATH should be rejected: directories in GOPATH
   467  // need not have dots in the first element, and they just take their chances
   468  // with future collisions in the standard library.
   469  func IsStandardImportPath(path string) bool {
   470  	i := strings.Index(path, "/")
   471  	if i < 0 {
   472  		i = len(path)
   473  	}
   474  	elem := path[:i]
   475  	return !strings.Contains(elem, ".")
   476  }
   477  
   478  // IsRelativePath reports whether pattern should be interpreted as a directory
   479  // path relative to the current directory, as opposed to a pattern matching
   480  // import paths.
   481  func IsRelativePath(pattern string) bool {
   482  	return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
   483  }
   484  
   485  // InDir checks whether path is in the file tree rooted at dir.
   486  // If so, InDir returns an equivalent path relative to dir.
   487  // If not, InDir returns an empty string.
   488  // InDir makes some effort to succeed even in the presence of symbolic links.
   489  // TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
   490  func InDir(path, dir string) string {
   491  	if rel := inDirLex(path, dir); rel != "" {
   492  		return rel
   493  	}
   494  	xpath, err := filepath.EvalSymlinks(path)
   495  	if err != nil || xpath == path {
   496  		xpath = ""
   497  	} else {
   498  		if rel := inDirLex(xpath, dir); rel != "" {
   499  			return rel
   500  		}
   501  	}
   502  
   503  	xdir, err := filepath.EvalSymlinks(dir)
   504  	if err == nil && xdir != dir {
   505  		if rel := inDirLex(path, xdir); rel != "" {
   506  			return rel
   507  		}
   508  		if xpath != "" {
   509  			if rel := inDirLex(xpath, xdir); rel != "" {
   510  				return rel
   511  			}
   512  		}
   513  	}
   514  	return ""
   515  }
   516  
   517  // inDirLex is like inDir but only checks the lexical form of the file names.
   518  // It does not consider symbolic links.
   519  // TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
   520  // return the suffix. Most uses of str.HasFilePathPrefix should probably
   521  // be calling InDir instead.
   522  func inDirLex(path, dir string) string {
   523  	pv := strings.ToUpper(filepath.VolumeName(path))
   524  	dv := strings.ToUpper(filepath.VolumeName(dir))
   525  	path = path[len(pv):]
   526  	dir = dir[len(dv):]
   527  	switch {
   528  	default:
   529  		return ""
   530  	case pv != dv:
   531  		return ""
   532  	case len(path) == len(dir):
   533  		if path == dir {
   534  			return "."
   535  		}
   536  		return ""
   537  	case dir == "":
   538  		return path
   539  	case len(path) > len(dir):
   540  		if dir[len(dir)-1] == filepath.Separator {
   541  			if path[:len(dir)] == dir {
   542  				return path[len(dir):]
   543  			}
   544  			return ""
   545  		}
   546  		if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
   547  			if len(path) == len(dir)+1 {
   548  				return "."
   549  			}
   550  			return path[len(dir)+1:]
   551  		}
   552  		return ""
   553  	}
   554  }