...

Source file src/pkg/cmd/go/internal/search/search.go

     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 fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
   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	
   331			if build.IsLocalImport(a) || filepath.IsAbs(a) {
   332				var m *Match
   333				if strings.Contains(a, "...") {
   334					m = MatchPackagesInFS(a)
   335				} else {
   336					m = &Match{Pattern: a, Literal: true, Pkgs: []string{a}}
   337				}
   338	
   339				// Change the file import path to a regular import path if the package
   340				// is in GOPATH or GOROOT. We don't report errors here; LoadImport
   341				// (or something similar) will report them later.
   342				for i, dir := range m.Pkgs {
   343					if !filepath.IsAbs(dir) {
   344						dir = filepath.Join(base.Cwd, dir)
   345					}
   346					if bp, _ := cfg.BuildContext.ImportDir(dir, build.FindOnly); bp.ImportPath != "" && bp.ImportPath != "." {
   347						m.Pkgs[i] = bp.ImportPath
   348					}
   349				}
   350				out = append(out, m)
   351				continue
   352			}
   353	
   354			if strings.Contains(a, "...") {
   355				out = append(out, MatchPackages(a))
   356				continue
   357			}
   358	
   359			out = append(out, &Match{Pattern: a, Literal: true, Pkgs: []string{a}})
   360		}
   361		return out
   362	}
   363	
   364	// CleanPatterns returns the patterns to use for the given
   365	// command line. It canonicalizes the patterns but does not
   366	// evaluate any matches. It preserves text after '@' for commands
   367	// that accept versions.
   368	func CleanPatterns(patterns []string) []string {
   369		if len(patterns) == 0 {
   370			return []string{"."}
   371		}
   372		var out []string
   373		for _, a := range patterns {
   374			var p, v string
   375			if i := strings.IndexByte(a, '@'); i < 0 {
   376				p = a
   377			} else {
   378				p = a[:i]
   379				v = a[i:]
   380			}
   381	
   382			// Arguments are supposed to be import paths, but
   383			// as a courtesy to Windows developers, rewrite \ to /
   384			// in command-line arguments. Handles .\... and so on.
   385			if filepath.Separator == '\\' {
   386				p = strings.ReplaceAll(p, `\`, `/`)
   387			}
   388	
   389			// Put argument in canonical form, but preserve leading ./.
   390			if strings.HasPrefix(p, "./") {
   391				p = "./" + path.Clean(p)
   392				if p == "./." {
   393					p = "."
   394				}
   395			} else {
   396				p = path.Clean(p)
   397			}
   398	
   399			out = append(out, p+v)
   400		}
   401		return out
   402	}
   403	
   404	// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
   405	func IsMetaPackage(name string) bool {
   406		return name == "std" || name == "cmd" || name == "all"
   407	}
   408	
   409	// hasPathPrefix reports whether the path s begins with the
   410	// elements in prefix.
   411	func hasPathPrefix(s, prefix string) bool {
   412		switch {
   413		default:
   414			return false
   415		case len(s) == len(prefix):
   416			return s == prefix
   417		case len(s) > len(prefix):
   418			if prefix != "" && prefix[len(prefix)-1] == '/' {
   419				return strings.HasPrefix(s, prefix)
   420			}
   421			return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   422		}
   423	}
   424	
   425	// hasFilepathPrefix reports whether the path s begins with the
   426	// elements in prefix.
   427	func hasFilepathPrefix(s, prefix string) bool {
   428		switch {
   429		default:
   430			return false
   431		case len(s) == len(prefix):
   432			return s == prefix
   433		case len(s) > len(prefix):
   434			if prefix != "" && prefix[len(prefix)-1] == filepath.Separator {
   435				return strings.HasPrefix(s, prefix)
   436			}
   437			return s[len(prefix)] == filepath.Separator && s[:len(prefix)] == prefix
   438		}
   439	}
   440	
   441	// IsStandardImportPath reports whether $GOROOT/src/path should be considered
   442	// part of the standard distribution. For historical reasons we allow people to add
   443	// their own code to $GOROOT instead of using $GOPATH, but we assume that
   444	// code will start with a domain name (dot in the first element).
   445	//
   446	// Note that this function is meant to evaluate whether a directory found in GOROOT
   447	// should be treated as part of the standard library. It should not be used to decide
   448	// that a directory found in GOPATH should be rejected: directories in GOPATH
   449	// need not have dots in the first element, and they just take their chances
   450	// with future collisions in the standard library.
   451	func IsStandardImportPath(path string) bool {
   452		i := strings.Index(path, "/")
   453		if i < 0 {
   454			i = len(path)
   455		}
   456		elem := path[:i]
   457		return !strings.Contains(elem, ".")
   458	}
   459	
   460	// IsRelativePath reports whether pattern should be interpreted as a directory
   461	// path relative to the current directory, as opposed to a pattern matching
   462	// import paths.
   463	func IsRelativePath(pattern string) bool {
   464		return strings.HasPrefix(pattern, "./") || strings.HasPrefix(pattern, "../") || pattern == "." || pattern == ".."
   465	}
   466	
   467	// InDir checks whether path is in the file tree rooted at dir.
   468	// If so, InDir returns an equivalent path relative to dir.
   469	// If not, InDir returns an empty string.
   470	// InDir makes some effort to succeed even in the presence of symbolic links.
   471	// TODO(rsc): Replace internal/test.inDir with a call to this function for Go 1.12.
   472	func InDir(path, dir string) string {
   473		if rel := inDirLex(path, dir); rel != "" {
   474			return rel
   475		}
   476		xpath, err := filepath.EvalSymlinks(path)
   477		if err != nil || xpath == path {
   478			xpath = ""
   479		} else {
   480			if rel := inDirLex(xpath, dir); rel != "" {
   481				return rel
   482			}
   483		}
   484	
   485		xdir, err := filepath.EvalSymlinks(dir)
   486		if err == nil && xdir != dir {
   487			if rel := inDirLex(path, xdir); rel != "" {
   488				return rel
   489			}
   490			if xpath != "" {
   491				if rel := inDirLex(xpath, xdir); rel != "" {
   492					return rel
   493				}
   494			}
   495		}
   496		return ""
   497	}
   498	
   499	// inDirLex is like inDir but only checks the lexical form of the file names.
   500	// It does not consider symbolic links.
   501	// TODO(rsc): This is a copy of str.HasFilePathPrefix, modified to
   502	// return the suffix. Most uses of str.HasFilePathPrefix should probably
   503	// be calling InDir instead.
   504	func inDirLex(path, dir string) string {
   505		pv := strings.ToUpper(filepath.VolumeName(path))
   506		dv := strings.ToUpper(filepath.VolumeName(dir))
   507		path = path[len(pv):]
   508		dir = dir[len(dv):]
   509		switch {
   510		default:
   511			return ""
   512		case pv != dv:
   513			return ""
   514		case len(path) == len(dir):
   515			if path == dir {
   516				return "."
   517			}
   518			return ""
   519		case dir == "":
   520			return path
   521		case len(path) > len(dir):
   522			if dir[len(dir)-1] == filepath.Separator {
   523				if path[:len(dir)] == dir {
   524					return path[len(dir):]
   525				}
   526				return ""
   527			}
   528			if path[len(dir)] == filepath.Separator && path[:len(dir)] == dir {
   529				if len(path) == len(dir)+1 {
   530					return "."
   531				}
   532				return path[len(dir)+1:]
   533			}
   534			return ""
   535		}
   536	}
   537	

View as plain text