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

Commit f38e02b

Browse files
committed
Add handling of feedback, etc. on ensure -add
1 parent 61b8954 commit f38e02b

File tree

1 file changed

+149
-32
lines changed

1 file changed

+149
-32
lines changed

cmd/dep/ensure.go

Lines changed: 149 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"encoding/hex"
1010
"flag"
1111
"go/build"
12+
"os"
13+
"path/filepath"
14+
"sort"
1215
"strconv"
1316
"strings"
1417

@@ -34,10 +37,10 @@ Flags:
3437
-dry-run: only report the changes that would be made
3538
3639
37-
Ensure gets a project into a complete, compilable, and reproducible state:
40+
Ensure gets a project into a complete, reproducible, and likely compilable state:
3841
3942
* All non-stdlib imports are fulfilled
40-
* All constraints and overrides in Gopkg.toml are respected
43+
* All rules in Gopkg.toml are respected
4144
* Gopkg.lock records precise versions for all dependencies
4245
* vendor/ is populated according to Gopkg.lock
4346
@@ -336,6 +339,9 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project,
336339
}
337340
solution, err := solver.Solve()
338341
if err != nil {
342+
// TODO(sdboyer) special handling for warning cases as described in spec
343+
// - e.g., named projects did not upgrade even though newer versions
344+
// were available.
339345
handleAllTheFailuresOfTheWorld(err)
340346
return errors.Wrap(err, "ensure Solve()")
341347
}
@@ -344,9 +350,6 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project,
344350
if err != nil {
345351
return err
346352
}
347-
// TODO(sdboyer) special handling for warning cases as described in spec -
348-
// e.g., named projects did not upgrade even though newer versions were
349-
// available.
350353
if cmd.dryRun {
351354
return sw.PrintPreparedActions(ctx.Out)
352355
}
@@ -403,11 +406,38 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
403406
exrmap[root] = true
404407
}
405408

406-
var reqlist []string
409+
// Note: these flags are only partialy used by the latter parts of the
410+
// algorithm; rather, it relies on inference. However, they remain in their
411+
// entirety as future needs may make further use of them, being a handy,
412+
// terse way of expressing the original context of the arg inputs.
413+
type addType uint8
414+
const (
415+
// Straightforward case - this induces a temporary require, and thus
416+
// a warning message about it being ephemeral.
417+
isInManifest addType = 1 << iota
418+
// If solving works, we'll pull this constraint from the in-memory
419+
// manifest (where we recorded it earlier) and then append it to the
420+
// manifest on disk.
421+
isInImportsWithConstraint
422+
// If solving works, we'll extract a constraint from the lock and
423+
// append it into the manifest on disk, similar to init's behavior.
424+
isInImportsNoConstraint
425+
// This gets a message AND a hoist from the solution up into the
426+
// manifest on disk.
427+
isInNeither
428+
)
429+
430+
type addInstruction struct {
431+
id gps.ProjectIdentifier
432+
ephReq map[string]bool
433+
constraint gps.Constraint
434+
typ addType
435+
}
436+
addInstructions := make(map[gps.ProjectRoot]addInstruction)
407437

