Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 82b3908

Browse files
authored
Merge pull request #1218 from darkowlzz/concurrent-ensure-add
Concurrent ensure add
2 parents 6fb987b + e7899a3 commit 82b3908

File tree

2 files changed

+112
-63
lines changed

2 files changed

+112
-63
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
IMPROVEMENTS:
44

5+
* `dep ensure -add` now concurrently fetches the source and adds the projects.
6+
(#1218)
57
* File name case check is now performed on `Gopkg.toml` and `Gopkg.lock`.
68
(#1114)
79
* gps: gps now supports pruning. (#1020)

cmd/dep/ensure.go

Lines changed: 110 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,10 @@ dep ensure -update -no-vendor
112112
113113
`
114114

115-
var errUpdateArgsValidation = errors.New("update arguments validation failed")
115+
var (
116+
errUpdateArgsValidation = errors.New("update arguments validation failed")
117+
errAddDepsFailed = errors.New("adding dependencies failed")
118+
)
116119

117120
func (cmd *ensureCommand) Name() string { return "ensure" }
118121
func (cmd *ensureCommand) Args() string {
@@ -468,86 +471,130 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
468471
}
469472
addInstructions := make(map[gps.ProjectRoot]addInstruction)
470473

471-
for _, arg := range args {
472-
// TODO(sdboyer) return all errors, not just the first one we encounter
473-
// TODO(sdboyer) do these concurrently?
474-
pc, path, err := getProjectConstraint(arg, sm)
475-
if err != nil {
476-
// TODO(sdboyer) ensure these errors are contextualized in a sensible way for -add
477-
return err
478-
}
474+
// A mutex for limited access to addInstructions by goroutines.
475+
var mutex sync.Mutex
479476

480-
// check if the the parsed path is the current root path
481-
if strings.EqualFold(string(p.ImportRoot), string(pc.Ident.ProjectRoot)) {
482-
return errors.New("cannot add current project to itself")
483-
}
477+
// Channel for receiving all the errors.
478+
errCh := make(chan error, len(args))
484479

485-
inManifest := p.Manifest.HasConstraintsOn(pc.Ident.ProjectRoot)
486-
inImports := exrmap[pc.Ident.ProjectRoot]
487-
if inManifest && inImports {
488-
return errors.Errorf("nothing to -add, %s is already in %s and the project's direct imports or required list", pc.Ident.ProjectRoot, dep.ManifestName)
489-
}
480+
var wg sync.WaitGroup
490481

491-
err = sm.SyncSourceFor(pc.Ident)
492-
if err != nil {
493-
return errors.Wrapf(err, "failed to fetch source for %s", pc.Ident.ProjectRoot)
482+
fmt.Println("Fetching sources...")
483+
484+
for i, arg := range args {
485+
wg.Add(1)
486+
487+
if ctx.Verbose {
488+
ctx.Err.Printf("(%d/%d) %s\n", i+1, len(args), arg)
494489
}
495490

496-
someConstraint := !gps.IsAny(pc.Constraint) || pc.Ident.Source != ""
491+
go func(arg string) {
492+
defer wg.Done()
493+
494+
pc, path, err := getProjectConstraint(arg, sm)
495+
if err != nil {
496+
// TODO(sdboyer) ensure these errors are contextualized in a sensible way for -add
497+
errCh <- err
498+
return
499+
}
500+
501+
// check if the the parsed path is the current root path
502+
if strings.EqualFold(string(p.ImportRoot), string(pc.Ident.ProjectRoot)) {
503+
errCh <- errors.New("cannot add current project to itself")
504+
return
505+
}
506+
507+
inManifest := p.Manifest.HasConstraintsOn(pc.Ident.ProjectRoot)
508+
inImports := exrmap[pc.Ident.ProjectRoot]
509+
if inManifest && inImports {
510+
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)
511+
return
512+
}
497513

498-
instr, has := addInstructions[pc.Ident.ProjectRoot]
499-
if has {
500-
// Multiple packages from the same project were specified as
501-
// arguments; make sure they agree on declared constraints.
502-
// TODO(sdboyer) until we have a general method for checking constraint equality, only allow one to declare
503-
if someConstraint {
504-
if !gps.IsAny(instr.constraint) || instr.id.Source != "" {
505-
return errors.Errorf("can only specify rules once per project being added; rules were given at least twice for %s", pc.Ident.ProjectRoot)
514+
err = sm.SyncSourceFor(pc.Ident)
515+
if err != nil {
516+
errCh <- errors.Wrapf(err, "failed to fetch source for %s", pc.Ident.ProjectRoot)
517+
return
518+
}
519+
520+
someConstraint := !gps.IsAny(pc.Constraint) || pc.Ident.Source != ""
521+
522+
// Obtain a lock for addInstructions
523+
mutex.Lock()
524+
defer mutex.Unlock()
525+
instr, has := addInstructions[pc.Ident.ProjectRoot]
526+
if has {
527+
// Multiple packages from the same project were specified as
528+
// arguments; make sure they agree on declared constraints.
529+
// TODO(sdboyer) until we have a general method for checking constraint equality, only allow one to declare
530+
if someConstraint {
531+
if !gps.IsAny(instr.constraint) || instr.id.Source != "" {
532+
errCh <- errors.Errorf("can only specify rules once per project being added; rules were given at least twice for %s", pc.Ident.ProjectRoot)
533+
return
534+
}
535+
instr.constraint = pc.Constraint
536+
instr.id = pc.Ident
506537
}
538+
} else {
539+
instr.ephReq = make(map[string]bool)
507540
instr.constraint = pc.Constraint
508541
instr.id = pc.Ident
509542
}
510-
} else {
511-
instr.ephReq = make(map[string]bool)
512-
instr.constraint = pc.Constraint
513-
instr.id = pc.Ident
514-
}
515-
516-
if inManifest {
517-
if someConstraint {
518-
return errors.Errorf("%s already contains rules for %s, cannot specify a version constraint or alternate source", dep.ManifestName, path)
519-
}
520543

521-
instr.ephReq[path] = true
522-
instr.typ |= isInManifest
523-
} else if inImports {
524-
if !someConstraint {
525-
if exmap[path] {
526-
return errors.Errorf("%s is already imported or required, so -add is only valid with a constraint", path)
544+
if inManifest {
545+
if someConstraint {
546+
errCh <- errors.Errorf("%s already contains rules for %s, cannot specify a version constraint or alternate source", dep.ManifestName, path)
547+
return
527548
}
528549

529-
// No constraints, but the package isn't imported; require it.
530-
// TODO(sdboyer) this case seems like it's getting overly specific and risks muddying the water more than it helps
531550
instr.ephReq[path] = true
532-
instr.typ |= isInImportsNoConstraint
533-
} else {
534-
// Don't require on this branch if the path was a ProjectRoot;
535-
// most common here will be the user adding constraints to
536-
// something they already imported, and if they specify the
537-
// root, there's a good chance they don't actually want to
538-
// require the project's root package, but are just trying to
539-
// indicate which project should receive the constraints.
540-
if !exmap[path] && string(pc.Ident.ProjectRoot) != path {
551+
instr.typ |= isInManifest
552+
} else if inImports {
553+
if !someConstraint {
554+
if exmap[path] {
555+
errCh <- errors.Errorf("%s is already imported or required, so -add is only valid with a constraint", path)
556+
return
557+
}
558+
559+
// No constraints, but the package isn't imported; require it.
560+
// TODO(sdboyer) this case seems like it's getting overly specific and risks muddying the water more than it helps
541561
instr.ephReq[path] = true
562+
instr.typ |= isInImportsNoConstraint
563+
} else {
564+
// Don't require on this branch if the path was a ProjectRoot;
565+
// most common here will be the user adding constraints to
566+
// something they already imported, and if they specify the
567+
// root, there's a good chance they don't actually want to
568+
// require the project's root package, but are just trying to
569+
// indicate which project should receive the constraints.
570+
if !exmap[path] && string(pc.Ident.ProjectRoot) != path {
571+
instr.ephReq[path] = true
572+
}
573+
instr.typ |= isInImportsWithConstraint
542574
}
543-
instr.typ |= isInImportsWithConstraint
575+
} else {
576+
instr.typ |= isInNeither
577+
instr.ephReq[path] = true
544578
}
545-
} else {
546-
instr.typ |= isInNeither
547-
instr.ephReq[path] = true
548-
}
549579

550-
addInstructions[pc.Ident.ProjectRoot] = instr
580+
addInstructions[pc.Ident.ProjectRoot] = instr
581+
}(arg)
582+
}
583+
584+
wg.Wait()
585+
close(errCh)
586+
587+
// Newline after printing the fetching source output.
588+
ctx.Err.Println()
589+
590+
// Log all the errors.
591+
if len(errCh) > 0 {
592+
ctx.Err.Printf("Failed to add the dependencies:\n\n")
593+
for err := range errCh {
594+
ctx.Err.Println(" ✗", err.Error())
595+
}
596+
ctx.Err.Println()
597+
return errAddDepsFailed
551598
}
552599

553600
// We're now sure all of our add instructions are individually and mutually

0 commit comments

Comments
 (0)