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, &params); 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  }