github.com/golang/dep@v0.5.4/cmd/dep/status.go (about) 1 // Copyright 2016 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 main 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/json" 11 "flag" 12 "fmt" 13 "io" 14 "io/ioutil" 15 "log" 16 "os" 17 "sort" 18 "strings" 19 "sync" 20 "text/tabwriter" 21 "text/template" 22 23 "github.com/golang/dep" 24 "github.com/golang/dep/gps" 25 "github.com/golang/dep/gps/paths" 26 "github.com/golang/dep/gps/verify" 27 "github.com/pkg/errors" 28 ) 29 30 const availableTemplateVariables = "ProjectRoot, Constraint, Version, Revision, Latest, and PackageCount." 31 const availableDefaultTemplateVariables = `.Projects[]{ 32 .ProjectRoot,.Source,.Constraint,.PackageCount,.Packages[], 33 .PruneOpts,.Digest,.Locked{.Branch,.Revision,.Version}, 34 .Latest{.Revision,.Version} 35 }, 36 .Metadata{ 37 .AnalyzerName,.AnalyzerVersion,.InputImports,.SolverName, 38 .SolverVersion 39 }` 40 41 const statusShortHelp = `Report the status of the project's dependencies` 42 const statusLongHelp = ` 43 With no arguments, print the status of each dependency of the project. 44 45 PROJECT Import path 46 CONSTRAINT Version constraint, from the manifest 47 VERSION Version chosen, from the lock 48 REVISION VCS revision of the chosen version 49 LATEST Latest VCS revision available 50 PKGS USED Number of packages from this project that are actually used 51 52 You may use the -f flag to create a custom format for the output of the 53 dep status command. The available fields you can utilize are as follows: 54 ` + availableTemplateVariables + ` 55 56 Status returns exit code zero if all dependencies are in a "good state". 57 ` 58 59 const statusExamples = ` 60 dep status 61 62 Displays a table of the various dependencies in the project along with 63 their properties such as the constraints they are bound by and the 64 revision they are at. 65 66 dep status -detail 67 68 Displays a detailed table of the dependencies in the project including 69 the value of any source rules used and full list of packages used from 70 each project (instead of simply a count). Text wrapping may make this 71 output hard to read. 72 73 dep status -f='{{if eq .Constraint "master"}}{{.ProjectRoot}} {{end}}' 74 75 Displays the list of package names constrained on the master branch. 76 The -f flag allows you to use Go templates along with it's various 77 constructs for formating output data. Available flags are as follows: 78 ` + availableTemplateVariables + ` 79 80 dep status -detail -f='{{range $i, $p := .Projects}}{{if ne .Source "" -}} 81 {{- if $i}},{{end}}{{$p.ProjectRoot}}:{{$p.Source}}{{end}}{{end}}' 82 83 Displays the package name and source for each package with a source 84 rule defined, with a comma between each name-source pair. 85 86 When used with -detail, the -f flag applies the supplied Go templates 87 to the full output document, instead of to packages one at a time. 88 Available flags are as follows: ` + availableDefaultTemplateVariables + ` 89 90 dep status -json 91 92 Displays the dependency information in JSON format as a list of 93 project objects. Each project object contains keys which correspond 94 to the table column names from the standard 'dep status' command. 95 96 Linux: dep status -dot | dot -T png | display 97 MacOS: dep status -dot | dot -T png | open -f -a /Applications/Preview.app 98 Windows: dep status -dot | dot -T png -o status.png; start status.png 99 100 Generates a visual representation of the dependency tree using GraphViz. 101 (Note: in order for this example to work you must first have graphviz 102 installed on your system) 103 104 ` 105 106 const ( 107 shortRev uint8 = iota 108 longRev 109 ) 110 111 var ( 112 errFailedUpdate = errors.New("failed to fetch updates") 113 errFailedListPkg = errors.New("failed to list packages") 114 errMultipleFailures = errors.New("multiple sources of failure") 115 errInputDigestMismatch = errors.New("input-digest mismatch") 116 ) 117 118 func (cmd *statusCommand) Name() string { return "status" } 119 func (cmd *statusCommand) Args() string { return "[package...]" } 120 func (cmd *statusCommand) ShortHelp() string { return statusShortHelp } 121 func (cmd *statusCommand) LongHelp() string { return statusLongHelp } 122 func (cmd *statusCommand) Hidden() bool { return false } 123 124 func (cmd *statusCommand) Register(fs *flag.FlagSet) { 125 fs.BoolVar(&cmd.examples, "examples", false, "print detailed usage examples") 126 fs.BoolVar(&cmd.json, "json", false, "output in JSON format") 127 fs.StringVar(&cmd.template, "f", "", "output in text/template format") 128 fs.BoolVar(&cmd.lock, "lock", false, "output in the lock file format (assumes -detail)") 129 fs.BoolVar(&cmd.dot, "dot", false, "output the dependency graph in GraphViz format") 130 fs.BoolVar(&cmd.old, "old", false, "only show out-of-date dependencies") 131 fs.BoolVar(&cmd.missing, "missing", false, "only show missing dependencies") 132 fs.StringVar(&cmd.outFilePath, "out", "", "path to a file to which to write the output. Blank value will be ignored") 133 fs.BoolVar(&cmd.detail, "detail", false, "include more detail in the chosen format") 134 } 135 136 type statusCommand struct { 137 examples bool 138 json bool 139 template string 140 lock bool 141 output string 142 dot bool 143 old bool 144 missing bool 145 outFilePath string 146 detail bool 147 } 148 149 type outputter interface { 150 BasicHeader() error 151 BasicLine(*BasicStatus) error 152 BasicFooter() error 153 DetailHeader(*dep.SolveMeta) error 154 DetailLine(*DetailStatus) error 155 DetailFooter(*dep.SolveMeta) error 156 MissingHeader() error 157 MissingLine(*MissingStatus) error 158 MissingFooter() error 159 } 160 161 // Only a subset of the outputters should be able to output old statuses. 162 type oldOutputter interface { 163 OldHeader() error 164 OldLine(*OldStatus) error 165 OldFooter() error 166 } 167 168 type tableOutput struct{ w *tabwriter.Writer } 169 170 func (out *tableOutput) BasicHeader() error { 171 _, err := fmt.Fprintf(out.w, "PROJECT\tCONSTRAINT\tVERSION\tREVISION\tLATEST\tPKGS USED\n") 172 return err 173 } 174 175 func (out *tableOutput) BasicFooter() error { 176 return out.w.Flush() 177 } 178 179 func (out *tableOutput) BasicLine(bs *BasicStatus) error { 180 _, err := fmt.Fprintf(out.w, 181 "%s\t%s\t%s\t%s\t%s\t%d\t\n", 182 bs.ProjectRoot, 183 bs.getConsolidatedConstraint(), 184 formatVersion(bs.Version), 185 formatVersion(bs.Revision), 186 bs.getConsolidatedLatest(shortRev), 187 bs.PackageCount, 188 ) 189 return err 190 } 191 192 func (out *tableOutput) DetailHeader(metadata *dep.SolveMeta) error { 193 _, err := fmt.Fprintf(out.w, "PROJECT\tSOURCE\tCONSTRAINT\tVERSION\tREVISION\tLATEST\tPKGS USED\n") 194 return err 195 } 196 197 func (out *tableOutput) DetailFooter(metadata *dep.SolveMeta) error { 198 return out.BasicFooter() 199 } 200 201 func (out *tableOutput) DetailLine(ds *DetailStatus) error { 202 _, err := fmt.Fprintf(out.w, 203 "%s\t%s\t%s\t%s\t%s\t%s\t[%s]\t\n", 204 ds.ProjectRoot, 205 ds.Source, 206 ds.getConsolidatedConstraint(), 207 formatVersion(ds.Version), 208 formatVersion(ds.Revision), 209 ds.getConsolidatedLatest(shortRev), 210 strings.Join(ds.Packages, ", "), 211 ) 212 return err 213 } 214 215 func (out *tableOutput) MissingHeader() error { 216 _, err := fmt.Fprintln(out.w, "PROJECT\tMISSING PACKAGES") 217 return err 218 } 219 220 func (out *tableOutput) MissingLine(ms *MissingStatus) error { 221 _, err := fmt.Fprintf(out.w, 222 "%s\t%s\t\n", 223 ms.ProjectRoot, 224 ms.MissingPackages, 225 ) 226 return err 227 } 228 229 func (out *tableOutput) MissingFooter() error { 230 return out.w.Flush() 231 } 232 233 func (out *tableOutput) OldHeader() error { 234 _, err := fmt.Fprintf(out.w, "PROJECT\tCONSTRAINT\tREVISION\tLATEST\n") 235 return err 236 } 237 238 func (out *tableOutput) OldLine(os *OldStatus) error { 239 _, err := fmt.Fprintf(out.w, 240 "%s\t%s\t%s\t%s\t\n", 241 os.ProjectRoot, 242 os.getConsolidatedConstraint(), 243 formatVersion(os.Revision), 244 os.getConsolidatedLatest(shortRev), 245 ) 246 return err 247 } 248 249 func (out *tableOutput) OldFooter() error { 250 return out.w.Flush() 251 } 252 253 type jsonOutput struct { 254 w io.Writer 255 basic []*rawStatus 256 detail []rawDetailProject 257 missing []*MissingStatus 258 old []*rawOldStatus 259 } 260 261 func (out *jsonOutput) BasicHeader() error { 262 out.basic = []*rawStatus{} 263 return nil 264 } 265 266 func (out *jsonOutput) BasicFooter() error { 267 return json.NewEncoder(out.w).Encode(out.basic) 268 } 269 270 func (out *jsonOutput) BasicLine(bs *BasicStatus) error { 271 out.basic = append(out.basic, bs.marshalJSON()) 272 return nil 273 } 274 275 func (out *jsonOutput) DetailHeader(metadata *dep.SolveMeta) error { 276 out.detail = []rawDetailProject{} 277 return nil 278 } 279 280 func (out *jsonOutput) DetailFooter(metadata *dep.SolveMeta) error { 281 doc := rawDetail{ 282 Projects: out.detail, 283 Metadata: newRawMetadata(metadata), 284 } 285 286 return json.NewEncoder(out.w).Encode(doc) 287 } 288 289 func (out *jsonOutput) DetailLine(ds *DetailStatus) error { 290 out.detail = append(out.detail, *ds.marshalJSON()) 291 return nil 292 } 293 294 func (out *jsonOutput) MissingHeader() error { 295 out.missing = []*MissingStatus{} 296 return nil 297 } 298 299 func (out *jsonOutput) MissingLine(ms *MissingStatus) error { 300 out.missing = append(out.missing, ms) 301 return nil 302 } 303 304 func (out *jsonOutput) MissingFooter() error { 305 return json.NewEncoder(out.w).Encode(out.missing) 306 } 307 308 func (out *jsonOutput) OldHeader() error { 309 out.old = []*rawOldStatus{} 310 return nil 311 } 312 313 func (out *jsonOutput) OldLine(os *OldStatus) error { 314 out.old = append(out.old, os.marshalJSON()) 315 return nil 316 } 317 318 func (out *jsonOutput) OldFooter() error { 319 return json.NewEncoder(out.w).Encode(out.old) 320 } 321 322 type dotOutput struct { 323 w io.Writer 324 o string 325 g *graphviz 326 p *dep.Project 327 } 328 329 func (out *dotOutput) BasicHeader() error { 330 out.g = new(graphviz).New() 331 332 ptree := out.p.RootPackageTree 333 // TODO(sdboyer) should be true, true, false, out.p.Manifest.IgnoredPackages() 334 prm, _ := ptree.ToReachMap(true, false, false, nil) 335 336 out.g.createNode(string(out.p.ImportRoot), "", prm.FlattenFn(paths.IsStandardImportPath)) 337 338 return nil 339 } 340 341 func (out *dotOutput) BasicFooter() error { 342 gvo := out.g.output("") 343 _, err := fmt.Fprint(out.w, gvo.String()) 344 return err 345 } 346 347 func (out *dotOutput) BasicLine(bs *BasicStatus) error { 348 out.g.createNode(bs.ProjectRoot, bs.getConsolidatedVersion(), bs.Children) 349 return nil 350 } 351 352 func (out *dotOutput) DetailHeader(metadata *dep.SolveMeta) error { 353 return out.BasicHeader() 354 } 355 356 func (out *dotOutput) DetailFooter(metadata *dep.SolveMeta) error { 357 return out.BasicFooter() 358 } 359 360 func (out *dotOutput) DetailLine(ds *DetailStatus) error { 361 return out.BasicLine(&ds.BasicStatus) 362 } 363 364 func (out *dotOutput) MissingHeader() error { return nil } 365 func (out *dotOutput) MissingLine(ms *MissingStatus) error { return nil } 366 func (out *dotOutput) MissingFooter() error { return nil } 367 368 type templateOutput struct { 369 w io.Writer 370 tmpl *template.Template 371 detail []rawDetailProject 372 } 373 374 func (out *templateOutput) BasicHeader() error { return nil } 375 func (out *templateOutput) BasicFooter() error { return nil } 376 func (out *templateOutput) BasicLine(bs *BasicStatus) error { 377 data := rawStatus{ 378 ProjectRoot: bs.ProjectRoot, 379 Constraint: bs.getConsolidatedConstraint(), 380 Version: bs.getConsolidatedVersion(), 381 Revision: bs.Revision.String(), 382 Latest: bs.getConsolidatedLatest(shortRev), 383 PackageCount: bs.PackageCount, 384 } 385 return out.tmpl.Execute(out.w, data) 386 } 387 388 func (out *templateOutput) DetailHeader(metadata *dep.SolveMeta) error { 389 out.detail = []rawDetailProject{} 390 391 return nil 392 } 393 394 func (out *templateOutput) DetailFooter(metadata *dep.SolveMeta) error { 395 raw := rawDetail{ 396 Projects: out.detail, 397 Metadata: newRawMetadata(metadata), 398 } 399 400 return out.tmpl.Execute(out.w, raw) 401 } 402 403 func (out *templateOutput) DetailLine(ds *DetailStatus) error { 404 data := rawDetailProject{ 405 ProjectRoot: ds.ProjectRoot, 406 Constraint: ds.getConsolidatedConstraint(), 407 Locked: formatDetailVersion(ds.Version, ds.Revision), 408 Latest: formatDetailLatestVersion(ds.Latest, ds.hasError), 409 PruneOpts: ds.getPruneOpts(), 410 Digest: ds.Digest.String(), 411 PackageCount: ds.PackageCount, 412 Source: ds.Source, 413 Packages: ds.Packages, 414 } 415 416 out.detail = append(out.detail, data) 417 418 return nil 419 } 420 421 func (out *templateOutput) OldHeader() error { return nil } 422 func (out *templateOutput) OldFooter() error { return nil } 423 func (out *templateOutput) OldLine(os *OldStatus) error { 424 return out.tmpl.Execute(out.w, os) 425 } 426 427 func (out *templateOutput) MissingHeader() error { return nil } 428 func (out *templateOutput) MissingFooter() error { return nil } 429 func (out *templateOutput) MissingLine(ms *MissingStatus) error { 430 return out.tmpl.Execute(out.w, ms) 431 } 432 433 func (cmd *statusCommand) Run(ctx *dep.Ctx, args []string) error { 434 if cmd.examples { 435 ctx.Err.Println(strings.TrimSpace(statusExamples)) 436 return nil 437 } 438 439 if err := cmd.validateFlags(); err != nil { 440 return err 441 } 442 443 p, err := ctx.LoadProject() 444 if err != nil { 445 return err 446 } 447 448 sm, err := ctx.SourceManager() 449 if err != nil { 450 return err 451 } 452 sm.UseDefaultSignalHandling() 453 defer sm.Release() 454 455 if err := dep.ValidateProjectRoots(ctx, p.Manifest, sm); err != nil { 456 return err 457 } 458 459 var buf bytes.Buffer 460 var out outputter 461 switch { 462 case cmd.missing: 463 return errors.Errorf("not implemented") 464 case cmd.json: 465 out = &jsonOutput{ 466 w: &buf, 467 } 468 case cmd.dot: 469 out = &dotOutput{ 470 p: p, 471 o: cmd.output, 472 w: &buf, 473 } 474 case cmd.template != "": 475 tmpl, err := parseStatusTemplate(cmd.template) 476 if err != nil { 477 return err 478 } 479 out = &templateOutput{ 480 w: &buf, 481 tmpl: tmpl, 482 } 483 case cmd.lock: 484 tmpl, err := parseStatusTemplate(statusLockTemplate) 485 if err != nil { 486 return err 487 } 488 out = &templateOutput{ 489 w: &buf, 490 tmpl: tmpl, 491 } 492 default: 493 out = &tableOutput{ 494 w: tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0), 495 } 496 } 497 498 // Check if the lock file exists. 499 if p.Lock == nil { 500 return errors.Errorf("no Gopkg.lock found. Run `dep ensure` to generate lock file") 501 } 502 503 if cmd.old { 504 if _, ok := out.(oldOutputter); !ok { 505 return errors.Errorf("invalid output format used") 506 } 507 err = cmd.runOld(ctx, out.(oldOutputter), p, sm) 508 ctx.Out.Print(buf.String()) 509 return err 510 } 511 512 _, errCount, runerr := cmd.runStatusAll(ctx, out, p, sm) 513 if runerr != nil { 514 switch runerr { 515 case errFailedUpdate: 516 // Print the help when in non-verbose mode 517 if !ctx.Verbose { 518 ctx.Out.Printf("The status of %d projects are unknown due to errors. Rerun with `-v` flag to see details.\n", errCount) 519 } 520 case errInputDigestMismatch: 521 ctx.Err.Printf("Gopkg.lock is out of sync with imports and/or Gopkg.toml. Run `dep check` for details.\n") 522 default: 523 return runerr 524 } 525 } 526 527 if cmd.outFilePath == "" { 528 // Print the status output 529 ctx.Out.Print(buf.String()) 530 } else { 531 file, err := os.Create(cmd.outFilePath) 532 if err != nil { 533 return fmt.Errorf("error creating output file: %v", err) 534 } 535 536 defer file.Close() 537 if _, err := io.Copy(file, bytes.NewReader(buf.Bytes())); err != nil { 538 return fmt.Errorf("error writing output file: %v", err) 539 } 540 } 541 542 return runerr 543 } 544 545 func (cmd *statusCommand) validateFlags() error { 546 // Operating mode flags. 547 var opModes []string 548 549 if cmd.old { 550 opModes = append(opModes, "-old") 551 } 552 553 if cmd.missing { 554 opModes = append(opModes, "-missing") 555 } 556 557 if cmd.detail { 558 opModes = append(opModes, "-detail") 559 } 560 561 // Check if any other flags are passed with -dot. 562 if cmd.dot { 563 if cmd.template != "" { 564 return errors.New("cannot pass template string with -dot") 565 } 566 567 if cmd.json { 568 return errors.New("cannot pass multiple output format flags") 569 } 570 571 if len(opModes) > 0 { 572 return errors.New("-dot generates dependency graph; cannot pass other flags") 573 } 574 } 575 576 if cmd.lock { 577 if cmd.template != "" { 578 return errors.New("cannot pass template string with -lock") 579 } 580 581 if !cmd.detail { 582 cmd.detail = true 583 } 584 } 585 586 if len(opModes) > 1 { 587 // List the flags because which flags are for operation mode might not 588 // be apparent to the users. 589 return errors.Wrapf(errors.New("cannot pass multiple operating mode flags"), "%v", opModes) 590 } 591 592 return nil 593 } 594 595 // OldStatus contains information about all the out of date packages in a project. 596 type OldStatus struct { 597 ProjectRoot string 598 Constraint gps.Constraint 599 Revision gps.Revision 600 Latest gps.Version 601 } 602 603 type rawOldStatus struct { 604 ProjectRoot, Constraint, Revision, Latest string 605 } 606 607 func (os OldStatus) getConsolidatedConstraint() string { 608 var constraint string 609 if os.Constraint != nil { 610 if v, ok := os.Constraint.(gps.Version); ok { 611 constraint = formatVersion(v) 612 } else { 613 constraint = os.Constraint.String() 614 } 615 } 616 return constraint 617 } 618 619 func (os OldStatus) getConsolidatedLatest(revSize uint8) string { 620 latest := "" 621 if os.Latest != nil { 622 switch revSize { 623 case shortRev: 624 latest = formatVersion(os.Latest) 625 case longRev: 626 latest = os.Latest.String() 627 } 628 } 629 return latest 630 } 631 632 func (os OldStatus) marshalJSON() *rawOldStatus { 633 return &rawOldStatus{ 634 ProjectRoot: os.ProjectRoot, 635 Constraint: os.getConsolidatedConstraint(), 636 Revision: string(os.Revision), 637 Latest: os.getConsolidatedLatest(longRev), 638 } 639 } 640 641 func (cmd *statusCommand) runOld(ctx *dep.Ctx, out oldOutputter, p *dep.Project, sm gps.SourceManager) error { 642 // While the network churns on ListVersions() requests, statically analyze 643 // code from the current project. 644 ptree := p.RootPackageTree 645 646 // Set up a solver in order to check the InputHash. 647 params := gps.SolveParameters{ 648 ProjectAnalyzer: dep.Analyzer{}, 649 RootDir: p.AbsRoot, 650 RootPackageTree: ptree, 651 Manifest: p.Manifest, 652 // Locks aren't a part of the input hash check, so we can omit it. 653 } 654 655 logger := ctx.Err 656 if ctx.Verbose { 657 params.TraceLogger = ctx.Err 658 } else { 659 logger = log.New(ioutil.Discard, "", 0) 660 } 661 662 // Check update for all the projects. 663 params.ChangeAll = true 664 665 solver, err := gps.Prepare(params, sm) 666 if err != nil { 667 return errors.Wrap(err, "fastpath solver prepare") 668 } 669 670 logger.Println("Solving dependency graph to determine which dependencies can be updated.") 671 solution, err := solver.Solve(context.TODO()) 672 if err != nil { 673 return errors.Wrap(err, "runOld") 674 } 675 676 var oldStatuses []OldStatus 677 solutionProjects := solution.Projects() 678 679 for _, proj := range p.Lock.Projects() { 680 for _, sProj := range solutionProjects { 681 // Look for the same project in solution and lock. 682 if sProj.Ident().ProjectRoot != proj.Ident().ProjectRoot { 683 continue 684 } 685 686 // If revisions are not the same then it is old and we should display it. 687 latestRev, _, _ := gps.VersionComponentStrings(sProj.Version()) 688 atRev, _, _ := gps.VersionComponentStrings(proj.Version()) 689 if atRev == latestRev { 690 continue 691 } 692 693 var constraint gps.Constraint 694 // Getting Constraint. 695 if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { 696 // manifest has override for project. 697 constraint = pp.Constraint 698 } else if pp, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { 699 // manifest has normal constraint. 700 constraint = pp.Constraint 701 } else { 702 // No constraint exists. No need to worry about displaying it. 703 continue 704 } 705 706 // Generate the old status data and append it. 707 os := OldStatus{ 708 ProjectRoot: proj.Ident().String(), 709 Revision: gps.Revision(atRev), 710 Latest: gps.Revision(latestRev), 711 Constraint: constraint, 712 } 713 oldStatuses = append(oldStatuses, os) 714 } 715 } 716 717 out.OldHeader() 718 for _, ostat := range oldStatuses { 719 out.OldLine(&ostat) 720 } 721 out.OldFooter() 722 723 return nil 724 } 725 726 type rawStatus struct { 727 ProjectRoot string 728 Constraint string 729 Version string 730 Revision string 731 Latest string 732 PackageCount int 733 } 734 735 // rawDetail is is additional information used for the status when the 736 // -detail flag is specified 737 type rawDetail struct { 738 Projects []rawDetailProject 739 Metadata rawDetailMetadata 740 } 741 742 type rawDetailVersion struct { 743 Revision string `json:"Revision,omitempty"` 744 Version string `json:"Version,omitempty"` 745 Branch string `json:"Branch,omitempty"` 746 } 747 748 type rawDetailProject struct { 749 ProjectRoot string 750 Packages []string 751 Locked rawDetailVersion 752 Latest rawDetailVersion 753 PruneOpts string 754 Digest string 755 Source string `json:"Source,omitempty"` 756 Constraint string 757 PackageCount int 758 } 759 760 type rawDetailMetadata struct { 761 AnalyzerName string 762 AnalyzerVersion int 763 InputsDigest string // deprecated 764 InputImports []string 765 SolverName string 766 SolverVersion int 767 } 768 769 func newRawMetadata(metadata *dep.SolveMeta) rawDetailMetadata { 770 if metadata == nil { 771 return rawDetailMetadata{} 772 } 773 774 return rawDetailMetadata{ 775 AnalyzerName: metadata.AnalyzerName, 776 AnalyzerVersion: metadata.AnalyzerVersion, 777 InputImports: metadata.InputImports, 778 SolverName: metadata.SolverName, 779 SolverVersion: metadata.SolverVersion, 780 } 781 } 782 783 // BasicStatus contains all the information reported about a single dependency 784 // in the summary/list status output mode. 785 type BasicStatus struct { 786 ProjectRoot string 787 Children []string 788 Constraint gps.Constraint 789 Version gps.UnpairedVersion 790 Revision gps.Revision 791 Latest gps.Version 792 PackageCount int 793 hasOverride bool 794 hasError bool 795 } 796 797 // DetailStatus contains all information reported about a single dependency 798 // in the detailed status output mode. The included information matches the 799 // information included about a a project in a lock file. 800 type DetailStatus struct { 801 BasicStatus 802 Packages []string 803 Source string 804 PruneOpts gps.PruneOptions 805 Digest verify.VersionedDigest 806 } 807 808 func (bs *BasicStatus) getConsolidatedConstraint() string { 809 var constraint string 810 if bs.Constraint != nil { 811 if v, ok := bs.Constraint.(gps.Version); ok { 812 constraint = formatVersion(v) 813 } else { 814 constraint = bs.Constraint.String() 815 } 816 } 817 818 if bs.hasOverride { 819 constraint += " (override)" 820 } 821 822 return constraint 823 } 824 825 func (bs *BasicStatus) getConsolidatedVersion() string { 826 version := formatVersion(bs.Revision) 827 if bs.Version != nil { 828 version = formatVersion(bs.Version) 829 } 830 return version 831 } 832 833 func (bs *BasicStatus) getConsolidatedLatest(revSize uint8) string { 834 latest := "" 835 if bs.Latest != nil { 836 switch revSize { 837 case shortRev: 838 latest = formatVersion(bs.Latest) 839 case longRev: 840 latest = bs.Latest.String() 841 } 842 } 843 844 if bs.hasError { 845 latest += "unknown" 846 } 847 848 return latest 849 } 850 851 func (ds *DetailStatus) getPruneOpts() string { 852 return (ds.PruneOpts & ^gps.PruneNestedVendorDirs).String() 853 } 854 855 func (bs *BasicStatus) marshalJSON() *rawStatus { 856 return &rawStatus{ 857 ProjectRoot: bs.ProjectRoot, 858 Constraint: bs.getConsolidatedConstraint(), 859 Version: formatVersion(bs.Version), 860 Revision: string(bs.Revision), 861 Latest: bs.getConsolidatedLatest(longRev), 862 PackageCount: bs.PackageCount, 863 } 864 } 865 866 func (ds *DetailStatus) marshalJSON() *rawDetailProject { 867 rawStatus := ds.BasicStatus.marshalJSON() 868 869 return &rawDetailProject{ 870 ProjectRoot: rawStatus.ProjectRoot, 871 Constraint: rawStatus.Constraint, 872 Locked: formatDetailVersion(ds.Version, ds.Revision), 873 Latest: formatDetailLatestVersion(ds.Latest, ds.hasError), 874 PruneOpts: ds.getPruneOpts(), 875 Digest: ds.Digest.String(), 876 Source: ds.Source, 877 Packages: ds.Packages, 878 PackageCount: ds.PackageCount, 879 } 880 } 881 882 // MissingStatus contains information about all the missing packages in a project. 883 type MissingStatus struct { 884 ProjectRoot string 885 MissingPackages []string 886 } 887 888 func (cmd *statusCommand) runStatusAll(ctx *dep.Ctx, out outputter, p *dep.Project, sm gps.SourceManager) (hasMissingPkgs bool, errCount int, err error) { 889 // While the network churns on ListVersions() requests, statically analyze 890 // code from the current project. 891 ptree := p.RootPackageTree 892 893 // Set up a solver in order to check the InputHash. 894 params := gps.SolveParameters{ 895 ProjectAnalyzer: dep.Analyzer{}, 896 RootDir: p.AbsRoot, 897 RootPackageTree: ptree, 898 Manifest: p.Manifest, 899 // Locks aren't a part of the input hash check, so we can omit it. 900 } 901 902 logger := ctx.Err 903 if ctx.Verbose { 904 params.TraceLogger = ctx.Err 905 } else { 906 logger = log.New(ioutil.Discard, "", 0) 907 } 908 909 if err := ctx.ValidateParams(sm, params); err != nil { 910 return false, 0, err 911 } 912 913 // Errors while collecting constraints should not fail the whole status run. 914 // It should count the error and tell the user about incomplete results. 915 cm, ccerrs := collectConstraints(ctx, p, sm) 916 if len(ccerrs) > 0 { 917 errCount += len(ccerrs) 918 } 919 920 // Get the project list and sort it so that the printed output users see is 921 // deterministically ordered. (This may be superfluous if the lock is always 922 // written in alpha order, but it doesn't hurt to double down.) 923 slp := p.Lock.Projects() 924 sort.Slice(slp, func(i, j int) bool { 925 return slp[i].Ident().Less(slp[j].Ident()) 926 }) 927 slcp := p.ChangedLock.Projects() 928 sort.Slice(slcp, func(i, j int) bool { 929 return slcp[i].Ident().Less(slcp[j].Ident()) 930 }) 931 932 lsat := verify.LockSatisfiesInputs(p.Lock, p.Manifest, params.RootPackageTree) 933 if lsat.Satisfied() { 934 // If the lock satisfies the inputs, we're guaranteed (barring manual 935 // meddling, about which we can do nothing) that the lock is a 936 // transitively complete picture of all deps. That eliminates the need 937 // for some checks. 938 939 logger.Println("Checking upstream projects:") 940 941 // DetailStatus channel to collect all the DetailStatus. 942 dsCh := make(chan *DetailStatus, len(slp)) 943 944 // Error channels to collect different errors. 945 errListPkgCh := make(chan error, len(slp)) 946 errListVerCh := make(chan error, len(slp)) 947 948 var wg sync.WaitGroup 949 950 for i, proj := range slp { 951 wg.Add(1) 952 logger.Printf("(%d/%d) %s\n", i+1, len(slp), proj.Ident().ProjectRoot) 953 954 go func(proj verify.VerifiableProject) { 955 bs := BasicStatus{ 956 ProjectRoot: string(proj.Ident().ProjectRoot), 957 PackageCount: len(proj.Packages()), 958 } 959 960 // Get children only for specific outputers 961 // in order to avoid slower status process. 962 switch out.(type) { 963 case *dotOutput: 964 ptr, err := sm.ListPackages(proj.Ident(), proj.Version()) 965 966 if err != nil { 967 bs.hasError = true 968 errListPkgCh <- err 969 } 970 971 prm, _ := ptr.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) 972 bs.Children = prm.FlattenFn(paths.IsStandardImportPath) 973 } 974 975 // Split apart the version from the lock into its constituent parts. 976 switch tv := proj.Version().(type) { 977 case gps.UnpairedVersion: 978 bs.Version = tv 979 case gps.Revision: 980 bs.Revision = tv 981 case gps.PairedVersion: 982 bs.Version = tv.Unpair() 983 bs.Revision = tv.Revision() 984 } 985 986 // Check if the manifest has an override for this project. If so, 987 // set that as the constraint. 988 if pp, has := p.Manifest.Ovr[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { 989 bs.hasOverride = true 990 bs.Constraint = pp.Constraint 991 } else if pp, has := p.Manifest.Constraints[proj.Ident().ProjectRoot]; has && pp.Constraint != nil { 992 // If the manifest has a constraint then set that as the constraint. 993 bs.Constraint = pp.Constraint 994 } else { 995 bs.Constraint = gps.Any() 996 for _, c := range cm[bs.ProjectRoot] { 997 bs.Constraint = c.Constraint.Intersect(bs.Constraint) 998 } 999 } 1000 1001 // Only if we have a non-rev and non-plain version do/can we display 1002 // anything wrt the version's updateability. 1003 if bs.Version != nil && bs.Version.Type() != gps.IsVersion { 1004 c, has := p.Manifest.Constraints[proj.Ident().ProjectRoot] 1005 if !has { 1006 // Get constraint for locked project 1007 for _, lockedP := range p.Lock.P { 1008 if lockedP.Ident().ProjectRoot == proj.Ident().ProjectRoot { 1009 // Use the unpaired version as the constraint for checking updates. 1010 c.Constraint = bs.Version 1011 } 1012 } 1013 } 1014 // TODO: This constraint is only the constraint imposed by the 1015 // current project, not by any transitive deps. As a result, 1016 // transitive project deps will always show "any" here. 1017 bs.Constraint = c.Constraint 1018 1019 vl, err := sm.ListVersions(proj.Ident()) 1020 if err == nil { 1021 gps.SortPairedForUpgrade(vl) 1022 1023 for _, v := range vl { 1024 // Because we've sorted the version list for 1025 // upgrade, the first version we encounter that 1026 // matches our constraint will be what we want. 1027 if c.Constraint.Matches(v) { 1028 // Latest should be of the same type as the Version. 1029 if bs.Version.Type() == gps.IsSemver { 1030 bs.Latest = v 1031 } else { 1032 bs.Latest = v.Revision() 1033 } 1034 break 1035 } 1036 } 1037 } else { 1038 // Failed to fetch version list (could happen due to 1039 // network issue). 1040 bs.hasError = true 1041 errListVerCh <- err 1042 } 1043 } 1044 1045 ds := DetailStatus{ 1046 BasicStatus: bs, 1047 } 1048 1049 if cmd.detail { 1050 ds.Source = proj.Ident().Source 1051 ds.Packages = proj.Packages() 1052 ds.PruneOpts = proj.PruneOpts 1053 ds.Digest = proj.Digest 1054 } 1055 1056 dsCh <- &ds 1057 1058 wg.Done() 1059 }(proj.(verify.VerifiableProject)) 1060 } 1061 1062 wg.Wait() 1063 close(dsCh) 1064 close(errListPkgCh) 1065 close(errListVerCh) 1066 1067 // Newline after printing the status progress output. 1068 logger.Println() 1069 1070 // List Packages errors. This would happen only for dot output. 1071 if len(errListPkgCh) > 0 { 1072 err = errFailedListPkg 1073 if ctx.Verbose { 1074 for err := range errListPkgCh { 1075 ctx.Err.Println(err.Error()) 1076 } 1077 ctx.Err.Println() 1078 } 1079 } 1080 1081 // List Version errors. 1082 if len(errListVerCh) > 0 { 1083 if err == nil { 1084 err = errFailedUpdate 1085 } else { 1086 err = errMultipleFailures 1087 } 1088 1089 // Count ListVersions error because we get partial results when 1090 // this happens. 1091 errCount += len(errListVerCh) 1092 if ctx.Verbose { 1093 for err := range errListVerCh { 1094 ctx.Err.Println(err.Error()) 1095 } 1096 ctx.Err.Println() 1097 } 1098 } 1099 1100 if cmd.detail { 1101 // A map of ProjectRoot and *DetailStatus. This is used in maintain the 1102 // order of DetailStatus in output by collecting all the DetailStatus and 1103 // then using them in order. 1104 dsMap := make(map[string]*DetailStatus) 1105 for ds := range dsCh { 1106 dsMap[ds.ProjectRoot] = ds 1107 } 1108 1109 if err := detailOutputAll(out, slp, dsMap, &p.Lock.SolveMeta); err != nil { 1110 return false, 0, err 1111 } 1112 } else { 1113 // A map of ProjectRoot and *BasicStatus. This is used in maintain the 1114 // order of BasicStatus in output by collecting all the BasicStatus and 1115 // then using them in order. 1116 bsMap := make(map[string]*BasicStatus) 1117 for bs := range dsCh { 1118 bsMap[bs.ProjectRoot] = &bs.BasicStatus 1119 } 1120 1121 if err := basicOutputAll(out, slp, bsMap); err != nil { 1122 return false, 0, err 1123 } 1124 } 1125 1126 return false, errCount, err 1127 } 1128 1129 rm, _ := ptree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) 1130 1131 external := rm.FlattenFn(paths.IsStandardImportPath) 1132 roots := make(map[gps.ProjectRoot][]string, len(external)) 1133 1134 type fail struct { 1135 ex string 1136 err error 1137 } 1138 var errs []fail 1139 for _, e := range external { 1140 root, err := sm.DeduceProjectRoot(e) 1141 if err != nil { 1142 errs = append(errs, fail{ 1143 ex: e, 1144 err: err, 1145 }) 1146 continue 1147 } 1148 1149 roots[root] = append(roots[root], e) 1150 } 1151 1152 if len(errs) != 0 { 1153 // TODO this is just a fix quick so staticcheck doesn't complain. 1154 // Visually reconciling failure to deduce project roots with the rest of 1155 // the mismatch output is a larger problem. 1156 ctx.Err.Printf("Failed to deduce project roots for import paths:\n") 1157 for _, fail := range errs { 1158 ctx.Err.Printf("\t%s: %s\n", fail.ex, fail.err.Error()) 1159 } 1160 1161 return false, 0, errors.New("address issues with undeducible import paths to get more status information") 1162 } 1163 1164 if err = out.MissingHeader(); err != nil { 1165 return false, 0, err 1166 } 1167 1168 outer: 1169 for root, pkgs := range roots { 1170 // TODO also handle the case where the project is present, but there 1171 // are items missing from just the package list 1172 for _, lp := range slp { 1173 if lp.Ident().ProjectRoot == root { 1174 continue outer 1175 } 1176 } 1177 1178 hasMissingPkgs = true 1179 err := out.MissingLine(&MissingStatus{ProjectRoot: string(root), MissingPackages: pkgs}) 1180 if err != nil { 1181 return false, 0, err 1182 } 1183 } 1184 if err = out.MissingFooter(); err != nil { 1185 return false, 0, err 1186 } 1187 1188 // We are here because of an input-digest mismatch. Return error. 1189 return hasMissingPkgs, 0, errInputDigestMismatch 1190 } 1191 1192 // basicOutputAll takes an outputter, a project list, and a map of ProjectRoot to *BasicStatus and 1193 // uses the outputter to output basic header, body lines (in the order of the project list), and 1194 // footer based on the project information. 1195 func basicOutputAll(out outputter, slp []gps.LockedProject, bsMap map[string]*BasicStatus) (err error) { 1196 if err := out.BasicHeader(); err != nil { 1197 return err 1198 } 1199 1200 // Use the collected BasicStatus in outputter. 1201 for _, proj := range slp { 1202 if err := out.BasicLine(bsMap[string(proj.Ident().ProjectRoot)]); err != nil { 1203 return err 1204 } 1205 } 1206 1207 return out.BasicFooter() 1208 } 1209 1210 // detailOutputAll takes an outputter, a project list, and a map of ProjectRoot to *DetailStatus and 1211 // uses the outputter to output detailed header, body lines (in the order of the project list), and 1212 // footer based on the project information. 1213 func detailOutputAll(out outputter, slp []gps.LockedProject, dsMap map[string]*DetailStatus, metadata *dep.SolveMeta) (err error) { 1214 if err := out.DetailHeader(metadata); err != nil { 1215 return err 1216 } 1217 1218 // Use the collected BasicStatus in outputter. 1219 for _, proj := range slp { 1220 if err := out.DetailLine(dsMap[string(proj.Ident().ProjectRoot)]); err != nil { 1221 return err 1222 } 1223 } 1224 1225 return out.DetailFooter(metadata) 1226 } 1227 1228 func formatVersion(v gps.Version) string { 1229 if v == nil { 1230 return "" 1231 } 1232 switch v.Type() { 1233 case gps.IsBranch: 1234 return "branch " + v.String() 1235 case gps.IsRevision: 1236 r := v.String() 1237 if len(r) > 7 { 1238 r = r[:7] 1239 } 1240 return r 1241 } 1242 return v.String() 1243 } 1244 1245 func formatDetailVersion(v gps.Version, r gps.Revision) rawDetailVersion { 1246 if v == nil { 1247 return rawDetailVersion{ 1248 Revision: r.String(), 1249 } 1250 } 1251 switch v.Type() { 1252 case gps.IsBranch: 1253 return rawDetailVersion{ 1254 Branch: v.String(), 1255 Revision: r.String(), 1256 } 1257 case gps.IsRevision: 1258 return rawDetailVersion{ 1259 Revision: v.String(), 1260 } 1261 } 1262 1263 return rawDetailVersion{ 1264 Version: v.String(), 1265 Revision: r.String(), 1266 } 1267 } 1268 1269 func formatDetailLatestVersion(v gps.Version, hasError bool) rawDetailVersion { 1270 if hasError { 1271 return rawDetailVersion{ 1272 Revision: "unknown", 1273 } 1274 } 1275 1276 return formatDetailVersion(v, "") 1277 } 1278 1279 // projectConstraint stores ProjectRoot and Constraint for that project. 1280 type projectConstraint struct { 1281 Project gps.ProjectRoot 1282 Constraint gps.Constraint 1283 } 1284 1285 // constraintsCollection is a map of ProjectRoot(dependency) and a collection of 1286 // projectConstraint for the dependencies. This can be used to find constraints 1287 // on a dependency and the projects that apply those constraints. 1288 type constraintsCollection map[string][]projectConstraint 1289 1290 // collectConstraints collects constraints declared by all the dependencies and 1291 // constraints from the root project. It returns constraintsCollection and 1292 // a slice of errors encountered while collecting the constraints, if any. 1293 func collectConstraints(ctx *dep.Ctx, p *dep.Project, sm gps.SourceManager) (constraintsCollection, []error) { 1294 logger := ctx.Err 1295 if !ctx.Verbose { 1296 logger = log.New(ioutil.Discard, "", 0) 1297 } 1298 1299 logger.Println("Collecting project constraints:") 1300 1301 var mutex sync.Mutex 1302 constraintCollection := make(constraintsCollection) 1303 1304 // Collect the complete set of direct project dependencies, incorporating 1305 // requireds and ignores appropriately. 1306 directDeps, err := p.GetDirectDependencyNames(sm) 1307 if err != nil { 1308 // Return empty collection, not nil, if we fail here. 1309 return constraintCollection, []error{errors.Wrap(err, "failed to get direct dependencies")} 1310 } 1311 1312 // Create a root analyzer. 1313 rootAnalyzer := newRootAnalyzer(true, ctx, directDeps, sm) 1314 1315 lp := p.Lock.Projects() 1316 1317 // Channel for receiving all the errors. 1318 errCh := make(chan error, len(lp)) 1319 1320 var wg sync.WaitGroup 1321 1322 // Iterate through the locked projects and collect constraints of all the projects. 1323 for i, proj := range lp { 1324 wg.Add(1) 1325 logger.Printf("(%d/%d) %s\n", i+1, len(lp), proj.Ident().ProjectRoot) 1326 1327 go func(proj gps.LockedProject) { 1328 defer wg.Done() 1329 1330 manifest, _, err := sm.GetManifestAndLock(proj.Ident(), proj.Version(), rootAnalyzer) 1331 if err != nil { 1332 errCh <- errors.Wrap(err, "error getting manifest and lock") 1333 return 1334 } 1335 1336 // Get project constraints. 1337 pc := manifest.DependencyConstraints() 1338 1339 // Obtain a lock for constraintCollection. 1340 mutex.Lock() 1341 defer mutex.Unlock() 1342 // Iterate through the project constraints to get individual dependency 1343 // project and constraint values. 1344 for pr, pp := range pc { 1345 // Check if the project constraint is imported in the root project 1346 if _, ok := directDeps[pr]; !ok { 1347 continue 1348 } 1349 1350 tempCC := append( 1351 constraintCollection[string(pr)], 1352 projectConstraint{proj.Ident().ProjectRoot, pp.Constraint}, 1353 ) 1354 1355 // Sort the inner projectConstraint slice by Project string. 1356 // Required for consistent returned value. 1357 sort.Sort(byProject(tempCC)) 1358 constraintCollection[string(pr)] = tempCC 1359 } 1360 }(proj) 1361 } 1362 1363 wg.Wait() 1364 close(errCh) 1365 1366 var errs []error 1367 if len(errCh) > 0 { 1368 for e := range errCh { 1369 errs = append(errs, e) 1370 logger.Println(e.Error()) 1371 } 1372 } 1373 1374 // Incorporate constraints set in the manifest of the root project. 1375 if p.Manifest != nil { 1376 1377 // Iterate through constraints in the manifest, append if it is a 1378 // direct dependency 1379 for pr, pp := range p.Manifest.Constraints { 1380 if _, ok := directDeps[pr]; !ok { 1381 continue 1382 } 1383 1384 // Mark constraints coming from the manifest as "root" 1385 tempCC := append( 1386 constraintCollection[string(pr)], 1387 projectConstraint{"root", pp.Constraint}, 1388 ) 1389 1390 // Sort the inner projectConstraint slice by Project string. 1391 // Required for consistent returned value. 1392 sort.Sort(byProject(tempCC)) 1393 constraintCollection[string(pr)] = tempCC 1394 } 1395 } 1396 1397 return constraintCollection, errs 1398 } 1399 1400 type byProject []projectConstraint 1401 1402 func (p byProject) Len() int { return len(p) } 1403 func (p byProject) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 1404 func (p byProject) Less(i, j int) bool { return p[i].Project < p[j].Project } 1405 1406 func parseStatusTemplate(format string) (*template.Template, error) { 1407 tmpl, err := template.New("status").Funcs(template.FuncMap{ 1408 "dec": func(i int) int { 1409 return i - 1 1410 }, 1411 "tomlStrSplit": tomlStrSplit, 1412 "tomlStrSplit2": func(strlist []string, level int) string { 1413 // Hardcode to two spaces. 1414 inbracket, inp := strings.Repeat(" ", level), strings.Repeat(" ", level+1) 1415 switch len(strlist) { 1416 case 0: 1417 return "[]" 1418 case 1: 1419 return fmt.Sprintf("[\"%s\"]", strlist[0]) 1420 default: 1421 var buf bytes.Buffer 1422 1423 fmt.Fprintf(&buf, "[\n") 1424 for _, str := range strlist { 1425 fmt.Fprintf(&buf, "%s\"%s\",\n", inp, str) 1426 } 1427 fmt.Fprintf(&buf, "%s]", inbracket) 1428 1429 return buf.String() 1430 } 1431 }, 1432 }).Parse(format) 1433 1434 return tmpl, err 1435 } 1436 1437 func tomlStrSplit(strlist []string) string { 1438 switch len(strlist) { 1439 case 0: 1440 return "[]" 1441 case 1: 1442 return fmt.Sprintf("[\"%s\"]", strlist[0]) 1443 default: 1444 var buf bytes.Buffer 1445 1446 // Hardcode to two spaces. 1447 fmt.Fprintf(&buf, "[\n") 1448 for _, str := range strlist { 1449 fmt.Fprintf(&buf, " \"%s\",\n", str) 1450 } 1451 fmt.Fprintf(&buf, " ]") 1452 1453 return buf.String() 1454 } 1455 } 1456 1457 const statusLockTemplate = `# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 1458 1459 1460 {{range $p := .Projects}}[[projects]] 1461 {{- if $p.Locked.Branch}} 1462 branch = "{{$p.Locked.Branch}}" 1463 {{- end}} 1464 digest = "{{$p.Digest}}" 1465 name = "{{$p.ProjectRoot}}" 1466 packages = {{(tomlStrSplit $p.Packages)}} 1467 pruneopts = "{{$p.PruneOpts}}" 1468 revision = "{{$p.Locked.Revision}}" 1469 {{- if $p.Source}} 1470 source = "{{$p.Source}}" 1471 {{- end}} 1472 {{- if $p.Locked.Version}} 1473 version = "{{$p.Locked.Version}}" 1474 {{- end}} 1475 1476 {{end}}[solve-meta] 1477 analyzer-name = "{{.Metadata.AnalyzerName}}" 1478 analyzer-version = {{.Metadata.AnalyzerVersion}} 1479 input-imports = {{(tomlStrSplit .Metadata.InputImports)}} 1480 solver-name = "{{.Metadata.SolverName}}" 1481 solver-version = {{.Metadata.SolverVersion}} 1482 `