gopkg.in/tools/godep.v63@v63.0.0-20160503185544-51f9ea00dbee/list.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"go/build"
     7  	"go/parser"
     8  	"go/token"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  	"unicode"
    16  
    17  	pathpkg "path"
    18  )
    19  
    20  var (
    21  	gorootSrc            = filepath.Join(build.Default.GOROOT, "src")
    22  	ignoreTags           = []string{"appengine", "ignore"} //TODO: appengine is a special case for now: https://github.com/tools/godep/issues/353
    23  	versionMatch         = regexp.MustCompile(`\Ago\d+\.\d+\z`)
    24  	versionNegativeMatch = regexp.MustCompile(`\A\!go\d+\.\d+\z`)
    25  )
    26  
    27  type errorMissingDep struct {
    28  	i, dir string // import, dir
    29  }
    30  
    31  func (e errorMissingDep) Error() string {
    32  	return "Unable to find dependent package " + e.i + " in context of " + e.dir
    33  }
    34  
    35  // packageContext is used to track an import and which package imported it.
    36  type packageContext struct {
    37  	pkg *build.Package // package that imports the import
    38  	imp string         // import
    39  }
    40  
    41  // depScanner tracks the processed and to be processed packageContexts
    42  type depScanner struct {
    43  	processed []packageContext
    44  	todo      []packageContext
    45  }
    46  
    47  // Next package and import to process
    48  func (ds *depScanner) Next() (*build.Package, string) {
    49  	c := ds.todo[0]
    50  	ds.processed = append(ds.processed, c)
    51  	ds.todo = ds.todo[1:]
    52  	return c.pkg, c.imp
    53  }
    54  
    55  // Continue looping?
    56  func (ds *depScanner) Continue() bool {
    57  	if len(ds.todo) > 0 {
    58  		return true
    59  	}
    60  	return false
    61  }
    62  
    63  // Add a package and imports to the depScanner. Skips already processed/pending package/import combos
    64  func (ds *depScanner) Add(pkg *build.Package, imports ...string) {
    65  NextImport:
    66  	for _, i := range imports {
    67  		if i == "C" {
    68  			i = "runtime/cgo"
    69  		}
    70  		for _, epc := range ds.processed {
    71  			if pkg.Dir == epc.pkg.Dir && i == epc.imp {
    72  				debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i)
    73  				continue NextImport
    74  			}
    75  		}
    76  		for _, epc := range ds.todo {
    77  			if pkg.Dir == epc.pkg.Dir && i == epc.imp {
    78  				debugln("ctxts epc.pkg.Dir == pkg.Dir && i == epc.imp, skipping", epc.pkg.Dir, i)
    79  				continue NextImport
    80  			}
    81  		}
    82  		pc := packageContext{pkg, i}
    83  		debugln("Adding pc:", pc.pkg.Dir, pc.imp)
    84  		ds.todo = append(ds.todo, pc)
    85  	}
    86  }
    87  
    88  var (
    89  	pkgCache = make(map[string]*build.Package) // dir => *build.Package
    90  )
    91  
    92  // returns the package in dir either from a cache or by importing it and then caching it
    93  func fullPackageInDir(dir string) (*build.Package, error) {
    94  	var err error
    95  	pkg, ok := pkgCache[dir]
    96  	if !ok {
    97  		pkg, err = build.ImportDir(dir, build.FindOnly)
    98  		if pkg.Goroot {
    99  			pkg, err = build.ImportDir(pkg.Dir, 0)
   100  		} else {
   101  			fillPackage(pkg)
   102  		}
   103  		if err == nil {
   104  			pkgCache[dir] = pkg
   105  		}
   106  	}
   107  	return pkg, err
   108  }
   109  
   110  // listPackage specified by path
   111  func listPackage(path string) (*Package, error) {
   112  	debugln("listPackage", path)
   113  	var lp *build.Package
   114  	dir, err := findDirForPath(path, nil)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	lp, err = fullPackageInDir(dir)
   119  	p := &Package{
   120  		Dir:            lp.Dir,
   121  		Root:           lp.Root,
   122  		ImportPath:     lp.ImportPath,
   123  		XTestImports:   lp.XTestImports,
   124  		TestImports:    lp.TestImports,
   125  		GoFiles:        lp.GoFiles,
   126  		CgoFiles:       lp.CgoFiles,
   127  		TestGoFiles:    lp.TestGoFiles,
   128  		XTestGoFiles:   lp.XTestGoFiles,
   129  		IgnoredGoFiles: lp.IgnoredGoFiles,
   130  	}
   131  	p.Standard = lp.Goroot && lp.ImportPath != "" && !strings.Contains(lp.ImportPath, ".")
   132  	if err != nil || p.Standard {
   133  		return p, err
   134  	}
   135  	debugln("Looking For Package:", path, "in", dir)
   136  	ppln(lp)
   137  
   138  	ds := depScanner{}
   139  	ds.Add(lp, lp.Imports...)
   140  	for ds.Continue() {
   141  		ip, i := ds.Next()
   142  
   143  		debugf("Processing import %s for %s\n", i, ip.Dir)
   144  		pdir, err := findDirForPath(i, ip)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  		dp, err := fullPackageInDir(pdir)
   149  		if err != nil { // This really should happen in this context though
   150  			ppln(err)
   151  			return nil, errorMissingDep{i: i, dir: ip.Dir}
   152  		}
   153  		ppln(dp)
   154  		if !dp.Goroot {
   155  			// Don't bother adding packages in GOROOT to the dependency scanner, they don't import things from outside of it.
   156  			ds.Add(dp, dp.Imports...)
   157  		}
   158  		debugln("lp:")
   159  		ppln(lp)
   160  		debugln("ip:")
   161  		ppln(ip)
   162  		if lp == ip {
   163  			debugln("lp == ip")
   164  			p.Imports = append(p.Imports, dp.ImportPath)
   165  		}
   166  		p.Deps = append(p.Deps, dp.ImportPath)
   167  	}
   168  	p.Imports = uniq(p.Imports)
   169  	p.Deps = uniq(p.Deps)
   170  	debugln("Done Looking For Package:", path, "in", dir)
   171  	ppln(p)
   172  	return p, nil
   173  }
   174  
   175  // finds the directory for the given import path in the context of the provided build.Package (if provided)
   176  func findDirForPath(path string, ip *build.Package) (string, error) {
   177  	debugln("findDirForPath", path, ip)
   178  	var search []string
   179  
   180  	if build.IsLocalImport(path) {
   181  		dir := path
   182  		if !filepath.IsAbs(dir) {
   183  			if abs, err := filepath.Abs(dir); err == nil {
   184  				// interpret relative to current directory
   185  				dir = abs
   186  			}
   187  		}
   188  		return dir, nil
   189  	}
   190  
   191  	// We need to check to see if the import exists in vendor/ folders up the hierarchy of the importing package
   192  	if VendorExperiment && ip != nil {
   193  		debugln("resolving vendor posibilities:", ip.Dir, ip.Root)
   194  		cr := cleanPath(ip.Root)
   195  
   196  		for base := cleanPath(ip.Dir); !pathEqual(base, cr); base = cleanPath(filepath.Dir(base)) {
   197  			s := filepath.Join(base, "vendor", path)
   198  			debugln("Adding search dir:", s)
   199  			search = append(search, s)
   200  		}
   201  	}
   202  
   203  	for _, base := range build.Default.SrcDirs() {
   204  		search = append(search, filepath.Join(base, path))
   205  	}
   206  
   207  	for _, dir := range search {
   208  		debugln("searching", dir)
   209  		fi, err := os.Stat(dir)
   210  		if err == nil && fi.IsDir() {
   211  			return dir, nil
   212  		}
   213  	}
   214  
   215  	return "", errPackageNotFound{path}
   216  }
   217  
   218  // fillPackage full of info. Assumes p.Dir is set at a minimum
   219  func fillPackage(p *build.Package) error {
   220  	if p.Goroot {
   221  		return nil
   222  	}
   223  
   224  	if p.SrcRoot == "" {
   225  		for _, base := range build.Default.SrcDirs() {
   226  			if strings.HasPrefix(p.Dir, base) {
   227  				p.SrcRoot = base
   228  			}
   229  		}
   230  	}
   231  
   232  	if p.SrcRoot == "" {
   233  		return errors.New("Unable to find SrcRoot for package " + p.ImportPath)
   234  	}
   235  
   236  	if p.Root == "" {
   237  		p.Root = filepath.Dir(p.SrcRoot)
   238  	}
   239  
   240  	var buildMatch = "+build "
   241  	var buildFieldSplit = func(r rune) bool {
   242  		return unicode.IsSpace(r) || r == ','
   243  	}
   244  
   245  	debugln("Filling package:", p.ImportPath, "from", p.Dir)
   246  	gofiles, err := filepath.Glob(filepath.Join(p.Dir, "*.go"))
   247  	if err != nil {
   248  		debugln("Error globbing", err)
   249  		return err
   250  	}
   251  
   252  	var testImports []string
   253  	var imports []string
   254  NextFile:
   255  	for _, file := range gofiles {
   256  		debugln(file)
   257  		pf, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly|parser.ParseComments)
   258  		if err != nil {
   259  			return err
   260  		}
   261  		testFile := strings.HasSuffix(file, "_test.go")
   262  		fname := filepath.Base(file)
   263  		for _, c := range pf.Comments {
   264  			ct := c.Text()
   265  			if i := strings.Index(ct, buildMatch); i != -1 {
   266  				for _, t := range strings.FieldsFunc(ct[i+len(buildMatch):], buildFieldSplit) {
   267  					for _, tag := range ignoreTags {
   268  						if t == tag {
   269  							p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
   270  							continue NextFile
   271  						}
   272  					}
   273  
   274  					if versionMatch.MatchString(t) && !isSameOrNewer(t, majorGoVersion) {
   275  						debugln("Adding", fname, "to ignored list because of version tag", t)
   276  						p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
   277  						continue NextFile
   278  					}
   279  					if versionNegativeMatch.MatchString(t) && isSameOrNewer(t[1:], majorGoVersion) {
   280  						debugln("Adding", fname, "to ignored list because of version tag", t)
   281  						p.IgnoredGoFiles = append(p.IgnoredGoFiles, fname)
   282  						continue NextFile
   283  					}
   284  				}
   285  			}
   286  		}
   287  		if testFile {
   288  			p.TestGoFiles = append(p.TestGoFiles, fname)
   289  		} else {
   290  			p.GoFiles = append(p.GoFiles, fname)
   291  		}
   292  		for _, is := range pf.Imports {
   293  			name, err := strconv.Unquote(is.Path.Value)
   294  			if err != nil {
   295  				return err // can't happen?
   296  			}
   297  			if testFile {
   298  				testImports = append(testImports, name)
   299  			} else {
   300  				imports = append(imports, name)
   301  			}
   302  		}
   303  	}
   304  	imports = uniq(imports)
   305  	testImports = uniq(testImports)
   306  	p.Imports = imports
   307  	p.TestImports = testImports
   308  	return nil
   309  }
   310  
   311  // All of the following functions were vendored from go proper. Locations are noted in comments, but may change in future Go versions.
   312  
   313  // importPaths returns the import paths to use for the given command line.
   314  // $GOROOT/src/cmd/main.go:366
   315  func importPaths(args []string) []string {
   316  	args = importPathsNoDotExpansion(args)
   317  	var out []string
   318  	for _, a := range args {
   319  		if strings.Contains(a, "...") {
   320  			if build.IsLocalImport(a) {
   321  				out = append(out, allPackagesInFS(a)...)
   322  			} else {
   323  				out = append(out, allPackages(a)...)
   324  			}
   325  			continue
   326  		}
   327  		out = append(out, a)
   328  	}
   329  	return out
   330  }
   331  
   332  // importPathsNoDotExpansion returns the import paths to use for the given
   333  // command line, but it does no ... expansion.
   334  // $GOROOT/src/cmd/main.go:332
   335  func importPathsNoDotExpansion(args []string) []string {
   336  	if len(args) == 0 {
   337  		return []string{"."}
   338  	}
   339  	var out []string
   340  	for _, a := range args {
   341  		// Arguments are supposed to be import paths, but
   342  		// as a courtesy to Windows developers, rewrite \ to /
   343  		// in command-line arguments.  Handles .\... and so on.
   344  		if filepath.Separator == '\\' {
   345  			a = strings.Replace(a, `\`, `/`, -1)
   346  		}
   347  
   348  		// Put argument in canonical form, but preserve leading ./.
   349  		if strings.HasPrefix(a, "./") {
   350  			a = "./" + pathpkg.Clean(a)
   351  			if a == "./." {
   352  				a = "."
   353  			}
   354  		} else {
   355  			a = pathpkg.Clean(a)
   356  		}
   357  		if a == "all" || a == "std" || a == "cmd" {
   358  			out = append(out, allPackages(a)...)
   359  			continue
   360  		}
   361  		out = append(out, a)
   362  	}
   363  	return out
   364  }
   365  
   366  // allPackagesInFS is like allPackages but is passed a pattern
   367  // beginning ./ or ../, meaning it should scan the tree rooted
   368  // at the given directory.  There are ... in the pattern too.
   369  // $GOROOT/src/cmd/main.go:620
   370  func allPackagesInFS(pattern string) []string {
   371  	pkgs := matchPackagesInFS(pattern)
   372  	if len(pkgs) == 0 {
   373  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   374  	}
   375  	return pkgs
   376  }
   377  
   378  // allPackages returns all the packages that can be found
   379  // under the $GOPATH directories and $GOROOT matching pattern.
   380  // The pattern is either "all" (all packages), "std" (standard packages),
   381  // "cmd" (standard commands), or a path including "...".
   382  // $GOROOT/src/cmd/main.go:542
   383  func allPackages(pattern string) []string {
   384  	pkgs := matchPackages(pattern)
   385  	if len(pkgs) == 0 {
   386  		fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
   387  	}
   388  	return pkgs
   389  }
   390  
   391  // $GOROOT/src/cmd/main.go:554
   392  func matchPackages(pattern string) []string {
   393  	match := func(string) bool { return true }
   394  	treeCanMatch := func(string) bool { return true }
   395  	if pattern != "all" && pattern != "std" && pattern != "cmd" {
   396  		match = matchPattern(pattern)
   397  		treeCanMatch = treeCanMatchPattern(pattern)
   398  	}
   399  
   400  	have := map[string]bool{
   401  		"builtin": true, // ignore pseudo-package that exists only for documentation
   402  	}
   403  	if !build.Default.CgoEnabled {
   404  		have["runtime/cgo"] = true // ignore during walk
   405  	}
   406  	var pkgs []string
   407  
   408  	for _, src := range build.Default.SrcDirs() {
   409  		if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
   410  			continue
   411  		}
   412  		src = filepath.Clean(src) + string(filepath.Separator)
   413  		root := src
   414  		if pattern == "cmd" {
   415  			root += "cmd" + string(filepath.Separator)
   416  		}
   417  		filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
   418  			if err != nil || !fi.IsDir() || path == src {
   419  				return nil
   420  			}
   421  
   422  			// Avoid .foo, _foo, and testdata directory trees.
   423  			_, elem := filepath.Split(path)
   424  			if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
   425  				return filepath.SkipDir
   426  			}
   427  
   428  			name := filepath.ToSlash(path[len(src):])
   429  			if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") {
   430  				// The name "std" is only the standard library.
   431  				// If the name has a dot, assume it's a domain name for go get,
   432  				// and if the name is cmd, it's the root of the command tree.
   433  				return filepath.SkipDir
   434  			}
   435  			if !treeCanMatch(name) {
   436  				return filepath.SkipDir
   437  			}
   438  			if have[name] {
   439  				return nil
   440  			}
   441  			have[name] = true
   442  			if !match(name) {
   443  				return nil
   444  			}
   445  			_, err = build.ImportDir(path, 0)
   446  			if err != nil {
   447  				if _, noGo := err.(*build.NoGoError); noGo {
   448  					return nil
   449  				}
   450  			}
   451  			pkgs = append(pkgs, name)
   452  			return nil
   453  		})
   454  	}
   455  	return pkgs
   456  }
   457  
   458  // treeCanMatchPattern(pattern)(name) reports whether
   459  // name or children of name can possibly match pattern.
   460  // Pattern is the same limited glob accepted by matchPattern.
   461  // $GOROOT/src/cmd/main.go:527
   462  func treeCanMatchPattern(pattern string) func(name string) bool {
   463  	wildCard := false
   464  	if i := strings.Index(pattern, "..."); i >= 0 {
   465  		wildCard = true
   466  		pattern = pattern[:i]
   467  	}
   468  	return func(name string) bool {
   469  		return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
   470  			wildCard && strings.HasPrefix(name, pattern)
   471  	}
   472  }
   473  
   474  // hasPathPrefix reports whether the path s begins with the
   475  // elements in prefix.
   476  // $GOROOT/src/cmd/main.go:489
   477  func hasPathPrefix(s, prefix string) bool {
   478  	switch {
   479  	default:
   480  		return false
   481  	case len(s) == len(prefix):
   482  		return s == prefix
   483  	case len(s) > len(prefix):
   484  		if prefix != "" && prefix[len(prefix)-1] == '/' {
   485  			return strings.HasPrefix(s, prefix)
   486  		}
   487  		return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
   488  	}
   489  }
   490  
   491  // $GOROOT/src/cmd/go/main.go:631
   492  func matchPackagesInFS(pattern string) []string {
   493  	// Find directory to begin the scan.
   494  	// Could be smarter but this one optimization
   495  	// is enough for now, since ... is usually at the
   496  	// end of a path.
   497  	i := strings.Index(pattern, "...")
   498  	dir, _ := pathpkg.Split(pattern[:i])
   499  
   500  	// pattern begins with ./ or ../.
   501  	// path.Clean will discard the ./ but not the ../.
   502  	// We need to preserve the ./ for pattern matching
   503  	// and in the returned import paths.
   504  	prefix := ""
   505  	if strings.HasPrefix(pattern, "./") {
   506  		prefix = "./"
   507  	}
   508  	match := matchPattern(pattern)
   509  
   510  	var pkgs []string
   511  	filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
   512  		if err != nil || !fi.IsDir() {
   513  			return nil
   514  		}
   515  		if path == dir {
   516  			// filepath.Walk starts at dir and recurses. For the recursive case,
   517  			// the path is the result of filepath.Join, which calls filepath.Clean.
   518  			// The initial case is not Cleaned, though, so we do this explicitly.
   519  			//
   520  			// This converts a path like "./io/" to "io". Without this step, running
   521  			// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
   522  			// package, because prepending the prefix "./" to the unclean path would
   523  			// result in "././io", and match("././io") returns false.
   524  			path = filepath.Clean(path)
   525  		}
   526  
   527  		// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
   528  		_, elem := filepath.Split(path)
   529  		dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
   530  		if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
   531  			return filepath.SkipDir
   532  		}
   533  
   534  		name := prefix + filepath.ToSlash(path)
   535  		if !match(name) {
   536  			return nil
   537  		}
   538  		if _, err = build.ImportDir(path, 0); err != nil {
   539  			if _, noGo := err.(*build.NoGoError); !noGo {
   540  				log.Print(err)
   541  			}
   542  			return nil
   543  		}
   544  		pkgs = append(pkgs, name)
   545  		return nil
   546  	})
   547  	return pkgs
   548  }