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 }