Home | History | Annotate | Download | only in load
      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 }
    386