import.go raw

   1  package main
   2  
   3  /*
   4  
   5  This file holds a direct copy of the import path matching code of
   6  https://github.com/golang/go/blob/master/src/cmd/go/main.go. It can be
   7  replaced when https://golang.org/issue/8768 is resolved.
   8  
   9  It has been updated to follow upstream changes in a few ways.
  10  
  11  */
  12  
  13  import (
  14  	"fmt"
  15  	"go/build"
  16  	"log"
  17  	"os"
  18  	"path"
  19  	"path/filepath"
  20  	"regexp"
  21  	"runtime"
  22  	"strings"
  23  )
  24  
  25  var (
  26  	buildContext = build.Default
  27  	goroot       = filepath.Clean(runtime.GOROOT())
  28  	gorootSrc    = filepath.Join(goroot, "src")
  29  )
  30  
  31  // importPathsNoDotExpansion returns the import paths to use for the given
  32  // command line, but it does no ... expansion.
  33  func importPathsNoDotExpansion(args []string) []string {
  34  	if len(args) == 0 {
  35  		return []string{"."}
  36  	}
  37  	var out []string
  38  	for _, a := range args {
  39  		// Arguments are supposed to be import paths, but
  40  		// as a courtesy to Windows developers, rewrite \ to /
  41  		// in command-line arguments.  Handles .\... and so on.
  42  		if filepath.Separator == '\\' {
  43  			a = strings.Replace(a, `\`, `/`, -1)
  44  		}
  45  
  46  		// Put argument in canonical form, but preserve leading ./.
  47  		if strings.HasPrefix(a, "./") {
  48  			a = "./" + path.Clean(a)
  49  			if a == "./." {
  50  				a = "."
  51  			}
  52  		} else {
  53  			a = path.Clean(a)
  54  		}
  55  		if a == "all" || a == "std" {
  56  			out = append(out, allPackages(a)...)
  57  			continue
  58  		}
  59  		out = append(out, a)
  60  	}
  61  	return out
  62  }
  63  
  64  // importPaths returns the import paths to use for the given command line.
  65  func importPaths(args []string) []string {
  66  	args = importPathsNoDotExpansion(args)
  67  	var out []string
  68  	for _, a := range args {
  69  		if strings.Contains(a, "...") {
  70  			if build.IsLocalImport(a) {
  71  				out = append(out, allPackagesInFS(a)...)
  72  			} else {
  73  				out = append(out, allPackages(a)...)
  74  			}
  75  			continue
  76  		}
  77  		out = append(out, a)
  78  	}
  79  	return out
  80  }
  81  
  82  // matchPattern(pattern)(name) reports whether
  83  // name matches pattern.  Pattern is a limited glob
  84  // pattern in which '...' means 'any string' and there
  85  // is no other special syntax.
  86  func matchPattern(pattern string) func(name string) bool {
  87  	re := regexp.QuoteMeta(pattern)
  88  	re = strings.Replace(re, `\.\.\.`, `.*`, -1)
  89  	// Special case: foo/... matches foo too.
  90  	if strings.HasSuffix(re, `/.*`) {
  91  		re = re[:len(re)-len(`/.*`)] + `(/.*)?`
  92  	}
  93  	reg := regexp.MustCompile(`^` + re + `$`)
  94  	return func(name string) bool {
  95  		return reg.MatchString(name)
  96  	}
  97  }
  98  
  99  // hasPathPrefix reports whether the path s begins with the
 100  // elements in prefix.
 101  func hasPathPrefix(s, prefix string) bool {
 102  	switch {
 103  	default:
 104  		return false
 105  	case len(s) == len(prefix):
 106  		return s == prefix
 107  	case len(s) > len(prefix):
 108  		if prefix != "" && prefix[len(prefix)-1] == '/' {
 109  			return strings.HasPrefix(s, prefix)
 110  		}
 111  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
 112  	}
 113  }
 114  
 115  // treeCanMatchPattern(pattern)(name) reports whether
 116  // name or children of name can possibly match pattern.
 117  // Pattern is the same limited glob accepted by matchPattern.
 118  func treeCanMatchPattern(pattern string) func(name string) bool {
 119  	wildCard := false
 120  	if i := strings.Index(pattern, "..."); i >= 0 {
 121  		wildCard = true
 122  		pattern = pattern[:i]
 123  	}
 124  	return func(name string) bool {
 125  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
 126  			wildCard && strings.HasPrefix(name, pattern)
 127  	}
 128  }
 129  
 130  // allPackages returns all the packages that can be found
 131  // under the $GOPATH directories and $GOROOT matching pattern.
 132  // The pattern is either "all" (all packages), "std" (standard packages)
 133  // or a path including "...".
 134  func allPackages(pattern string) []string {
 135  	pkgs := matchPackages(pattern)
 136  	if len(pkgs) == 0 {
 137  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
 138  	}
 139  	return pkgs
 140  }
 141  
 142  func matchPackages(pattern string) []string {
 143  	match := func(string) bool { return true }
 144  	treeCanMatch := func(string) bool { return true }
 145  	if pattern != "all" && pattern != "std" {
 146  		match = matchPattern(pattern)
 147  		treeCanMatch = treeCanMatchPattern(pattern)
 148  	}
 149  
 150  	have := map[string]bool{
 151  		"builtin": true, // ignore pseudo-package that exists only for documentation
 152  	}
 153  	if !buildContext.CgoEnabled {
 154  		have["runtime/cgo"] = true // ignore during walk
 155  	}
 156  	var pkgs []string
 157  
 158  	// Commands
 159  	cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator)
 160  	filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error {
 161  		if err != nil || !fi.IsDir() || path == cmd {
 162  			return nil
 163  		}
 164  		name := path[len(cmd):]
 165  		if !treeCanMatch(name) {
 166  			return filepath.SkipDir
 167  		}
 168  		// Commands are all in cmd/, not in subdirectories.
 169  		if strings.Contains(name, string(filepath.Separator)) {
 170  			return filepath.SkipDir
 171  		}
 172  
 173  		// We use, e.g., cmd/gofmt as the pseudo import path for gofmt.
 174  		name = "cmd/" + name
 175  		if have[name] {
 176  			return nil
 177  		}
 178  		have[name] = true
 179  		if !match(name) {
 180  			return nil
 181  		}
 182  		_, err = buildContext.ImportDir(path, 0)
 183  		if err != nil {
 184  			if _, noGo := err.(*build.NoGoError); !noGo {
 185  				log.Print(err)
 186  			}
 187  			return nil
 188  		}
 189  		pkgs = append(pkgs, name)
 190  		return nil
 191  	})
 192  
 193  	for _, src := range buildContext.SrcDirs() {
 194  		if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
 195  			continue
 196  		}
 197  		src = filepath.Clean(src) + string(filepath.Separator)
 198  		root := src
 199  		if pattern == "cmd" {
 200  			root += "cmd" + string(filepath.Separator)
 201  		}
 202  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
 203  			if err != nil || !fi.IsDir() || path == src {
 204  				return nil
 205  			}
 206  
 207  			// Avoid .foo, _foo, and testdata directory trees.
 208  			_, elem := filepath.Split(path)
 209  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
 210  				return filepath.SkipDir
 211  			}
 212  
 213  			name := filepath.ToSlash(path[len(src):])
 214  			if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
 215  				// The name "std" is only the standard library.
 216  				// If the name is cmd, it's the root of the command tree.
 217  				return filepath.SkipDir
 218  			}
 219  			if !treeCanMatch(name) {
 220  				return filepath.SkipDir
 221  			}
 222  			if have[name] {
 223  				return nil
 224  			}
 225  			have[name] = true
 226  			if !match(name) {
 227  				return nil
 228  			}
 229  			_, err = buildContext.ImportDir(path, 0)
 230  			if err != nil {
 231  				if _, noGo := err.(*build.NoGoError); noGo {
 232  					return nil
 233  				}
 234  			}
 235  			pkgs = append(pkgs, name)
 236  			return nil
 237  		})
 238  	}
 239  	return pkgs
 240  }
 241  
 242  // allPackagesInFS is like allPackages but is passed a pattern
 243  // beginning ./ or ../, meaning it should scan the tree rooted
 244  // at the given directory.  There are ... in the pattern too.
 245  func allPackagesInFS(pattern string) []string {
 246  	pkgs := matchPackagesInFS(pattern)
 247  	if len(pkgs) == 0 {
 248  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
 249  	}
 250  	return pkgs
 251  }
 252  
 253  func matchPackagesInFS(pattern string) []string {
 254  	// Find directory to begin the scan.
 255  	// Could be smarter but this one optimization
 256  	// is enough for now, since ... is usually at the
 257  	// end of a path.
 258  	i := strings.Index(pattern, "...")
 259  	dir, _ := path.Split(pattern[:i])
 260  
 261  	// pattern begins with ./ or ../.
 262  	// path.Clean will discard the ./ but not the ../.
 263  	// We need to preserve the ./ for pattern matching
 264  	// and in the returned import paths.
 265  	prefix := ""
 266  	if strings.HasPrefix(pattern, "./") {
 267  		prefix = "./"
 268  	}
 269  	match := matchPattern(pattern)
 270  
 271  	var pkgs []string
 272  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
 273  		if err != nil || !fi.IsDir() {
 274  			return nil
 275  		}
 276  		if path == dir {
 277  			// filepath.Walk starts at dir and recurses. For the recursive case,
 278  			// the path is the result of filepath.Join, which calls filepath.Clean.
 279  			// The initial case is not Cleaned, though, so we do this explicitly.
 280  			//
 281  			// This converts a path like "./io/" to "io". Without this step, running
 282  			// "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io
 283  			// package, because prepending the prefix "./" to the unclean path would
 284  			// result in "././io", and match("././io") returns false.
 285  			path = filepath.Clean(path)
 286  		}
 287  
 288  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
 289  		_, elem := filepath.Split(path)
 290  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
 291  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
 292  			return filepath.SkipDir
 293  		}
 294  
 295  		name := prefix + filepath.ToSlash(path)
 296  		if !match(name) {
 297  			return nil
 298  		}
 299  		if _, err = build.ImportDir(path, 0); err != nil {
 300  			if _, noGo := err.(*build.NoGoError); !noGo {
 301  				log.Print(err)
 302  			}
 303  			return nil
 304  		}
 305  		pkgs = append(pkgs, name)
 306  		return nil
 307  	})
 308  	return pkgs
 309  }
 310