github.com/golang/dep@v0.5.4/gps/pkgtree/pkgtree.go (about)

     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 pkgtree
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/build"
    12  	"go/parser"
    13  	gscan "go/scanner"
    14  	"go/token"
    15  	"os"
    16  	"path/filepath"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  	"unicode"
    21  )
    22  
    23  // Package represents a Go package. It contains a subset of the information
    24  // go/build.Package does.
    25  type Package struct {
    26  	Name        string   // Package name, as declared in the package statement
    27  	ImportPath  string   // Full import path, including the prefix provided to ListPackages()
    28  	CommentPath string   // Import path given in the comment on the package statement
    29  	Imports     []string // Imports from all go and cgo files
    30  	TestImports []string // Imports from all go test files (in go/build parlance: both TestImports and XTestImports)
    31  }
    32  
    33  // vcsRoots is a set of directories we should not descend into in ListPackages when
    34  // searching for Go packages
    35  var vcsRoots = map[string]struct{}{
    36  	".git": {},
    37  	".bzr": {},
    38  	".svn": {},
    39  	".hg":  {},
    40  }
    41  
    42  // ListPackages reports Go package information about all directories in the tree
    43  // at or below the provided fileRoot.
    44  //
    45  // The importRoot parameter is prepended to the relative path when determining
    46  // the import path for each package. The obvious case is for something typical,
    47  // like:
    48  //
    49  //  fileRoot = "/home/user/go/src/github.com/foo/bar"
    50  //  importRoot = "github.com/foo/bar"
    51  //
    52  // where the fileRoot and importRoot align. However, if you provide:
    53  //
    54  //  fileRoot = "/home/user/workspace/path/to/repo"
    55  //  importRoot = "github.com/foo/bar"
    56  //
    57  // then the root package at path/to/repo will be ascribed import path
    58  // "github.com/foo/bar", and the package at
    59  // "/home/user/workspace/path/to/repo/baz" will be "github.com/foo/bar/baz".
    60  //
    61  // A PackageTree is returned, which contains the ImportRoot and map of import path
    62  // to PackageOrErr - each path under the root that exists will have either a
    63  // Package, or an error describing why the directory is not a valid package.
    64  func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
    65  	ptree := PackageTree{
    66  		ImportRoot: importRoot,
    67  		Packages:   make(map[string]PackageOrErr),
    68  	}
    69  
    70  	var err error
    71  	fileRoot, err = filepath.Abs(fileRoot)
    72  	if err != nil {
    73  		return PackageTree{}, err
    74  	}
    75  
    76  	err = filepath.Walk(fileRoot, func(wp string, fi os.FileInfo, err error) error {
    77  		if err != nil && err != filepath.SkipDir {
    78  			if os.IsPermission(err) {
    79  				return filepath.SkipDir
    80  			}
    81  			return err
    82  		}
    83  		if !fi.IsDir() {
    84  			return nil
    85  		}
    86  
    87  		// Skip dirs that are known to hold non-local/dependency code.
    88  		//
    89  		// We don't skip _*, or testdata dirs because, while it may be poor
    90  		// form, importing them is not a compilation error.
    91  		switch fi.Name() {
    92  		case "vendor":
    93  			return filepath.SkipDir
    94  		}
    95  
    96  		// Skip dirs that are known to be VCS roots.
    97  		//
    98  		// Note that there are some pathological edge cases this doesn't cover,
    99  		// such as a user using Git for version control, but having a package
   100  		// named "svn" in a directory named ".svn".
   101  		if _, ok := vcsRoots[fi.Name()]; ok {
   102  			return filepath.SkipDir
   103  		}
   104  
   105  		{
   106  			// For Go 1.9 and earlier:
   107  			//
   108  			// The entry error is nil when visiting a directory that itself is
   109  			// untraversable, as it's still governed by the parent directory's
   110  			// perms. We have to check readability of the dir here, because
   111  			// otherwise we'll have an empty package entry when we fail to read any
   112  			// of the dir's contents.
   113  			//
   114  			// If we didn't check here, then the next time this closure is called it
   115  			// would have an err with the same path as is called this time, as only
   116  			// then will filepath.Walk have attempted to descend into the directory
   117  			// and encountered an error.
   118  			var f *os.File
   119  			f, err = os.Open(wp)
   120  			if err != nil {
   121  				if os.IsPermission(err) {
   122  					return filepath.SkipDir
   123  				}
   124  				return err
   125  			}
   126  			f.Close()
   127  		}
   128  
   129  		// Compute the import path. Run the result through ToSlash(), so that
   130  		// windows file paths are normalized to slashes, as is expected of
   131  		// import paths.
   132  		ip := filepath.ToSlash(filepath.Join(importRoot, strings.TrimPrefix(wp, fileRoot)))
   133  
   134  		// Find all the imports, across all os/arch combos
   135  		p := &build.Package{
   136  			Dir:        wp,
   137  			ImportPath: ip,
   138  		}
   139  		err = fillPackage(p)
   140  
   141  		if err != nil {
   142  			switch err.(type) {
   143  			case gscan.ErrorList, *gscan.Error, *build.NoGoError, *ConflictingImportComments:
   144  				// Assorted cases in which we've encountered malformed or
   145  				// nonexistent Go source code.
   146  				ptree.Packages[ip] = PackageOrErr{
   147  					Err: err,
   148  				}
   149  				return nil
   150  			default:
   151  				return err
   152  			}
   153  		}
   154  
   155  		pkg := Package{
   156  			ImportPath:  ip,
   157  			CommentPath: p.ImportComment,
   158  			Name:        p.Name,
   159  			Imports:     p.Imports,
   160  			TestImports: dedupeStrings(p.TestImports, p.XTestImports),
   161  		}
   162  
   163  		if pkg.CommentPath != "" && !strings.HasPrefix(pkg.CommentPath, importRoot) {
   164  			ptree.Packages[ip] = PackageOrErr{
   165  				Err: &NonCanonicalImportRoot{
   166  					ImportRoot: importRoot,
   167  					Canonical:  pkg.CommentPath,
   168  				},
   169  			}
   170  			return nil
   171  		}
   172  
   173  		// This area has some...fuzzy rules, but check all the imports for
   174  		// local/relative/dot-ness, and record an error for the package if we
   175  		// see any.
   176  		var lim []string
   177  		for _, imp := range append(pkg.Imports, pkg.TestImports...) {
   178  			if build.IsLocalImport(imp) {
   179  				// Do allow the single-dot, at least for now
   180  				if imp == "." {
   181  					continue
   182  				}
   183  				lim = append(lim, imp)
   184  			}
   185  		}
   186  
   187  		if len(lim) > 0 {
   188  			ptree.Packages[ip] = PackageOrErr{
   189  				Err: &LocalImportsError{
   190  					Dir:          wp,
   191  					ImportPath:   ip,
   192  					LocalImports: lim,
   193  				},
   194  			}
   195  		} else {
   196  			ptree.Packages[ip] = PackageOrErr{
   197  				P: pkg,
   198  			}
   199  		}
   200  
   201  		return nil
   202  	})
   203  
   204  	if err != nil {
   205  		return PackageTree{}, err
   206  	}
   207  
   208  	return ptree, nil
   209  }
   210  
   211  // fillPackage full of info. Assumes p.Dir is set at a minimum
   212  func fillPackage(p *build.Package) error {
   213  	var buildPrefix = "// +build "
   214  	var buildFieldSplit = func(r rune) bool {
   215  		return unicode.IsSpace(r) || r == ','
   216  	}
   217  
   218  	gofiles, err := filepath.Glob(filepath.Join(p.Dir, "*.go"))
   219  	if err != nil {
   220  		return err
   221  	}
   222  
   223  	if len(gofiles) == 0 {
   224  		return &build.NoGoError{Dir: p.Dir}
   225  	}
   226  
   227  	var testImports []string
   228  	var imports []string
   229  	var importComments []string
   230  	for _, file := range gofiles {
   231  		// Skip underscore-led or dot-led files, in keeping with the rest of the toolchain.
   232  		bPrefix := filepath.Base(file)[0]
   233  		if bPrefix == '_' || bPrefix == '.' {
   234  			continue
   235  		}
   236  
   237  		// Skip any directories that happened to get caught by glob
   238  		if stat, err := os.Stat(file); err == nil && stat.IsDir() {
   239  			continue
   240  		}
   241  
   242  		pf, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ImportsOnly|parser.ParseComments)
   243  		if err != nil {
   244  			if os.IsPermission(err) {
   245  				continue
   246  			}
   247  			return err
   248  		}
   249  		testFile := strings.HasSuffix(file, "_test.go")
   250  		fname := filepath.Base(file)
   251  
   252  		var ignored bool
   253  		for _, c := range pf.Comments {
   254  			ic := findImportComment(pf.Name, c)
   255  			if ic != "" {
   256  				importComments = append(importComments, ic)
   257  			}
   258  			if c.Pos() > pf.Package { // "+build" comment must come before package
   259  				continue
   260  			}
   261  
   262  			var ct string
   263  			for _, cl := range c.List {
   264  				if strings.HasPrefix(cl.Text, buildPrefix) {
   265  					ct = cl.Text
   266  					break
   267  				}
   268  			}
   269  			if ct == "" {
   270  				continue
   271  			}
   272  
   273  			for _, t := range strings.FieldsFunc(ct[len(buildPrefix):], buildFieldSplit) {
   274  				// hardcoded (for now) handling for the "ignore" build tag
   275  				// We "soft" ignore the files tagged with ignore so that we pull in their imports.
   276  				if t == "ignore" {
   277  					ignored = true
   278  				}
   279  			}
   280  		}
   281  
   282  		if testFile {
   283  			p.TestGoFiles = append(p.TestGoFiles, fname)
   284  			if p.Name == "" && !ignored {
   285  				p.Name = strings.TrimSuffix(pf.Name.Name, "_test")
   286  			}
   287  		} else {
   288  			if p.Name == "" && !ignored {
   289  				p.Name = pf.Name.Name
   290  			}
   291  			p.GoFiles = append(p.GoFiles, fname)
   292  		}
   293  
   294  		for _, is := range pf.Imports {
   295  			name, err := strconv.Unquote(is.Path.Value)
   296  			if err != nil {
   297  				return err // can't happen?
   298  			}
   299  			if testFile {
   300  				testImports = append(testImports, name)
   301  			} else {
   302  				imports = append(imports, name)
   303  			}
   304  		}
   305  	}
   306  	importComments = uniq(importComments)
   307  	if len(importComments) > 1 {
   308  		return &ConflictingImportComments{
   309  			ImportPath:                p.ImportPath,
   310  			ConflictingImportComments: importComments,
   311  		}
   312  	}
   313  	if len(importComments) > 0 {
   314  		p.ImportComment = importComments[0]
   315  	}
   316  	imports = uniq(imports)
   317  	testImports = uniq(testImports)
   318  	p.Imports = imports
   319  	p.TestImports = testImports
   320  	return nil
   321  }
   322  
   323  var (
   324  	slashSlash = []byte("//")
   325  	slashStar  = []byte("/*")
   326  	starSlash  = []byte("*/")
   327  	importKwd  = []byte("import ")
   328  )
   329  
   330  func findImportComment(pkgName *ast.Ident, c *ast.CommentGroup) string {
   331  	afterPkg := pkgName.NamePos + token.Pos(len(pkgName.Name)) + 1
   332  	commentSlash := c.List[0].Slash
   333  	if afterPkg != commentSlash {
   334  		return ""
   335  	}
   336  	text := []byte(c.List[0].Text)
   337  	switch {
   338  	case bytes.HasPrefix(text, slashSlash):
   339  		eol := bytes.IndexByte(text, '\n')
   340  		if eol < 0 {
   341  			eol = len(text)
   342  		}
   343  		text = text[2:eol]
   344  	case bytes.HasPrefix(text, slashStar):
   345  		text = text[2:]
   346  		end := bytes.Index(text, starSlash)
   347  		if end < 0 {
   348  			// malformed comment
   349  			return ""
   350  		}
   351  		text = text[:end]
   352  		if bytes.IndexByte(text, '\n') >= 0 {
   353  			// multiline comment, can't be an import comment
   354  			return ""
   355  		}
   356  	}
   357  	text = bytes.TrimSpace(text)
   358  	if !bytes.HasPrefix(text, importKwd) {
   359  		return ""
   360  	}
   361  	quotedPath := bytes.TrimSpace(text[len(importKwd):])
   362  	return string(bytes.Trim(quotedPath, `"`))
   363  }
   364  
   365  // ConflictingImportComments indicates that the package declares more than one
   366  // different canonical path.
   367  type ConflictingImportComments struct {
   368  	ImportPath                string   // An import path referring to this package
   369  	ConflictingImportComments []string // All distinct "canonical" paths encountered in the package files
   370  }
   371  
   372  func (e *ConflictingImportComments) Error() string {
   373  	return fmt.Sprintf("import path %s had conflicting import comments: %s",
   374  		e.ImportPath, quotedPaths(e.ConflictingImportComments))
   375  }
   376  
   377  // NonCanonicalImportRoot reports the situation when the dependee imports a
   378  // package via something other than the package's declared canonical path.
   379  type NonCanonicalImportRoot struct {
   380  	ImportRoot string // A root path that is being used to import a package
   381  	Canonical  string // A canonical path declared by the package being imported
   382  }
   383  
   384  func (e *NonCanonicalImportRoot) Error() string {
   385  	return fmt.Sprintf("import root %q is not a prefix for the package's declared canonical path %q",
   386  		e.ImportRoot, e.Canonical)
   387  }
   388  
   389  func quotedPaths(ps []string) string {
   390  	quoted := make([]string, 0, len(ps))
   391  	for _, p := range ps {
   392  		quoted = append(quoted, fmt.Sprintf("%q", p))
   393  	}
   394  	return strings.Join(quoted, ", ")
   395  }
   396  
   397  // LocalImportsError indicates that a package contains at least one relative
   398  // import that will prevent it from compiling.
   399  //
   400  // TODO(sdboyer) add a Files property once we're doing our own per-file parsing
   401  type LocalImportsError struct {
   402  	ImportPath   string
   403  	Dir          string
   404  	LocalImports []string
   405  }
   406  
   407  func (e *LocalImportsError) Error() string {
   408  	switch len(e.LocalImports) {
   409  	case 0:
   410  		// shouldn't be possible, but just cover the case
   411  		return fmt.Sprintf("import path %s had bad local imports", e.ImportPath)
   412  	case 1:
   413  		return fmt.Sprintf("import path %s had a local import: %q", e.ImportPath, e.LocalImports[0])
   414  	default:
   415  		return fmt.Sprintf("import path %s had local imports: %s", e.ImportPath, quotedPaths(e.LocalImports))
   416  	}
   417  }
   418  
   419  type wm struct {
   420  	err error
   421  	ex  map[string]bool
   422  	in  map[string]bool
   423  }
   424  
   425  // PackageOrErr stores the results of attempting to parse a single directory for
   426  // Go source code.
   427  type PackageOrErr struct {
   428  	P   Package
   429  	Err error
   430  }
   431  
   432  // ProblemImportError describes the reason that a particular import path is
   433  // not safely importable.
   434  type ProblemImportError struct {
   435  	// The import path of the package with some problem rendering it
   436  	// unimportable.
   437  	ImportPath string
   438  	// The path to the internal package the problem package imports that is the
   439  	// original cause of this issue. If empty, the package itself is the
   440  	// problem.
   441  	Cause []string
   442  	// The actual error from ListPackages that is undermining importability for
   443  	// this package.
   444  	Err error
   445  }
   446  
   447  // Error formats the ProblemImportError as a string, reflecting whether the
   448  // error represents a direct or transitive problem.
   449  func (e *ProblemImportError) Error() string {
   450  	switch len(e.Cause) {
   451  	case 0:
   452  		return fmt.Sprintf("%q contains malformed code: %s", e.ImportPath, e.Err.Error())
   453  	case 1:
   454  		return fmt.Sprintf("%q imports %q, which contains malformed code: %s", e.ImportPath, e.Cause[0], e.Err.Error())
   455  	default:
   456  		return fmt.Sprintf("%q transitively (through %v packages) imports %q, which contains malformed code: %s", e.ImportPath, len(e.Cause)-1, e.Cause[len(e.Cause)-1], e.Err.Error())
   457  	}
   458  }
   459  
   460  // Helper func to create an error when a package is missing.
   461  func missingPkgErr(pkg string) error {
   462  	return fmt.Errorf("no package exists at %q", pkg)
   463  }
   464  
   465  // A PackageTree represents the results of recursively parsing a tree of
   466  // packages, starting at the ImportRoot. The results of parsing the files in the
   467  // directory identified by each import path - a Package or an error - are stored
   468  // in the Packages map, keyed by that import path.
   469  type PackageTree struct {
   470  	ImportRoot string
   471  	Packages   map[string]PackageOrErr
   472  }
   473  
   474  // ToReachMap looks through a PackageTree and computes the list of external
   475  // import statements (that is, import statements pointing to packages that are
   476  // not logical children of PackageTree.ImportRoot) that are transitively
   477  // imported by the internal packages in the tree.
   478  //
   479  // main indicates whether (true) or not (false) to include main packages in the
   480  // analysis. When utilized by gps' solver, main packages are generally excluded
   481  // from analyzing anything other than the root project, as they necessarily can't
   482  // be imported.
   483  //
   484  // tests indicates whether (true) or not (false) to include imports from test
   485  // files in packages when computing the reach map.
   486  //
   487  // backprop indicates whether errors (an actual PackageOrErr.Err, or an import
   488  // to a nonexistent internal package) should be backpropagated, transitively
   489  // "poisoning" all corresponding importers to all importers.
   490  //
   491  // ignore is a map of import paths that, if encountered, should be excluded from
   492  // analysis. This exclusion applies to both internal and external packages. If
   493  // an external import path is ignored, it is simply omitted from the results.
   494  //
   495  // If an internal path is ignored, then it not only does not appear in the final
   496  // map, but it is also excluded from the transitive calculations of other
   497  // internal packages.  That is, if you ignore A/foo, then the external package
   498  // list for all internal packages that import A/foo will not include external
   499  // packages that are only reachable through A/foo.
   500  //
   501  // Visually, this means that, given a PackageTree with root A and packages at A,
   502  // A/foo, and A/bar, and the following import chain:
   503  //
   504  //  A -> A/foo -> A/bar -> B/baz
   505  //
   506  // In this configuration, all of A's packages transitively import B/baz, so the
   507  // returned map would be:
   508  //
   509  //  map[string][]string{
   510  // 	"A": []string{"B/baz"},
   511  // 	"A/foo": []string{"B/baz"}
   512  // 	"A/bar": []string{"B/baz"},
   513  //  }
   514  //
   515  // However, if you ignore A/foo, then A's path to B/baz is broken, and A/foo is
   516  // omitted entirely. Thus, the returned map would be:
   517  //
   518  //  map[string][]string{
   519  // 	"A": []string{},
   520  // 	"A/bar": []string{"B/baz"},
   521  //  }
   522  //
   523  // If there are no packages to ignore, it is safe to pass a nil map.
   524  //
   525  // Finally, if an internal PackageOrErr contains an error, it is always omitted
   526  // from the result set. If backprop is true, then the error from that internal
   527  // package will be transitively propagated back to any other internal
   528  // PackageOrErrs that import it, causing them to also be omitted. So, with the
   529  // same import chain:
   530  //
   531  //  A -> A/foo -> A/bar -> B/baz
   532  //
   533  // If A/foo has an error, then it would backpropagate to A, causing both to be
   534  // omitted, and the returned map to contain only A/bar:
   535  //
   536  //  map[string][]string{
   537  // 	"A/bar": []string{"B/baz"},
   538  //  }
   539  //
   540  // If backprop is false, then errors will not backpropagate to internal
   541  // importers. So, with an error in A/foo, this would be the result map:
   542  //
   543  //  map[string][]string{
   544  // 	"A": []string{},
   545  // 	"A/bar": []string{"B/baz"},
   546  //  }
   547  func (t PackageTree) ToReachMap(main, tests, backprop bool, ignore *IgnoredRuleset) (ReachMap, map[string]*ProblemImportError) {
   548  	// world's simplest adjacency list
   549  	workmap := make(map[string]wm)
   550  
   551  	var imps []string
   552  	for ip, perr := range t.Packages {
   553  		if perr.Err != nil {
   554  			workmap[ip] = wm{
   555  				err: perr.Err,
   556  			}
   557  			continue
   558  		}
   559  		p := perr.P
   560  
   561  		// Skip main packages, unless param says otherwise
   562  		if p.Name == "main" && !main {
   563  			continue
   564  		}
   565  		// Skip ignored packages
   566  		if ignore.IsIgnored(ip) {
   567  			continue
   568  		}
   569  
   570  		// TODO (kris-nova) Disable to get staticcheck passing
   571  		//imps = imps[:0]
   572  
   573  		if tests {
   574  			imps = dedupeStrings(p.Imports, p.TestImports)
   575  		} else {
   576  			imps = p.Imports
   577  		}
   578  
   579  		w := wm{
   580  			ex: make(map[string]bool),
   581  			in: make(map[string]bool),
   582  		}
   583  
   584  		// For each import, decide whether it should be ignored, or if it
   585  		// belongs in the external or internal imports list.
   586  		for _, imp := range imps {
   587  			if ignore.IsIgnored(imp) || imp == "." {
   588  				continue
   589  			}
   590  
   591  			if !eqOrSlashedPrefix(imp, t.ImportRoot) {
   592  				w.ex[imp] = true
   593  			} else {
   594  				w.in[imp] = true
   595  			}
   596  		}
   597  
   598  		workmap[ip] = w
   599  	}
   600  
   601  	return wmToReach(workmap, backprop)
   602  }
   603  
   604  // Copy copies the PackageTree.
   605  //
   606  // This is really only useful as a defensive measure to prevent external state
   607  // mutations.
   608  func (t PackageTree) Copy() PackageTree {
   609  	return PackageTree{
   610  		ImportRoot: t.ImportRoot,
   611  		Packages:   CopyPackages(t.Packages, nil),
   612  	}
   613  }
   614  
   615  // CopyPackages returns a deep copy of p, optionally modifying the entries with fn.
   616  func CopyPackages(p map[string]PackageOrErr, fn func(string, PackageOrErr) (string, PackageOrErr)) map[string]PackageOrErr {
   617  	p2 := make(map[string]PackageOrErr, len(p))
   618  	// Walk through and count up the total number of string slice elements we'll
   619  	// need, then allocate them all at once.
   620  	strcount := 0
   621  	for _, poe := range p {
   622  		strcount = strcount + len(poe.P.Imports) + len(poe.P.TestImports)
   623  	}
   624  	pool := make([]string, strcount)
   625  
   626  	for path, poe := range p {
   627  		var poe2 PackageOrErr
   628  
   629  		if poe.Err != nil {
   630  			poe2.Err = poe.Err
   631  		} else {
   632  			poe2.P = poe.P
   633  			il, til := len(poe.P.Imports), len(poe.P.TestImports)
   634  			if il > 0 {
   635  				poe2.P.Imports, pool = pool[:il], pool[il:]
   636  				copy(poe2.P.Imports, poe.P.Imports)
   637  			}
   638  			if til > 0 {
   639  				poe2.P.TestImports, pool = pool[:til], pool[til:]
   640  				copy(poe2.P.TestImports, poe.P.TestImports)
   641  			}
   642  		}
   643  		if fn != nil {
   644  			path, poe2 = fn(path, poe2)
   645  		}
   646  		p2[path] = poe2
   647  	}
   648  
   649  	return p2
   650  }
   651  
   652  // TrimHiddenPackages returns a new PackageTree where packages that are ignored,
   653  // or both hidden and unreachable, have been removed.
   654  //
   655  // The package list is partitioned into two sets: visible, and hidden, where
   656  // packages are considered hidden if they are within or beneath directories
   657  // with:
   658  //
   659  //  * leading dots
   660  //  * leading underscores
   661  //  * the exact name "testdata"
   662  //
   663  // Packages in the hidden set are dropped from the returned PackageTree, unless
   664  // they are transitively reachable from imports in the visible set.
   665  //
   666  // The "main", "tests" and "ignored" parameters have the same behavior as with
   667  // PackageTree.ToReachMap(): the first two determine, respectively, whether
   668  // imports from main packages, and imports from tests, should be considered for
   669  // reachability checks. Setting 'main' to true will additionally result in main
   670  // packages being trimmed.
   671  //
   672  // "ignored" designates import paths, or patterns of import paths, where the
   673  // corresponding packages should be excluded from reachability checks, if
   674  // encountered. Ignored packages are also removed from the final set.
   675  //
   676  // Note that it is not recommended to call this method if the goal is to obtain
   677  // a set of tree-external imports; calling ToReachMap and FlattenFn will achieve
   678  // the same effect.
   679  func (t PackageTree) TrimHiddenPackages(main, tests bool, ignore *IgnoredRuleset) PackageTree {
   680  	rm, pie := t.ToReachMap(main, tests, false, ignore)
   681  	t2 := t.Copy()
   682  	preserve := make(map[string]bool)
   683  
   684  	for pkg, ie := range rm {
   685  		if pkgFilter(pkg) && !ignore.IsIgnored(pkg) {
   686  			preserve[pkg] = true
   687  			for _, in := range ie.Internal {
   688  				preserve[in] = true
   689  			}
   690  		}
   691  	}
   692  
   693  	// Also process the problem map, as packages in the visible set with errors
   694  	// need to be included in the return values.
   695  	for pkg := range pie {
   696  		if pkgFilter(pkg) && !ignore.IsIgnored(pkg) {
   697  			preserve[pkg] = true
   698  		}
   699  	}
   700  
   701  	for ip := range t.Packages {
   702  		if !preserve[ip] {
   703  			delete(t2.Packages, ip)
   704  		}
   705  	}
   706  
   707  	return t2
   708  }
   709  
   710  // wmToReach takes an internal "workmap" constructed by
   711  // PackageTree.ExternalReach(), transitively walks (via depth-first traversal)
   712  // all internal imports until they reach an external path or terminate, then
   713  // translates the results into a slice of external imports for each internal
   714  // pkg.
   715  //
   716  // It drops any packages with errors, and - if backprop is true - backpropagates
   717  // those errors, causing internal packages that (transitively) import other
   718  // internal packages having errors to also be dropped.
   719  func wmToReach(workmap map[string]wm, backprop bool) (ReachMap, map[string]*ProblemImportError) {
   720  	// Uses depth-first exploration to compute reachability into external
   721  	// packages, dropping any internal packages on "poisoned paths" - a path
   722  	// containing a package with an error, or with a dep on an internal package
   723  	// that's missing.
   724  
   725  	const (
   726  		white uint8 = iota
   727  		grey
   728  		black
   729  	)
   730  
   731  	colors := make(map[string]uint8)
   732  	exrsets := make(map[string]map[string]struct{})
   733  	inrsets := make(map[string]map[string]struct{})
   734  	errmap := make(map[string]*ProblemImportError)
   735  
   736  	// poison is a helper func to eliminate specific reachsets from exrsets and
   737  	// inrsets, and populate error information along the way.
   738  	poison := func(path []string, err *ProblemImportError) {
   739  		for k, ppkg := range path {
   740  			delete(exrsets, ppkg)
   741  			delete(inrsets, ppkg)
   742  
   743  			// Duplicate the err for this package
   744  			kerr := &ProblemImportError{
   745  				ImportPath: ppkg,
   746  				Err:        err.Err,
   747  			}
   748  
   749  			// Shift the slice bounds on the incoming err.Cause.
   750  			//
   751  			// This check will only be false on the final path element when
   752  			// entering via poisonWhite, where the last pkg is the underlying
   753  			// cause of the problem, and is thus expected to have an empty Cause
   754  			// slice.
   755  			if k+1 < len(err.Cause) {
   756  				// reuse the slice
   757  				kerr.Cause = err.Cause[k+1:]
   758  			}
   759  
   760  			// Both black and white cases can have the final element be a
   761  			// package that doesn't exist. If that's the case, don't write it
   762  			// directly to the errmap, as presence in the errmap indicates the
   763  			// package was present in the input PackageTree.
   764  			if k == len(path)-1 {
   765  				if _, exists := workmap[path[len(path)-1]]; !exists {
   766  					continue
   767  				}
   768  			}
   769  
   770  			// Direct writing to the errmap means that if multiple errors affect
   771  			// a given package, only the last error visited will be reported.
   772  			// But that should be sufficient; presumably, the user can
   773  			// iteratively resolve the errors.
   774  			errmap[ppkg] = kerr
   775  		}
   776  	}
   777  
   778  	// poisonWhite wraps poison for error recording in the white-poisoning case,
   779  	// where we're constructing a new poison path.
   780  	poisonWhite := func(path []string) {
   781  		err := &ProblemImportError{
   782  			Cause: make([]string, len(path)),
   783  		}
   784  		copy(err.Cause, path)
   785  
   786  		// find the tail err
   787  		tail := path[len(path)-1]
   788  		if w, exists := workmap[tail]; exists {
   789  			// If we make it to here, the dfe guarantees that the workmap
   790  			// will contain an error for this pkg.
   791  			err.Err = w.err
   792  		} else {
   793  			err.Err = missingPkgErr(tail)
   794  		}
   795  
   796  		poison(path, err)
   797  	}
   798  	// poisonBlack wraps poison for error recording in the black-poisoning case,
   799  	// where we're connecting to an existing poison path.
   800  	poisonBlack := func(path []string, from string) {
   801  		// Because the outer dfe loop ensures we never directly re-visit a pkg
   802  		// that was already completed (black), we don't have to defend against
   803  		// an empty path here.
   804  
   805  		fromErr, exists := errmap[from]
   806  		// FIXME: It should not be possible for fromErr to not exist,
   807  		// See issue https://github.com/golang/dep/issues/351
   808  		// This is a temporary solution to avoid a panic.
   809  		if !exists {
   810  			fromErr = &ProblemImportError{
   811  				Err: fmt.Errorf("unknown error for %q, if you get this error see https://github.com/golang/dep/issues/351", from),
   812  			}
   813  		}
   814  		err := &ProblemImportError{
   815  			Err:   fromErr.Err,
   816  			Cause: make([]string, 0, len(path)+len(fromErr.Cause)+1),
   817  		}
   818  		err.Cause = append(err.Cause, path...)
   819  		err.Cause = append(err.Cause, from)
   820  		err.Cause = append(err.Cause, fromErr.Cause...)
   821  
   822  		poison(path, err)
   823  	}
   824  
   825  	var dfe func(string, []string) bool
   826  
   827  	// dfe is the depth-first-explorer that computes a safe, error-free external
   828  	// reach map.
   829  	//
   830  	// pkg is the import path of the pkg currently being visited; path is the
   831  	// stack of parent packages we've visited to get to pkg. The return value
   832  	// indicates whether the level completed successfully (true) or if it was
   833  	// poisoned (false).
   834  	dfe = func(pkg string, path []string) bool {
   835  		// white is the zero value of uint8, which is what we want if the pkg
   836  		// isn't in the colors map, so this works fine
   837  		switch colors[pkg] {
   838  		case white:
   839  			// first visit to this pkg; mark it as in-process (grey)
   840  			colors[pkg] = grey
   841  
   842  			// make sure it's present and w/out errs
   843  			w, exists := workmap[pkg]
   844  
   845  			// Push current visitee onto the path slice. Passing path through
   846  			// recursion levels as a value has the effect of auto-popping the
   847  			// slice, while also giving us safe memory reuse.
   848  			path = append(path, pkg)
   849  
   850  			if !exists || w.err != nil {
   851  				if backprop {
   852  					// Does not exist or has an err; poison self and all parents
   853  					poisonWhite(path)
   854  				} else if exists {
   855  					// Only record something in the errmap if there's actually a
   856  					// package there, per the semantics of the errmap
   857  					errmap[pkg] = &ProblemImportError{
   858  						ImportPath: pkg,
   859  						Err:        w.err,
   860  					}
   861  				}
   862  
   863  				// we know we're done here, so mark it black
   864  				colors[pkg] = black
   865  				return false
   866  			}
   867  			// pkg exists with no errs; start internal and external reachsets for it.
   868  			rs := make(map[string]struct{})
   869  			irs := make(map[string]struct{})
   870  
   871  			// Dump this package's external pkgs into its own reachset. Separate
   872  			// loop from the parent dump to avoid nested map loop lookups.
   873  			for ex := range w.ex {
   874  				rs[ex] = struct{}{}
   875  			}
   876  			exrsets[pkg] = rs
   877  			// Same deal for internal imports
   878  			for in := range w.in {
   879  				irs[in] = struct{}{}
   880  			}
   881  			inrsets[pkg] = irs
   882  
   883  			// Push this pkg's imports into all parent reachsets. Not all
   884  			// parents will necessarily have a reachset; none, some, or all
   885  			// could have been poisoned by a different path than what we're on
   886  			// right now.
   887  			for _, ppkg := range path {
   888  				if prs, exists := exrsets[ppkg]; exists {
   889  					for ex := range w.ex {
   890  						prs[ex] = struct{}{}
   891  					}
   892  				}
   893  
   894  				if prs, exists := inrsets[ppkg]; exists {
   895  					for in := range w.in {
   896  						prs[in] = struct{}{}
   897  					}
   898  				}
   899  			}
   900  
   901  			// Now, recurse until done, or a false bubbles up, indicating the
   902  			// path is poisoned.
   903  			for in := range w.in {
   904  				clean := dfe(in, path)
   905  				if !clean && backprop {
   906  					// Path is poisoned. If we're backpropagating errors, then
   907  					// the  reachmap for the visitee was already deleted by the
   908  					// path we're returning from; mark the visitee black, then
   909  					// return false to bubble up the poison. This is OK to do
   910  					// early, before exploring all internal imports, because the
   911  					// outer loop visits all internal packages anyway.
   912  					//
   913  					// In fact, stopping early is preferable - white subpackages
   914  					// won't have to iterate pointlessly through a parent path
   915  					// with no reachset.
   916  					colors[pkg] = black
   917  					return false
   918  				}
   919  			}
   920  
   921  			// Fully done with this pkg; no transitive problems.
   922  			colors[pkg] = black
   923  			return true
   924  
   925  		case grey:
   926  			// Grey means an import cycle. These can arise in healthy situations
   927  			// through xtest. They can also arise in less healthy but valid
   928  			// situations where an edge in the import graph is reversed based on
   929  			// the presence of a build tag. For example, if A depends on B on
   930  			// Linux, but B depends on A on Darwin, the import graph is not
   931  			// cyclic on either Linux or Darwin but dep will see what appears to
   932  			// be a dependency cycle because it considers all tags at once.
   933  			//
   934  			// Handling import cycles for the purposes of reachablity is
   935  			// straightforward: we treat all packages in the cycle as
   936  			// equivalent. Any package imported by one package in the cycle is
   937  			// necessarily reachable by all other packages in the cycle.
   938  
   939  			// Merge the reachsets in the cycle by sharing the same external
   940  			// reachset and internal reachset amongst all packages in the
   941  			// cycle.
   942  			var cycleStarted bool
   943  			for _, ppkg := range path {
   944  				if cycleStarted {
   945  					exrsets[ppkg] = exrsets[pkg]
   946  					inrsets[ppkg] = inrsets[pkg]
   947  				} else if ppkg == pkg {
   948  					cycleStarted = true
   949  				}
   950  			}
   951  			if !cycleStarted {
   952  				panic(fmt.Sprintf("path to grey package %s did not include cycle: %s", pkg, path))
   953  			}
   954  			return true
   955  
   956  		case black:
   957  			// black means we're revisiting a package that was already
   958  			// completely explored. If it has an entry in exrsets, it completed
   959  			// successfully. If not, it was poisoned, and we need to bubble the
   960  			// poison back up.
   961  			rs, exists := exrsets[pkg]
   962  			if !exists {
   963  				if backprop {
   964  					// just poison parents; self was necessarily already poisoned
   965  					poisonBlack(path, pkg)
   966  				}
   967  				return false
   968  			}
   969  			// If external reachset existed, internal must (even if empty)
   970  			irs := inrsets[pkg]
   971  
   972  			// It's good; pull over the imports from its reachset into all
   973  			// non-poisoned parent reachsets
   974  			for _, ppkg := range path {
   975  				if prs, exists := exrsets[ppkg]; exists {
   976  					for ex := range rs {
   977  						prs[ex] = struct{}{}
   978  					}
   979  				}
   980  
   981  				if prs, exists := inrsets[ppkg]; exists {
   982  					for in := range irs {
   983  						prs[in] = struct{}{}
   984  					}
   985  				}
   986  			}
   987  			return true
   988  
   989  		default:
   990  			panic(fmt.Sprintf("invalid color marker %v for %s", colors[pkg], pkg))
   991  		}
   992  	}
   993  
   994  	// Run the depth-first exploration.
   995  	//
   996  	// Don't bother computing graph sources, this straightforward loop works
   997  	// comparably well, and fits nicely with an escape hatch in the dfe.
   998  	var path []string
   999  	for pkg := range workmap {
  1000  		// However, at least check that the package isn't already fully visited;
  1001  		// this saves a bit of time and implementation complexity inside the
  1002  		// closures.
  1003  		if colors[pkg] != black {
  1004  			dfe(pkg, path)
  1005  		}
  1006  	}
  1007  
  1008  	type ie struct {
  1009  		Internal, External []string
  1010  	}
  1011  
  1012  	// Flatten exrsets into reachmap
  1013  	rm := make(ReachMap)
  1014  	for pkg, rs := range exrsets {
  1015  		rlen := len(rs)
  1016  		if rlen == 0 {
  1017  			rm[pkg] = ie{}
  1018  			continue
  1019  		}
  1020  
  1021  		edeps := make([]string, 0, rlen)
  1022  		for opkg := range rs {
  1023  			edeps = append(edeps, opkg)
  1024  		}
  1025  
  1026  		sort.Strings(edeps)
  1027  
  1028  		sets := rm[pkg]
  1029  		sets.External = edeps
  1030  		rm[pkg] = sets
  1031  	}
  1032  
  1033  	// Flatten inrsets into reachmap
  1034  	for pkg, rs := range inrsets {
  1035  		rlen := len(rs)
  1036  		if rlen == 0 {
  1037  			continue
  1038  		}
  1039  
  1040  		ideps := make([]string, 0, rlen)
  1041  		for opkg := range rs {
  1042  			ideps = append(ideps, opkg)
  1043  		}
  1044  
  1045  		sort.Strings(ideps)
  1046  
  1047  		sets := rm[pkg]
  1048  		sets.Internal = ideps
  1049  		rm[pkg] = sets
  1050  	}
  1051  
  1052  	return rm, errmap
  1053  }
  1054  
  1055  // eqOrSlashedPrefix checks to see if the prefix is either equal to the string,
  1056  // or that it is a prefix and the next char in the string is "/".
  1057  func eqOrSlashedPrefix(s, prefix string) bool {
  1058  	if !strings.HasPrefix(s, prefix) {
  1059  		return false
  1060  	}
  1061  
  1062  	prflen, pathlen := len(prefix), len(s)
  1063  	return prflen == pathlen || strings.Index(s[prflen:], "/") == 0
  1064  }
  1065  
  1066  // helper func to merge, dedupe, and sort strings
  1067  func dedupeStrings(s1, s2 []string) (r []string) {
  1068  	dedupe := make(map[string]bool)
  1069  
  1070  	if len(s1) > 0 && len(s2) > 0 {
  1071  		for _, i := range s1 {
  1072  			dedupe[i] = true
  1073  		}
  1074  		for _, i := range s2 {
  1075  			dedupe[i] = true
  1076  		}
  1077  
  1078  		for i := range dedupe {
  1079  			r = append(r, i)
  1080  		}
  1081  		// And then re-sort them
  1082  		sort.Strings(r)
  1083  	} else if len(s1) > 0 {
  1084  		r = s1
  1085  	} else if len(s2) > 0 {
  1086  		r = s2
  1087  	}
  1088  
  1089  	return
  1090  }
  1091  
  1092  func uniq(a []string) []string {
  1093  	if a == nil {
  1094  		return make([]string, 0)
  1095  	}
  1096  	var s string
  1097  	var i int
  1098  	if !sort.StringsAreSorted(a) {
  1099  		sort.Strings(a)
  1100  	}
  1101  	for _, t := range a {
  1102  		if t != s {
  1103  			a[i] = t
  1104  			i++
  1105  			s = t
  1106  		}
  1107  	}
  1108  	return a[:i]
  1109  }