9
9
"encoding/hex"
10
10
"flag"
11
11
"go/build"
12
+ "os"
13
+ "path/filepath"
14
+ "sort"
12
15
"strconv"
13
16
"strings"
14
17
@@ -34,10 +37,10 @@ Flags:
34
37
-dry-run: only report the changes that would be made
35
38
36
39
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:
38
41
39
42
* All non-stdlib imports are fulfilled
40
- * All constraints and overrides in Gopkg.toml are respected
43
+ * All rules in Gopkg.toml are respected
41
44
* Gopkg.lock records precise versions for all dependencies
42
45
* vendor/ is populated according to Gopkg.lock
43
46
@@ -336,6 +339,9 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project,
336
339
}
337
340
solution , err := solver .Solve ()
338
341
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.
339
345
handleAllTheFailuresOfTheWorld (err )
340
346
return errors .Wrap (err , "ensure Solve()" )
341
347
}
@@ -344,9 +350,6 @@ func (cmd *ensureCommand) runUpdate(ctx *dep.Ctx, args []string, p *dep.Project,
344
350
if err != nil {
345
351
return err
346
352
}
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.
350
353
if cmd .dryRun {
351
354
return sw .PrintPreparedActions (ctx .Out )
352
355
}
@@ -403,11 +406,38 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
403
406
exrmap [root ] = true
404
407
}
405
408
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 )
407
437
408
438
for _ , arg := range args {
409
439
// TODO(sdboyer) return all errors, not just the first one we encounter
410
- // TODO(sdboyer) do these concurrently
440
+ // TODO(sdboyer) do these concurrently?
411
441
pc , path , err := getProjectConstraint (arg , sm )
412
442
if err != nil {
413
443
// 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
426
456
}
427
457
428
458
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
+
429
478
if inManifest {
430
479
if someConstraint {
431
480
return errors .Errorf ("%s already contains rules for %s, cannot specify a version constraint or alternate source" , dep .ManifestName , path )
432
481
}
433
482
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)
436
486
} else if inImports {
437
487
if ! someConstraint {
438
488
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 )
440
490
}
441
491
442
492
// 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
447
496
} else {
448
- p .Manifest .Constraints [pc .Ident .ProjectRoot ] = gps.ProjectProperties {
449
- Source : pc .Ident .Source ,
450
- Constraint : pc .Constraint ,
451
- }
452
-
453
497
// Don't require on this branch if the path was a ProjectRoot;
454
498
// most common here will be the user adding constraints to
455
499
// something they already imported, and if they specify the
456
500
// root, there's a good chance they don't actually want to
457
501
// require the project's root package, but are just trying to
458
502
// indicate which project should receive the constraints.
459
503
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
462
505
}
506
+ instr .typ |= isInImportsWithConstraint
463
507
}
464
508
} 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
+ }
469
512
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 {
471
524
p .Manifest .Required = append (p .Manifest .Required , path )
472
525
}
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
+ }
473
535
}
474
536
475
537
// 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
485
547
return errors .Wrap (err , "ensure Solve()" )
486
548
}
487
549
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
+
488
576
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.
492
577
if cmd .dryRun {
493
578
return sw .PrintPreparedActions (ctx .Out )
494
579
}
@@ -498,9 +583,41 @@ func (cmd *ensureCommand) runAdd(ctx *dep.Ctx, args []string, p *dep.Project, sm
498
583
return err
499
584
}
500
585
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 )
504
621
}
505
622
506
623
type stringSlice []string
0 commit comments