408438
for _, arg := range args {
409439
// TODO(sdboyer) return all errors, not just the first one we encounter
410-
// TODO(sdboyer) do these concurrently
440+
// TODO(sdboyer) do these concurrently?
411441
pc, path, err := getProjectConstraint(arg, sm)
412442
if err != nil {
413443
// TODO(sdboyer) ensure these errors are contextualized in a sensible way for -add
@@ -426,50 +456,82 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
426456
}
427457

428458
someConstraint := !gps.IsAny(pc.Constraint) || pc.Ident.Source != ""
459+
460+
instr, has := addInstructions[pc.Ident.ProjectRoot]
461+
if has {
462+
// Multiple packages from the same project were specified as
463+
// arguments; make sure they agree on declared constraints.
464+
// TODO(sdboyer) until we have a general method for checking constraint equality, only allow one to declare
465+
if someConstraint {
466+
if !gps.IsAny(instr.constraint) || instr.id.Source != "" {
467+
return errors.Errorf("can only specify rules once per project being added; rules were given at least twice for %s", pc.Ident.ProjectRoot)
468+
}
469+
instr.constraint = pc.Constraint
470+
instr.id = pc.Ident
471+
}
472+
} else {
473+
instr.ephReq = make(map[string]bool)
474+
instr.constraint = pc.Constraint
475+
instr.id = pc.Ident
476+
}
477+
429478
if inManifest {
430479
if someConstraint {
431480
return errors.Errorf("%s already contains rules for %s, cannot specify a version constraint or alternate source", dep.ManifestName, path)
432481
}
433482

434-
reqlist = append(reqlist, path)
435-
p.Manifest.Required = append(p.Manifest.Required, path)
483+
instr.ephReq[path] = true
484+
instr.typ |= isInManifest
485+
//p.Manifest.Required = append(p.Manifest.Required, path)
436486
} else if inImports {
437487
if !someConstraint {
438488
if exmap[path] {
439-
return errors.Errorf("%s is already imported or required; -add must specify a constraint, but none were provided", path)
489+
return errors.Errorf("%s is already imported or required, so -add is only valid with a constraint", path)
440490
}
441491

442492
// No constraints, but the package isn't imported; require it.
443-
// TODO(sdboyer) this case seems like it's getting overly
444-
// specific and risks muddying the water more than it helps
445-
reqlist = append(reqlist, path)
446-
p.Manifest.Required = append(p.Manifest.Required, path)
493+
// TODO(sdboyer) this case seems like it's getting overly specific and risks muddying the water more than it helps
494+
instr.ephReq[path] = true
495+
instr.typ |= isInImportsNoConstraint
447496
} else {
448-
p.Manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{
449-
Source: pc.Ident.Source,
450-
Constraint: pc.Constraint,
451-
}
452-
453497
// Don't require on this branch if the path was a ProjectRoot;
454498
// most common here will be the user adding constraints to
455499
// something they already imported, and if they specify the
456500
// root, there's a good chance they don't actually want to
457501
// require the project's root package, but are just trying to
458502
// indicate which project should receive the constraints.
459503
if !exmap[path] && string(pc.Ident.ProjectRoot) != path {
460-
reqlist = append(reqlist, path)
461-
p.Manifest.Required = append(p.Manifest.Required, path)
504+
instr.ephReq[path] = true
462505
}
506+
instr.typ |= isInImportsWithConstraint
463507
}
464508
} else {
465-
p.Manifest.Constraints[pc.Ident.ProjectRoot] = gps.ProjectProperties{
466-
Source: pc.Ident.Source,
467-
Constraint: pc.Constraint,
468-
}
509+
instr.typ |= isInNeither
510+
instr.ephReq[path] = true
511+
}
469512

470-
reqlist = append(reqlist, path)
513+
addInstructions[pc.Ident.ProjectRoot] = instr
514+
}
515+
516+
// We're now sure all of our add instructions are individually and mutually
517+
// valid, so it's safe to begin modifying the input parameters.
518+
for pr, instr := range addInstructions {
519+
// The arg processing logic above only adds to the ephReq list if
520+
// that package definitely needs to be on that list, so we don't
521+
// need to check instr.typ here - if it's in instr.ephReq, it
522+
// definitely needs to be added to the manifest's required list.
523+
for path := range instr.ephReq {
471524
p.Manifest.Required = append(p.Manifest.Required, path)
472525
}
526+
527+
// Only two branches can possibly be adding rules, though the
528+
// isInNeither case may or may not have an empty constraint.
529+
if instr.typ&(isInNeither|isInImportsWithConstraint) != 0 {
530+
p.Manifest.Constraints[pr] = gps.ProjectProperties{
531+
Source: instr.id.Source,
532+
Constraint: instr.constraint,
533+
}
534+
}
473535
}
474536

