gopkg.in/tools/godep.v41@v41.0.0-20151217180337-fe5ce707f879/list.go (about)

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