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