github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/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  			_, err = cfg.BuildContext.ImportDir(path, 0)
    97  			if err != nil {
    98  				if _, noGo := err.(*build.NoGoError); noGo {
    99  					return nil
   100  				}
   101  			}
   102  			pkgs = append(pkgs, name)
   103  			return nil
   104  		})
   105  	}
   106  	return pkgs
   107  }
   108  
   109  // MatchPackagesInFS returns a list of package paths matching pattern,
   110  // which must begin with ./ or ../
   111  // (see go help packages for pattern syntax).
   112  func MatchPackagesInFS(pattern string) []string {
   113  	// Find directory to begin the scan.
   114  	// Could be smarter but this one optimization
   115  	// is enough for now, since ... is usually at the
   116  	// end of a path.
   117  	i := strings.Index(pattern, "...")
   118  	dir, _ := path.Split(pattern[:i])
   119  
   120  	// pattern begins with ./ or ../.
   121  	// path.Clean will discard the ./ but not the ../.
   122  	// We need to preserve the ./ for pattern matching
   123  	// and in the returned import paths.
   124  	prefix := ""
   125  	if strings.HasPrefix(pattern, "./") {
   126  		prefix = "./"
   127  	}
   128  	match := matchPattern(pattern)
   129  
   130  	var pkgs []string
   131  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   132  		if err != nil || !fi.IsDir() {
   133  			return nil
   134  		}
   135  		if path == dir {
   136  			// filepath.Walk starts at dir and recurses. For the recursive case,
   137  			// the path is the result of filepath.Join, which calls filepath.Clean.
   138  			// The initial case is not Cleaned, though, so we do this explicitly.
   139  			//
   140  			// This converts a path like "./io/" to "io". Without this step, running
   141  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   142  			// package, because prepending the prefix "./" to the unclean path would
   143  			// result in "././io", and match("././io") returns false.
   144  			path = filepath.Clean(path)
   145  		}
   146  
   147  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   148  		_, elem := filepath.Split(path)
   149  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   150  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   151  			return filepath.SkipDir
   152  		}
   153  
   154  		name := prefix + filepath.ToSlash(path)
   155  		if !match(name) {
   156  			return nil
   157  		}
   158  
   159  		// We keep the directory if we can import it, or if we can't import it
   160  		// due to invalid Go source files. This means that directories containing
   161  		// parse errors will be built (and fail) instead of being silently skipped
   162  		// as not matching the pattern. Go 1.5 and earlier skipped, but that
   163  		// behavior means people miss serious mistakes.
   164  		// See golang.org/issue/11407.
   165  		if p, err := cfg.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
   166  			if _, noGo := err.(*build.NoGoError); !noGo {
   167  				log.Print(err)
   168  			}
   169  			return nil
   170  		}
   171  		pkgs = append(pkgs, name)
   172  		return nil
   173  	})
   174  	return pkgs
   175  }
   176  
   177  // treeCanMatchPattern(pattern)(name) reports whether
   178  // name or children of name can possibly match pattern.
   179  // Pattern is the same limited glob accepted by matchPattern.
   180  func treeCanMatchPattern(pattern string) func(name string) bool {
   181  	wildCard := false
   182  	if i := strings.Index(pattern, "..."); i >= 0 {
   183  		wildCard = true
   184  		pattern = pattern[:i]
   185  	}
   186  	return func(name string) bool {
   187  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   188  			wildCard && strings.HasPrefix(name, pattern)
   189  	}
   190  }
   191  
   192  // matchPattern(pattern)(name) reports whether
   193  // name matches pattern. Pattern is a limited glob
   194  // pattern in which '...' means 'any string' and there
   195  // is no other special syntax.
   196  func matchPattern(pattern string) func(name string) bool {
   197  	re := regexp.QuoteMeta(pattern)
   198  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
   199  	// Special case: foo/... matches foo too.
   200  	if strings.HasSuffix(re, `/.*`) {
   201  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
   202  	}
   203  	reg := regexp.MustCompile(`^` + re + `$`)
   204  	return func(name string) bool {
   205  		return reg.MatchString(name)
   206  	}
   207  }
   208  
   209  // ImportPaths returns the import paths to use for the given command line.
   210  func ImportPaths(args []string) []string {
   211  	args = ImportPathsNoDotExpansion(args)
   212  	var out []string
   213  	for _, a := range args {
   214  		if strings.Contains(a, "...") {
   215  			if build.IsLocalImport(a) {
   216  				out = append(out, allPackagesInFS(a)...)
   217  			} else {
   218  				out = append(out, allPackages(a)...)
   219  			}
   220  			continue
   221  		}
   222  		out = append(out, a)
   223  	}
   224  	return out
   225  }
   226  
   227  // ImportPathsNoDotExpansion returns the import paths to use for the given
   228  // command line, but it does no ... expansion.
   229  func ImportPathsNoDotExpansion(args []string) []string {
   230  	if len(args) == 0 {
   231  		return []string{"."}
   232  	}
   233  	var out []string
   234  	for _, a := range args {
   235  		// Arguments are supposed to be import paths, but
   236  		// as a courtesy to Windows developers, rewrite \ to /
   237  		// in command-line arguments. Handles .\... and so on.
   238  		if filepath.Separator == '\\' {
   239  			a = strings.Replace(a, `\`, `/`, -1)
   240  		}
   241  
   242  		// Put argument in canonical form, but preserve leading ./.
   243  		if strings.HasPrefix(a, "./") {
   244  			a = "./" + path.Clean(a)
   245  			if a == "./." {
   246  				a = "."
   247  			}
   248  		} else {
   249  			a = path.Clean(a)
   250  		}
   251  		if IsMetaPackage(a) {
   252  			out = append(out, allPackages(a)...)
   253  			continue
   254  		}
   255  		out = append(out, a)
   256  	}
   257  	return out
   258  }
   259  
   260  // isMetaPackage checks if name is a reserved package name that expands to multiple packages.
   261  func IsMetaPackage(name string) bool {
   262  	return name == "std" || name == "cmd" || name == "all"
   263  }