github.com/golang/dep@v0.5.4/cmd/dep/ensure.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 "context" 9 "flag" 10 "fmt" 11 "go/build" 12 "log" 13 "os" 14 "path/filepath" 15 "sort" 16 "strings" 17 "sync" 18 19 "github.com/golang/dep" 20 "github.com/golang/dep/gps" 21 "github.com/golang/dep/gps/paths" 22 "github.com/golang/dep/gps/pkgtree" 23 "github.com/golang/dep/gps/verify" 24 "github.com/pkg/errors" 25 ) 26 27 const ensureShortHelp = `Ensure a dependency is safely vendored in the project` 28 const ensureLongHelp = ` 29 Project spec: 30 31 <import path>[:alt source URL][@<constraint>] 32 33 34 Ensure gets a project into a complete, reproducible, and likely compilable state: 35 36 * All imports are fulfilled 37 * All rules in Gopkg.toml are respected 38 * Gopkg.lock records immutable versions for all dependencies 39 * vendor/ is populated according to Gopkg.lock 40 41 Ensure has fast techniques to determine that some of these steps may be 42 unnecessary. If that determination is made, ensure may skip some steps. Flags 43 may be passed to bypass these checks; -vendor-only will allow an out-of-date 44 Gopkg.lock to populate vendor/, and -no-vendor will update Gopkg.lock (if 45 needed), but never touch vendor/. 46 47 The effect of passing project spec arguments varies slightly depending on the 48 combination of flags that are passed. 49 50 51 Examples: 52 53 dep ensure Populate vendor from existing Gopkg.toml and Gopkg.lock 54 dep ensure -add github.com/pkg/foo Introduce a named dependency at its newest version 55 dep ensure -add github.com/pkg/foo@^1.0.1 Introduce a named dependency with a particular constraint 56 57 For more detailed usage examples, see dep ensure -examples. 58 ` 59 const ensureExamples = ` 60 dep ensure 61 62 Solve the project's dependency graph, and place all dependencies in the 63 vendor folder. If a dependency is in the lock file, use the version 64 specified there. Otherwise, use the most recent version that can satisfy the 65 constraints in the manifest file. 66 67 dep ensure -vendor-only 68 69 Write vendor/ from an existing Gopkg.lock file, without first verifying that 70 the lock is in sync with imports and Gopkg.toml. (This may be useful for 71 e.g. strategically layering a Docker images) 72 73 dep ensure -add github.com/pkg/foo github.com/pkg/foo/bar 74 75 Introduce one or more dependencies, at their newest version, ensuring that 76 specific packages are present in Gopkg.lock and vendor/. Also, append a 77 corresponding constraint to Gopkg.toml. 78 79 Note: packages introduced in this way will disappear on the next "dep 80 ensure" if an import statement is not added first. 81 82 dep ensure -add github.com/pkg/foo/subpkg@1.0.0 bitbucket.org/pkg/bar/baz@master 83 84 Append version constraints to Gopkg.toml for one or more packages, if no 85 such rules already exist. 86 87 If the named packages are not already imported, also ensure they are present 88 in Gopkg.lock and vendor/. As in the preceding example, packages introduced 89 in this way will disappear on the next "dep ensure" if an import statement 90 is not added first. 91 92 dep ensure -add github.com/pkg/foo:git.internal.com/alt/foo 93 94 Specify an alternate location to treat as the upstream source for a dependency. 95 96 dep ensure -update github.com/pkg/foo github.com/pkg/bar 97 98 Update a list of dependencies to the latest versions allowed by Gopkg.toml, 99 ignoring any versions recorded in Gopkg.lock. Write the results to 100 Gopkg.lock and vendor/. 101 102 dep ensure -update 103 104 Update all dependencies to the latest versions allowed by Gopkg.toml, 105 ignoring any versions recorded in Gopkg.lock. Update the lock file with any 106 changes. (NOTE: Not recommended. Updating one/some dependencies at a time is 107 preferred.) 108 109 dep ensure -update -no-vendor 110 111 As above, but only modify Gopkg.lock; leave vendor/ unchanged. 112 113 dep ensure -no-vendor -dry-run 114 115 This fails with a non zero exit code if Gopkg.lock is not up to date with 116 the Gopkg.toml or the project imports. It can be useful to run this during 117 CI to check if Gopkg.lock is up to date. 118 119 ` 120 121 var ( 122 errUpdateArgsValidation = errors.New("update arguments validation failed") 123 errAddDepsFailed = errors.New("adding dependencies failed") 124 ) 125 126 func (cmd *ensureCommand) Name() string { return "ensure" } 127 func (cmd *ensureCommand) Args() string { 128 return "[-update | -add] [-no-vendor | -vendor-only] [-dry-run] [-v] [<spec>...]" 129 } 130 func (cmd *ensureCommand) ShortHelp() string { return ensureShortHelp } 131 func (cmd *ensureCommand) LongHelp() string { return ensureLongHelp } 132 func (cmd *ensureCommand) Hidden() bool { return false } 133 134 func (cmd *ensureCommand) Register(fs *flag.FlagSet) { 135 fs.BoolVar(&cmd.examples, "examples", false, "print detailed usage examples") 136 fs.BoolVar(&cmd.update, "update", false, "update the named dependencies (or all, if none are named) in Gopkg.lock to the latest allowed by Gopkg.toml") 137 fs.BoolVar(&cmd.add, "add", false, "add new dependencies, or populate Gopkg.toml with constraints for existing dependencies") 138 fs.BoolVar(&cmd.vendorOnly, "vendor-only", false, "populate vendor/ from Gopkg.lock without updating it first") 139 fs.BoolVar(&cmd.noVendor, "no-vendor", false, "update Gopkg.lock (if needed), but do not update vendor/") 140 fs.BoolVar(&cmd.dryRun, "dry-run", false, "only report the changes that would be made") 141 } 142 143 type ensureCommand struct { 144 examples bool 145 update bool 146 add bool 147 noVendor bool 148 vendorOnly bool 149 dryRun bool 150 } 151 152 func (cmd *ensureCommand) Run(ctx *dep.Ctx, args []string) error { 153 if cmd.examples { 154 ctx.Err.Println(strings.TrimSpace(ensureExamples)) 155 return nil 156 } 157 158 if err := cmd.validateFlags(); err != nil { 159 return err 160 } 161 162 p, err := ctx.LoadProject() 163 if err != nil { 164 return err 165 } 166 167 sm, err := ctx.SourceManager() 168 if err != nil { 169 return err 170 } 171 sm.UseDefaultSignalHandling() 172 defer sm.Release() 173 174 if err := dep.ValidateProjectRoots(ctx, p.Manifest, sm); err != nil { 175 return err 176 } 177 178 params := p.MakeParams() 179 if ctx.Verbose { 180 params.TraceLogger = ctx.Err 181 } 182 183 if cmd.vendorOnly { 184 return cmd.runVendorOnly(ctx, args, p, sm, params) 185 } 186 187 if fatal, err := checkErrors(params.RootPackageTree.Packages, p.Manifest.IgnoredPackages()); err != nil { 188 if fatal { 189 return err 190 } else if ctx.Verbose { 191 ctx.Out.Println(err) 192 } 193 } 194 if ineffs := p.FindIneffectualConstraints(sm); len(ineffs) > 0 { 195 ctx.Err.Printf("Warning: the following project(s) have [[constraint]] stanzas in %s:\n\n", dep.ManifestName) 196 for _, ineff := range ineffs { 197 ctx.Err.Println(" ✗ ", ineff) 198 } 199 // TODO(sdboyer) lazy wording, it does not mention ignores at all 200 ctx.Err.Printf("\nHowever, these projects are not direct dependencies of the current project:\n") 201 ctx.Err.Printf("they are not imported in any .go files, nor are they in the 'required' list in\n") 202 ctx.Err.Printf("%s. Dep only applies [[constraint]] rules to direct dependencies, so\n", dep.ManifestName) 203 ctx.Err.Printf("these rules will have no effect.\n\n") 204 ctx.Err.Printf("Either import/require packages from these projects so that they become direct\n") 205 ctx.Err.Printf("dependencies, or convert each [[constraint]] to an [[override]] to enforce rules\n") 206 ctx.Err.Printf("on these projects, if they happen to be transitive dependencies.\n\n") 207 } 208 209 // Kick off vendor verification in the background. All of the remaining 210 // paths from here will need it, whether or not they end up solving. 211 go p.VerifyVendor() 212 213 if cmd.add { 214 return cmd.runAdd(ctx, args, p, sm, params) 215 } else if cmd.update { 216 return cmd.runUpdate(ctx, args, p, sm, params) 217 } 218 return cmd.runDefault(ctx, args, p, sm, params) 219 } 220 221 func (cmd *ensureCommand) validateFlags() error { 222 if cmd.add && cmd.update { 223 return errors.New("cannot pass both -add and -update") 224 } 225 226 if cmd.vendorOnly { 227 if cmd.update { 228 return errors.New("-vendor-only makes -update a no-op; cannot pass them together") 229 } 230 if cmd.add { 231 return errors.New("-vendor-only makes -add a no-op; cannot pass them together") 232 } 233 if cmd.noVendor { 234 // TODO(sdboyer) can't think of anything not snarky right now 235 return errors.New("really?") 236 } 237 } 238 return nil 239 } 240 241 func (cmd *ensureCommand) vendorBehavior() dep.VendorBehavior { 242 if cmd.noVendor { 243 return dep.VendorNever 244 } 245 return dep.VendorOnChanged 246 } 247 248 func (cmd *ensureCommand) runDefault(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { 249 // Bare ensure doesn't take any args. 250 if len(args) != 0 { 251 return errors.New("dep ensure only takes spec arguments with -add or -update") 252 } 253 254 if err := ctx.ValidateParams(sm, params); err != nil { 255 return err 256 } 257 258 var solve bool 259 lock := p.ChangedLock 260 if lock != nil { 261 lsat := verify.LockSatisfiesInputs(p.Lock, p.Manifest, params.RootPackageTree) 262 if !lsat.Satisfied() { 263 if ctx.Verbose { 264 ctx.Out.Printf("# Gopkg.lock is out of sync with Gopkg.toml and project imports:\n%s\n\n", sprintLockUnsat(lsat)) 265 } 266 solve = true 267 } else if cmd.noVendor { 268 // The user said not to touch vendor/, so definitely nothing to do. 269 return nil 270 } 271 } else { 272 solve = true 273 } 274 275 if solve { 276 solver, err := gps.Prepare(params, sm) 277 if err != nil { 278 return errors.Wrap(err, "prepare solver") 279 } 280 281 solution, err := solver.Solve(context.TODO()) 282 if err != nil { 283 return handleAllTheFailuresOfTheWorld(err) 284 } 285 lock = dep.LockFromSolution(solution, p.Manifest.PruneOptions) 286 } 287 288 dw, err := dep.NewDeltaWriter(p, lock, cmd.vendorBehavior()) 289 if err != nil { 290 return err 291 } 292 293 if cmd.dryRun { 294 return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) 295 } 296 297 var logger *log.Logger 298 if ctx.Verbose { 299 logger = ctx.Err 300 } 301 return errors.WithMessage(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") 302 } 303 304 func (cmd *ensureCommand) runVendorOnly(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { 305 if len(args) != 0 { 306 return errors.Errorf("dep ensure -vendor-only only populates vendor/ from %s; it takes no spec arguments", dep.LockName) 307 } 308 309 if p.Lock == nil { 310 return errors.Errorf("no %s exists from which to populate vendor/", dep.LockName) 311 } 312 313 // Pass the same lock as old and new so that the writer will observe no 314 // difference, and write out only ncessary vendor/ changes. 315 dw, err := dep.NewSafeWriter(nil, p.Lock, p.Lock, dep.VendorAlways, p.Manifest.PruneOptions, nil) 316 //dw, err := dep.NewDeltaWriter(p.Lock, p.Lock, p.Manifest.PruneOptions, filepath.Join(p.AbsRoot, "vendor"), dep.VendorAlways) 317 if err != nil { 318 return err 319 } 320 321 if cmd.dryRun { 322 return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) 323 } 324 325 var logger *log.Logger 326 if ctx.Verbose { 327 logger = ctx.Err 328 } 329 return errors.WithMessage(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor") 330 } 331 332 func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { 333 if p.Lock == nil { 334 return errors.Errorf("-update works by updating the versions recorded in %s, but %s does not exist", dep.LockName, dep.LockName) 335 } 336 337 if err := ctx.ValidateParams(sm, params); err != nil { 338 return err 339 } 340 341 // When -update is specified without args, allow every dependency to change 342 // versions, regardless of the lock file. 343 if len(args) == 0 { 344 params.ChangeAll = true 345 } 346 347 if err := validateUpdateArgs(ctx, args, p, sm, ¶ms); err != nil { 348 return err 349 } 350 351 // Re-prepare a solver now that our params are complete. 352 solver, err := gps.Prepare(params, sm) 353 if err != nil { 354 return errors.Wrap(err, "fastpath solver prepare") 355 } 356 solution, err := solver.Solve(context.TODO()) 357 if err != nil { 358 // TODO(sdboyer) special handling for warning cases as described in spec 359 // - e.g., named projects did not upgrade even though newer versions 360 // were available. 361 return handleAllTheFailuresOfTheWorld(err) 362 } 363 364 dw, err := dep.NewDeltaWriter(p, dep.LockFromSolution(solution, p.Manifest.PruneOptions), cmd.vendorBehavior()) 365 if err != nil { 366 return err 367 } 368 if cmd.dryRun { 369 return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) 370 } 371 372 var logger *log.Logger 373 if ctx.Verbose { 374 logger = ctx.Err 375 } 376 return errors.Wrap(dw.Write(p.AbsRoot, sm, false, logger), "grouped write of manifest, lock and vendor") 377 } 378 379 func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params gps.SolveParameters) error { 380 if len(args) == 0 { 381 return errors.New("must specify at least one project or package to -add") 382 } 383 384 if err := ctx.ValidateParams(sm, params); err != nil { 385 return err 386 } 387 388 // Compile unique sets of 1) all external packages imported or required, and 389 // 2) the project roots under which they fall. 390 exmap := make(map[string]bool) 391 if p.ChangedLock != nil { 392 for _, imp := range p.ChangedLock.InputImports() { 393 exmap[imp] = true 394 } 395 } else { 396 // We'll only hit this branch if Gopkg.lock did not exist. 397 rm, _ := p.RootPackageTree.ToReachMap(true, true, false, p.Manifest.IgnoredPackages()) 398 for _, imp := range rm.FlattenFn(paths.IsStandardImportPath) { 399 exmap[imp] = true 400 } 401 for imp := range p.Manifest.RequiredPackages() { 402 exmap[imp] = true 403 } 404 } 405 406 // Note: these flags are only partially used by the latter parts of the 407 // algorithm; rather, it relies on inference. However, they remain in their 408 // entirety as future needs may make further use of them, being a handy, 409 // terse way of expressing the original context of the arg inputs. 410 type addType uint8 411 const ( 412 // Straightforward case - this induces a temporary require, and thus 413 // a warning message about it being ephemeral. 414 isInManifest addType = 1 << iota 415 // If solving works, we'll pull this constraint from the in-memory 416 // manifest (where we recorded it earlier) and then append it to the 417 // manifest on disk. 418 isInImportsWithConstraint 419 // If solving works, we'll extract a constraint from the lock and 420 // append it into the manifest on disk, similar to init's behavior. 421 isInImportsNoConstraint 422 // This gets a message AND a hoist from the solution up into the 423 // manifest on disk. 424 isInNeither 425 ) 426 427 type addInstruction struct { 428 id gps.ProjectIdentifier 429 ephReq map[string]bool 430 constraint gps.Constraint 431 typ addType 432 } 433 addInstructions := make(map[gps.ProjectRoot]addInstruction) 434 435 // A mutex for limited access to addInstructions by goroutines. 436 var mutex sync.Mutex 437 438 // Channel for receiving all the errors. 439 errCh := make(chan error, len(args)) 440 441 var wg sync.WaitGroup 442 443 ctx.Out.Println("Fetching sources...") 444 445 for i, arg := range args { 446 wg.Add(1) 447 448 if ctx.Verbose { 449 ctx.Err.Printf("(%d/%d) %s\n", i+1, len(args), arg) 450 } 451 452 go func(arg string) { 453 defer wg.Done() 454 455 pc, path, err := getProjectConstraint(arg, sm) 456 if err != nil { 457 // TODO(sdboyer) ensure these errors are contextualized in a sensible way for -add 458 errCh <- err 459 return 460 } 461 462 // check if the the parsed path is the current root path 463 if strings.EqualFold(string(p.ImportRoot), string(pc.Ident.ProjectRoot)) { 464 errCh <- errors.New("cannot add current project to itself") 465 return 466 } 467 468 inManifest := p.Manifest.HasConstraintsOn(pc.Ident.ProjectRoot) 469 inImports := exmap[string(pc.Ident.ProjectRoot)] 470 if inManifest && inImports { 471 errCh <- errors.Errorf("nothing to -add, %s is already in %s and the project's direct imports or required list", pc.Ident.ProjectRoot, dep.ManifestName) 472 return 473 } 474 475 err = sm.SyncSourceFor(pc.Ident) 476 if err != nil { 477 errCh <- errors.Wrapf(err, "failed to fetch source for %s", pc.Ident.ProjectRoot) 478 return 479 } 480 481 someConstraint := !gps.IsAny(pc.Constraint) || pc.Ident.Source != "" 482 483 // Obtain a lock for addInstructions 484 mutex.Lock() 485 defer mutex.Unlock() 486 instr, has := addInstructions[pc.Ident.ProjectRoot] 487 if has { 488 // Multiple packages from the same project were specified as 489 // arguments; make sure they agree on declared constraints. 490 // TODO(sdboyer) until we have a general method for checking constraint equality, only allow one to declare 491 if someConstraint { 492 if !gps.IsAny(instr.constraint) || instr.id.Source != "" { 493 errCh <- errors.Errorf("can only specify rules once per project being added; rules were given at least twice for %s", pc.Ident.ProjectRoot) 494 return 495 } 496 instr.constraint = pc.Constraint 497 instr.id = pc.Ident 498 } 499 } else { 500 instr.ephReq = make(map[string]bool) 501 instr.constraint = pc.Constraint 502 instr.id = pc.Ident 503 } 504 505 if inManifest { 506 if someConstraint { 507 errCh <- errors.Errorf("%s already contains rules for %s, cannot specify a version constraint or alternate source", dep.ManifestName, path) 508 return 509 } 510 511 instr.ephReq[path] = true 512 instr.typ |= isInManifest 513 } else if inImports { 514 if !someConstraint { 515 if exmap[path] { 516 errCh <- errors.Errorf("%s is already imported or required, so -add is only valid with a constraint", path) 517 return 518 } 519 520 // No constraints, but the package isn't imported; require it. 521 // TODO(sdboyer) this case seems like it's getting overly specific and risks muddying the water more than it helps 522 instr.ephReq[path] = true 523 instr.typ |= isInImportsNoConstraint 524 } else { 525 // Don't require on this branch if the path was a ProjectRoot; 526 // most common here will be the user adding constraints to 527 // something they already imported, and if they specify the 528 // root, there's a good chance they don't actually want to 529 // require the project's root package, but are just trying to 530 // indicate which project should receive the constraints. 531 if !exmap[path] && string(pc.Ident.ProjectRoot) != path { 532 instr.ephReq[path] = true 533 } 534 instr.typ |= isInImportsWithConstraint 535 } 536 } else { 537 instr.typ |= isInNeither 538 instr.ephReq[path] = true 539 } 540 541 addInstructions[pc.Ident.ProjectRoot] = instr 542 }(arg) 543 } 544 545 wg.Wait() 546 close(errCh) 547 548 // Newline after printing the fetching source output. 549 ctx.Err.Println() 550 551 // Log all the errors. 552 if len(errCh) > 0 { 553 ctx.Err.Printf("Failed to add the dependencies:\n\n") 554 for err := range errCh { 555 ctx.Err.Println(" ✗", err.Error()) 556 } 557 ctx.Err.Println() 558 return errAddDepsFailed 559 } 560 561 // We're now sure all of our add instructions are individually and mutually 562 // valid, so it's safe to begin modifying the input parameters. 563 for pr, instr := range addInstructions { 564 // The arg processing logic above only adds to the ephReq list if 565 // that package definitely needs to be on that list, so we don't 566 // need to check instr.typ here - if it's in instr.ephReq, it 567 // definitely needs to be added to the manifest's required list. 568 for path := range instr.ephReq { 569 p.Manifest.Required = append(p.Manifest.Required, path) 570 } 571 572 // Only two branches can possibly be adding rules, though the 573 // isInNeither case may or may not have an empty constraint. 574 if instr.typ&(isInNeither|isInImportsWithConstraint) != 0 { 575 p.Manifest.Constraints[pr] = gps.ProjectProperties{ 576 Source: instr.id.Source, 577 Constraint: instr.constraint, 578 } 579 } 580 } 581 582 // Re-prepare a solver now that our params are complete. 583 solver, err := gps.Prepare(params, sm) 584 if err != nil { 585 return errors.Wrap(err, "fastpath solver prepare") 586 } 587 solution, err := solver.Solve(context.TODO()) 588 if err != nil { 589 // TODO(sdboyer) detect if the failure was specifically about some of the -add arguments 590 return handleAllTheFailuresOfTheWorld(err) 591 } 592 593 // Prep post-actions and feedback from adds. 594 var reqlist []string 595 appender := dep.NewManifest() 596 597 for pr, instr := range addInstructions { 598 for path := range instr.ephReq { 599 reqlist = append(reqlist, path) 600 } 601 602 if instr.typ&isInManifest == 0 { 603 var pp gps.ProjectProperties 604 var found bool 605 for _, proj := range solution.Projects() { 606 // We compare just ProjectRoot instead of the whole 607 // ProjectIdentifier here because an empty source on the input side 608 // could have been converted into a source by the solver. 609 if proj.Ident().ProjectRoot == pr { 610 found = true 611 pp = getProjectPropertiesFromVersion(proj.Version()) 612 break 613 } 614 } 615 if !found { 616 panic(fmt.Sprintf("unreachable: solution did not contain -add argument %s, but solver did not fail", pr)) 617 } 618 pp.Source = instr.id.Source 619 620 if !gps.IsAny(instr.constraint) { 621 pp.Constraint = instr.constraint 622 } 623 appender.Constraints[pr] = pp 624 } 625 } 626 627 extra, err := appender.MarshalTOML() 628 if err != nil { 629 return errors.Wrap(err, "could not marshal manifest into TOML") 630 } 631 sort.Strings(reqlist) 632 633 dw, err := dep.NewDeltaWriter(p, dep.LockFromSolution(solution, p.Manifest.PruneOptions), cmd.vendorBehavior()) 634 if err != nil { 635 return err 636 } 637 638 if cmd.dryRun { 639 return dw.PrintPreparedActions(ctx.Out, ctx.Verbose) 640 } 641 642 var logger *log.Logger 643 if ctx.Verbose { 644 logger = ctx.Err 645 } 646 if err := errors.Wrap(dw.Write(p.AbsRoot, sm, true, logger), "grouped write of manifest, lock and vendor"); err != nil { 647 return err 648 } 649 650 // FIXME(sdboyer) manifest writes ABSOLUTELY need verification - follow up! 651 f, err := os.OpenFile(filepath.Join(p.AbsRoot, dep.ManifestName), os.O_APPEND|os.O_WRONLY, 0666) 652 if err != nil { 653 return errors.Wrapf(err, "opening %s failed", dep.ManifestName) 654 } 655 656 if _, err := f.Write(extra); err != nil { 657 f.Close() 658 return errors.Wrapf(err, "writing to %s failed", dep.ManifestName) 659 } 660 661 switch len(reqlist) { 662 case 0: 663 // nothing to tell the user 664 case 1: 665 if cmd.noVendor { 666 ctx.Out.Printf("%q is not imported by your project, and has been temporarily added to %s.\n", reqlist[0], dep.LockName) 667 ctx.Out.Printf("If you run \"dep ensure\" again before actually importing it, it will disappear from %s. Running \"dep ensure -vendor-only\" is safe, and will guarantee it is present in vendor/.", dep.LockName) 668 } else { 669 ctx.Out.Printf("%q is not imported by your project, and has been temporarily added to %s and vendor/.\n", reqlist[0], dep.LockName) 670 ctx.Out.Printf("If you run \"dep ensure\" again before actually importing it, it will disappear from %s and vendor/.", dep.LockName) 671 } 672 default: 673 if cmd.noVendor { 674 ctx.Out.Printf("The following packages are not imported by your project, and have been temporarily added to %s:\n", dep.LockName) 675 ctx.Out.Printf("\t%s\n", strings.Join(reqlist, "\n\t")) 676 ctx.Out.Printf("If you run \"dep ensure\" again before actually importing them, they will disappear from %s. Running \"dep ensure -vendor-only\" is safe, and will guarantee they are present in vendor/.", dep.LockName) 677 } else { 678 ctx.Out.Printf("The following packages are not imported by your project, and have been temporarily added to %s and vendor/:\n", dep.LockName) 679 ctx.Out.Printf("\t%s\n", strings.Join(reqlist, "\n\t")) 680 ctx.Out.Printf("If you run \"dep ensure\" again before actually importing them, they will disappear from %s and vendor/.", dep.LockName) 681 } 682 } 683 684 return errors.Wrapf(f.Close(), "closing %s", dep.ManifestName) 685 } 686 687 func getProjectConstraint(arg string, sm gps.SourceManager) (gps.ProjectConstraint, string, error) { 688 emptyPC := gps.ProjectConstraint{ 689 Constraint: gps.Any(), // default to any; avoids panics later 690 } 691 692 // try to split on '@' 693 // When there is no `@`, use any version 694 var versionStr string 695 atIndex := strings.Index(arg, "@") 696 if atIndex > 0 { 697 parts := strings.SplitN(arg, "@", 2) 698 arg = parts[0] 699 versionStr = parts[1] 700 } 701 702 // TODO: if we decide to keep equals..... 703 704 // split on colon if there is a network location 705 var source string 706 colonIndex := strings.Index(arg, ":") 707 if colonIndex > 0 { 708 parts := strings.SplitN(arg, ":", 2) 709 arg = parts[0] 710 source = parts[1] 711 } 712 713 pr, err := sm.DeduceProjectRoot(arg) 714 if err != nil { 715 return emptyPC, "", errors.Wrapf(err, "could not infer project root from dependency path: %s", arg) // this should go through to the user 716 } 717 718 pi := gps.ProjectIdentifier{ProjectRoot: pr, Source: source} 719 c, err := sm.InferConstraint(versionStr, pi) 720 if err != nil { 721 return emptyPC, "", err 722 } 723 return gps.ProjectConstraint{Ident: pi, Constraint: c}, arg, nil 724 } 725 726 func checkErrors(m map[string]pkgtree.PackageOrErr, ignore *pkgtree.IgnoredRuleset) (fatal bool, err error) { 727 var ( 728 noGoErrors int 729 pkgtreeErrors = make(pkgtreeErrs, 0, len(m)) 730 ) 731 732 for ip, poe := range m { 733 if ignore.IsIgnored(ip) { 734 continue 735 } 736 737 if poe.Err != nil { 738 switch poe.Err.(type) { 739 case *build.NoGoError: 740 noGoErrors++ 741 default: 742 pkgtreeErrors = append(pkgtreeErrors, poe.Err) 743 } 744 } 745 } 746 747 // If pkgtree was empty or all dirs lacked any Go code, return an error. 748 if len(m) == 0 || len(m) == noGoErrors { 749 return true, errors.New("no dirs contained any Go code") 750 } 751 752 // If all dirs contained build errors, return an error. 753 if len(m) == len(pkgtreeErrors) { 754 return true, errors.New("all dirs contained build errors") 755 } 756 757 // If all directories either had no Go files or caused a build error, return an error. 758 if len(m) == len(pkgtreeErrors)+noGoErrors { 759 return true, pkgtreeErrors 760 } 761 762 // If m contained some errors, return a warning with those errors. 763 if len(pkgtreeErrors) > 0 { 764 return false, pkgtreeErrors 765 } 766 767 return false, nil 768 } 769 770 type pkgtreeErrs []error 771 772 func (e pkgtreeErrs) Error() string { 773 errs := make([]string, 0, len(e)) 774 775 for _, err := range e { 776 errs = append(errs, err.Error()) 777 } 778 779 return fmt.Sprintf("found %d errors in the package tree:\n%s", len(e), strings.Join(errs, "\n")) 780 } 781 782 func validateUpdateArgs(ctx *dep.Ctx, args []string, p *dep.Project, sm gps.SourceManager, params *gps.SolveParameters) error { 783 // Channel for receiving all the valid arguments. 784 argsCh := make(chan string, len(args)) 785 786 // Channel for receiving all the validation errors. 787 errCh := make(chan error, len(args)) 788 789 var wg sync.WaitGroup 790 791 // Allow any of specified project versions to change, regardless of the lock 792 // file. 793 for _, arg := range args { 794 wg.Add(1) 795 796 go func(arg string) { 797 defer wg.Done() 798 799 // Ensure the provided path has a deducible project root. 800 pc, path, err := getProjectConstraint(arg, sm) 801 if err != nil { 802 // TODO(sdboyer) ensure these errors are contextualized in a sensible way for -update 803 errCh <- err 804 return 805 } 806 if path != string(pc.Ident.ProjectRoot) { 807 // TODO(sdboyer): does this really merit an abortive error? 808 errCh <- errors.Errorf("%s is not a project root, try %s instead", path, pc.Ident.ProjectRoot) 809 return 810 } 811 812 if !p.Lock.HasProjectWithRoot(pc.Ident.ProjectRoot) { 813 errCh <- errors.Errorf("%s is not present in %s, cannot -update it", pc.Ident.ProjectRoot, dep.LockName) 814 return 815 } 816 817 if pc.Ident.Source != "" { 818 errCh <- errors.Errorf("cannot specify alternate sources on -update (%s)", pc.Ident.Source) 819 return 820 } 821 822 if !gps.IsAny(pc.Constraint) { 823 // TODO(sdboyer) constraints should be allowed to allow solves that 824 // target particular versions while remaining within declared constraints. 825 errCh <- errors.Errorf("version constraint %s passed for %s, but -update follows constraints declared in %s, not CLI arguments", pc.Constraint, pc.Ident.ProjectRoot, dep.ManifestName) 826 return 827 } 828 829 // Valid argument. 830 argsCh <- arg 831 }(arg) 832 } 833 834 wg.Wait() 835 close(errCh) 836 close(argsCh) 837 838 // Log all the errors. 839 if len(errCh) > 0 { 840 ctx.Err.Printf("Invalid arguments passed to ensure -update:\n\n") 841 for err := range errCh { 842 ctx.Err.Println(" ✗", err.Error()) 843 } 844 ctx.Err.Println() 845 return errUpdateArgsValidation 846 } 847 848 // Add all the valid arguments to solve params. 849 for arg := range argsCh { 850 params.ToChange = append(params.ToChange, gps.ProjectRoot(arg)) 851 } 852 853 return nil 854 }