475537
// Re-prepare a solver now that our params are complete.
@@ -485,10 +547,33 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
485547
return errors.Wrap(err, "ensure Solve()")
486548
}
487549

550+
// Prep post-actions and feedback from adds.
551+
var reqlist []string
552+
appender := &dep.Manifest{
553+
Constraints: make(gps.ProjectConstraints),
554+
}
555+
for pr, instr := range addInstructions {
556+
for path := range instr.ephReq {
557+
reqlist = append(reqlist, path)
558+
}
559+
560+
if !gps.IsAny(instr.constraint) || instr.id.Source != "" {
561+
appender.Constraints[pr] = gps.ProjectProperties{
562+
Source: instr.id.Source,
563+
Constraint: instr.constraint,
564+
}
565+
} else {
566+
// TODO(sdboyer) hoist a constraint into the manifest from the lock
567+
}
568+
}
569+
570+
extra, err := appender.MarshalTOML()
571+
if err != nil {
572+
return errors.Wrap(err, "could not marshal manifest into TOML")
573+
}
574+
sort.Strings(reqlist)
575+
488576
sw, err := dep.NewSafeWriter(nil, p.Lock, dep.LockFromSolution(solution), dep.VendorOnChanged)
489-
// TODO(sdboyer) special handling for warning cases as described in spec -
490-
// e.g., named projects did not upgrade even though newer versions were
491-
// available.
492577
if cmd.dryRun {
493578
return sw.PrintPreparedActions(ctx.Out)
494579
}
@@ -498,9 +583,41 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
498583
return err
499584
}
500585

501-
// TODO(sdboyer) handle appending of constraints to Gopkg.toml here, plus
502-
// info messages to user
503-
return nil
586+
// FIXME(sdboyer) manifest writes ABSOLUTELY need verification - follow up!
587+
f, err := os.OpenFile(filepath.Join(p.AbsRoot, dep.ManifestName), os.O_APPEND|os.O_WRONLY, 0666)
588+
if err != nil {
589+
return errors.Wrapf(err, "opening %s failed", dep.ManifestName)
590+
}
591+
592+
_, err = f.Write(append([]byte("\n"), extra...))
593+
if err != nil {
594+
return errors.Wrapf(err, "writing to %s failed", dep.ManifestName)
595+
}
596+
597+
switch len(reqlist) {
598+
case 0:
599+
// nothing to tell the user
600+
case 1:
601+
if cmd.noVendor {
602+
ctx.Out.Printf("%q is not imported by your project, and has been temporarily added to %s.\n", reqlist[0], dep.LockName)
603+
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)
604+
} else {
605+
ctx.Out.Printf("%q is not imported by your project, and has been temporarily added to %s and vendor/.\n", reqlist[0], dep.LockName)
606+
ctx.Out.Printf("If you run \"dep ensure\" again before actually importing it, it will disappear from %s and vendor/.", dep.LockName)
607+
}
608+
default:
609+
if cmd.noVendor {
610+
ctx.Out.Printf("The following packages are not imported by your project, and have been temporarily added to %s:\n", dep.LockName)
611+
ctx.Out.Printf("\t%s\n", strings.Join(reqlist, "\n\t"))
612+
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)
613+
} else {
614+
ctx.Out.Printf("The following packages are not imported by your project, and have been temporarily added to %s and vendor/:\n", dep.LockName)
615+
ctx.Out.Printf("\t%s\n", strings.Join(reqlist, "\n\t"))
616+
ctx.Out.Printf("If you run \"dep ensure\" again before actually importing them, they will disappear from %s and vendor/.", dep.LockName)
617+
}
618+
}
619+
620+
return errors.Wrapf(f.Close(), "closing %s", dep.ManifestName)
504621
}
505622

506623
type stringSlice []string

0 commit comments

Comments
 (0)