2016-12-07 04:15:37 +03:00
|
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
2016-12-07 05:13:44 +03:00
|
|
|
|
import (
|
2016-12-13 04:54:57 +03:00
|
|
|
|
"bytes"
|
2016-12-07 05:13:44 +03:00
|
|
|
|
"encoding/hex"
|
2016-12-13 04:54:57 +03:00
|
|
|
|
"flag"
|
2016-12-07 05:13:44 +03:00
|
|
|
|
"fmt"
|
2016-12-09 22:27:39 +03:00
|
|
|
|
"io"
|
|
|
|
|
"log"
|
2016-12-07 05:13:44 +03:00
|
|
|
|
"os"
|
2016-12-09 22:27:39 +03:00
|
|
|
|
"path/filepath"
|
2016-12-07 05:13:44 +03:00
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2016-12-09 22:27:39 +03:00
|
|
|
|
"io/ioutil"
|
|
|
|
|
|
2016-12-07 05:13:44 +03:00
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/sdboyer/gps"
|
|
|
|
|
)
|
|
|
|
|
|
2016-12-07 04:15:37 +03:00
|
|
|
|
var ensureCmd = &command{
|
|
|
|
|
fn: runEnsure,
|
|
|
|
|
name: "ensure",
|
2016-12-13 04:54:57 +03:00
|
|
|
|
flag: flag.NewFlagSet("", flag.ExitOnError),
|
2016-12-07 04:15:37 +03:00
|
|
|
|
short: `[flags] <path>[:alt location][@<version specifier>]
|
|
|
|
|
To ensure a dependency is in your project at a specific version (if specified).
|
|
|
|
|
`,
|
|
|
|
|
long: `
|
|
|
|
|
Run it when
|
|
|
|
|
To ensure a new dependency is in your project.
|
|
|
|
|
To ensure a dependency is updated.
|
|
|
|
|
To the latest version that satisfies constraints.
|
|
|
|
|
To a specific version or different constraint.
|
|
|
|
|
(With no arguments) To ensure that you have all the dependencies specified by your Manifest + lockfile.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
What it does
|
|
|
|
|
Download code, placing in the vendor/ directory of the project. Only the packages that are actually used by the current project and its dependencies are included.
|
|
|
|
|
Any authentication, proxy settings, or other parameters regarding communicating with external repositories is the responsibility of the underlying VCS tools.
|
|
|
|
|
Resolve version constraints
|
|
|
|
|
If the set of constraints are not solvable, print an error
|
|
|
|
|
Collapse any vendor folders in the downloaded code and its transient deps to the root.
|
|
|
|
|
Includes dependencies required by the current project’s tests. There are arguments both for and against including the deps of tests for any transitive deps. Defer on deciding this for now.
|
|
|
|
|
Copy the relevant versions of the code to the current project’s vendor directory.
|
|
|
|
|
The source of that code is implementation dependant. Probably some kind of local cache of the VCS data (not a GOPATH/workspace).
|
|
|
|
|
Write Manifest (if changed) and Lockfile
|
|
|
|
|
Print what changed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Flags:
|
|
|
|
|
-update update all packages
|
|
|
|
|
-n dry run
|
|
|
|
|
-override <specs> specify an override constraints for package(s)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Package specs:
|
|
|
|
|
<path>[:alt location][@<version specifier>]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
Fetch/update github.com/heroku/rollrus to latest version, including transitive dependencies (ensuring it matches the constraints of rollrus, or—if not contrained—their latest versions):
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus
|
|
|
|
|
Same dep, but choose any minor patch release in the 0.9.X series, setting the constraint. If another constraint exists that constraint is changed to ~0.9.0:
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus@~0.9.0
|
|
|
|
|
Same dep, but choose any release >= 0.9.1 and < 1.0.0, setting/changing constraints:
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus@^0.9.1
|
|
|
|
|
Same dep, but updating to 1.0.X:
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus@~1.0.0
|
|
|
|
|
Same dep, but fetching from a different location:
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus:git.example.com/foo/bar
|
|
|
|
|
Same dep, but check out a specific version or range without updating the Manifest and update the Lockfile. This will fail if the specified version does not satisfy any existing constraints:
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus==1.2.3 # 1.2.3 specifically
|
|
|
|
|
$ dep ensure github.com/heroku/rollrus=^1.2.0 # >= 1.2.0 < 2.0.0
|
|
|
|
|
Override any declared dependency range of 'github.com/foo/bar' to have the range of '^0.9.1'. This applies transitively:
|
|
|
|
|
$ dep ensure -override github.com/foo/bar@^0.9.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Transitive deps are ensured based on constraints in the local Manifest if they exist, then constraints in the dependency’s Manifest file. A lack of constraints defaults to the latest version, eg "^2".
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For a description of the version specifier string, see this handy guide from crates.io. We are going to defer on making a final decision about this syntax until we have more experience with it in practice.
|
|
|
|
|
`,
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-13 05:18:24 +03:00
|
|
|
|
// stringSlice is a slice of strings
|
|
|
|
|
type stringSlice []string
|
|
|
|
|
|
|
|
|
|
// implement the flag interface for stringSlice
|
|
|
|
|
func (s *stringSlice) String() string {
|
|
|
|
|
return fmt.Sprintf("%s", *s)
|
|
|
|
|
}
|
|
|
|
|
func (s *stringSlice) Set(value string) error {
|
|
|
|
|
*s = append(*s, value)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var overrides stringSlice
|
2016-12-13 04:54:57 +03:00
|
|
|
|
|
|
|
|
|
func init() {
|
2016-12-13 05:18:24 +03:00
|
|
|
|
ensureCmd.flag.Var(&overrides, "override", "Interpret specified constraint(s) as override(s) rather than normal constraints")
|
2016-12-13 04:54:57 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-07 04:15:37 +03:00
|
|
|
|
func runEnsure(args []string) error {
|
2016-12-07 05:13:44 +03:00
|
|
|
|
p, err := depContext.loadProject("")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sm, err := depContext.sourceManager()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer sm.Release()
|
|
|
|
|
|
|
|
|
|
var errs []error
|
|
|
|
|
for _, arg := range args {
|
|
|
|
|
// default persist to manifest
|
|
|
|
|
constraint, err := getProjectConstraint(arg, sm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, err)
|
|
|
|
|
}
|
2016-12-13 05:18:24 +03:00
|
|
|
|
p.m.Dependencies[constraint.Ident.ProjectRoot] = gps.ProjectProperties{
|
2016-12-07 05:13:44 +03:00
|
|
|
|
NetworkName: constraint.Ident.NetworkName,
|
|
|
|
|
Constraint: constraint.Constraint,
|
|
|
|
|
}
|
2016-12-13 05:07:55 +03:00
|
|
|
|
|
2016-12-13 05:18:24 +03:00
|
|
|
|
for i, lp := range p.l.P {
|
|
|
|
|
if lp.Ident() == constraint.Ident {
|
|
|
|
|
p.l.P = append(p.l.P[:i], p.l.P[i+1:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ovr := range overrides {
|
|
|
|
|
constraint, err := getProjectConstraint(ovr, sm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = append(errs, err)
|
|
|
|
|
}
|
|
|
|
|
p.m.Ovr[constraint.Ident.ProjectRoot] = gps.ProjectProperties{
|
|
|
|
|
NetworkName: constraint.Ident.NetworkName,
|
|
|
|
|
Constraint: constraint.Constraint,
|
2016-12-13 05:07:55 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-09 22:54:12 +03:00
|
|
|
|
for i, lp := range p.l.P {
|
|
|
|
|
if lp.Ident() == constraint.Ident {
|
|
|
|
|
p.l.P = append(p.l.P[:i], p.l.P[i+1:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-07 05:13:44 +03:00
|
|
|
|
}
|
2016-12-13 04:54:57 +03:00
|
|
|
|
|
2016-12-07 05:13:44 +03:00
|
|
|
|
if len(errs) > 0 {
|
2016-12-13 04:54:57 +03:00
|
|
|
|
var buf bytes.Buffer
|
2016-12-07 05:13:44 +03:00
|
|
|
|
for err := range errs {
|
2016-12-13 04:54:57 +03:00
|
|
|
|
fmt.Fprintln(&buf, err)
|
2016-12-07 05:13:44 +03:00
|
|
|
|
}
|
2016-12-13 04:54:57 +03:00
|
|
|
|
|
|
|
|
|
return errors.New(buf.String())
|
2016-12-07 05:13:44 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-09 22:27:39 +03:00
|
|
|
|
params := gps.SolveParameters{
|
|
|
|
|
RootDir: p.absroot,
|
|
|
|
|
Manifest: p.m,
|
|
|
|
|
Lock: p.l,
|
|
|
|
|
Trace: true,
|
|
|
|
|
TraceLogger: log.New(os.Stdout, "", 0),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
params.RootPackageTree, err = gps.ListPackages(p.absroot, string(p.importroot))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure ListPackage for project")
|
|
|
|
|
}
|
|
|
|
|
solver, err := gps.Prepare(params, sm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure Prepare")
|
|
|
|
|
}
|
|
|
|
|
solution, err := solver.Solve()
|
|
|
|
|
if err != nil {
|
2016-12-10 03:18:11 +03:00
|
|
|
|
handleAllTheFailuresOfTheWorld(err)
|
2016-12-09 22:27:39 +03:00
|
|
|
|
return errors.Wrap(err, "ensure Solve()")
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-10 03:18:11 +03:00
|
|
|
|
p.l.P = solution.Projects()
|
|
|
|
|
p.l.Memo = solution.InputHash()
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
|
|
|
|
tv, err := ioutil.TempDir("", "vendor")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure making temporary vendor")
|
|
|
|
|
}
|
|
|
|
|
defer os.RemoveAll(tv)
|
|
|
|
|
|
|
|
|
|
tm, err := ioutil.TempFile("", "manifest")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure making temporary manifest")
|
|
|
|
|
}
|
|
|
|
|
tm.Close()
|
|
|
|
|
defer os.Remove(tm.Name())
|
|
|
|
|
|
|
|
|
|
tl, err := ioutil.TempFile("", "lock")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure making temporary lock file")
|
|
|
|
|
}
|
|
|
|
|
tl.Close()
|
|
|
|
|
defer os.Remove(tl.Name())
|
|
|
|
|
|
|
|
|
|
if err := gps.WriteDepTree(tv, p.l, sm, true); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure gps.WriteDepTree")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := writeFile(tm.Name(), p.m); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure writeFile for manifest")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := writeFile(tl.Name(), p.l); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure writeFile for lock")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := os.Rename(tm.Name(), filepath.Join(p.absroot, manifestName)); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure moving temp manifest into place!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := os.Rename(tl.Name(), filepath.Join(p.absroot, lockName)); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure moving temp manifest into place!")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
os.RemoveAll(filepath.Join(p.absroot, "vendor"))
|
|
|
|
|
if err := copyFolder(tv, filepath.Join(p.absroot, "vendor")); err != nil {
|
|
|
|
|
return errors.Wrap(err, "ensure moving temp vendor")
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-07 04:15:37 +03:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2016-12-07 05:13:44 +03:00
|
|
|
|
|
|
|
|
|
func getProjectConstraint(arg string, sm *gps.SourceMgr) (gps.ProjectConstraint, error) {
|
|
|
|
|
constraint := gps.ProjectConstraint{}
|
|
|
|
|
|
|
|
|
|
// try to split on '@'
|
|
|
|
|
atIndex := strings.Index(arg, "@")
|
|
|
|
|
if atIndex > 0 {
|
|
|
|
|
parts := strings.SplitN(arg, "@", 2)
|
|
|
|
|
constraint.Constraint = deduceConstraint(parts[1])
|
|
|
|
|
arg = parts[0]
|
|
|
|
|
}
|
2016-12-09 22:27:39 +03:00
|
|
|
|
// TODO: What if there is no @, assume default branch (which may not be master) ?
|
2016-12-07 05:13:44 +03:00
|
|
|
|
// TODO: if we decide to keep equals.....
|
|
|
|
|
|
|
|
|
|
// split on colon if there is a network location
|
|
|
|
|
colonIndex := strings.Index(arg, ":")
|
|
|
|
|
if colonIndex > 0 {
|
|
|
|
|
parts := strings.SplitN(arg, ":", 2)
|
|
|
|
|
arg = parts[0]
|
|
|
|
|
constraint.Ident.NetworkName = parts[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pr, err := sm.DeduceProjectRoot(arg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return constraint, errors.Wrapf(err, "could not infer project root from dependency path: %s", arg) // this should go through to the user
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if string(pr) != arg {
|
|
|
|
|
return constraint, errors.Wrapf(err, "dependency path %s is not a project root", arg)
|
|
|
|
|
}
|
|
|
|
|
constraint.Ident.ProjectRoot = gps.ProjectRoot(arg)
|
|
|
|
|
|
|
|
|
|
return constraint, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// deduceConstraint tries to puzzle out what kind of version is given in a string -
|
|
|
|
|
// semver, a revision, or as a fallback, a plain tag
|
|
|
|
|
func deduceConstraint(s string) gps.Constraint {
|
|
|
|
|
// always semver if we can
|
|
|
|
|
c, err := gps.NewSemverConstraint(s)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slen := len(s)
|
|
|
|
|
if slen == 40 {
|
|
|
|
|
if _, err = hex.DecodeString(s); err == nil {
|
|
|
|
|
// Whether or not it's intended to be a SHA1 digest, this is a
|
|
|
|
|
// valid byte sequence for that, so go with Revision. This
|
|
|
|
|
// covers git and hg
|
|
|
|
|
return gps.Revision(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Next, try for bzr, which has a three-component GUID separated by
|
|
|
|
|
// dashes. There should be two, but the email part could contain
|
|
|
|
|
// internal dashes
|
|
|
|
|
if strings.Count(s, "-") >= 2 {
|
|
|
|
|
// Work from the back to avoid potential confusion from the email
|
|
|
|
|
i3 := strings.LastIndex(s, "-")
|
|
|
|
|
// Skip if - is last char, otherwise this would panic on bounds err
|
|
|
|
|
if slen == i3+1 {
|
|
|
|
|
return gps.NewVersion(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err = hex.DecodeString(s[i3+1:]); err == nil {
|
|
|
|
|
i2 := strings.LastIndex(s[:i3], "-")
|
|
|
|
|
if _, err = strconv.ParseUint(s[i2+1:i3], 10, 64); err == nil {
|
|
|
|
|
// Getting this far means it'd pretty much be nuts if it's not a
|
|
|
|
|
// bzr rev, so don't bother parsing the email.
|
|
|
|
|
return gps.Revision(s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If not a plain SHA1 or bzr custom GUID, assume a plain version.
|
|
|
|
|
// TODO: if there is amgibuity here, then prompt the user?
|
|
|
|
|
return gps.NewVersion(s)
|
|
|
|
|
}
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
// copyFolder takes in a directory and copies it's contents to the destination.
|
|
|
|
|
// It preserves the file mode on files as well.
|
|
|
|
|
func copyFolder(src string, dest string) error {
|
|
|
|
|
fi, err := os.Lstat(src)
|
2016-12-09 22:27:39 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = os.MkdirAll(dest, fi.Mode())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
dir, err := os.Open(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer dir.Close()
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
objects, err := dir.Readdir(-1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
|
|
|
|
for _, obj := range objects {
|
|
|
|
|
if obj.Mode()&os.ModeSymlink != 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
srcfile := filepath.Join(src, obj.Name())
|
|
|
|
|
destfile := filepath.Join(dest, obj.Name())
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
|
|
|
|
if obj.IsDir() {
|
2016-12-13 00:17:45 +03:00
|
|
|
|
err = copyFolder(srcfile, destfile)
|
2016-12-09 22:27:39 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-12-13 00:17:45 +03:00
|
|
|
|
continue
|
2016-12-09 22:27:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
if err := copyFile(srcfile, destfile); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-12-09 22:27:39 +03:00
|
|
|
|
}
|
2016-12-13 00:17:45 +03:00
|
|
|
|
|
|
|
|
|
return nil
|
2016-12-09 22:27:39 +03:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
// copyFile copies a file from one place to another with the permission bits
|
|
|
|
|
// perserved as well.
|
|
|
|
|
func copyFile(src string, dest string) error {
|
|
|
|
|
srcfile, err := os.Open(src)
|
2016-12-09 22:27:39 +03:00
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-12-13 00:17:45 +03:00
|
|
|
|
defer srcfile.Close()
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
|
|
|
|
destfile, err := os.Create(dest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer destfile.Close()
|
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
if _, err := io.Copy(destfile, srcfile); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-12-09 22:27:39 +03:00
|
|
|
|
|
2016-12-13 00:17:45 +03:00
|
|
|
|
srcinfo, err := os.Stat(src)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2016-12-09 22:27:39 +03:00
|
|
|
|
}
|
2016-12-13 00:17:45 +03:00
|
|
|
|
|
|
|
|
|
return os.Chmod(dest, srcinfo.Mode())
|
2016-12-09 22:27:39 +03:00
|
|
|
|
}
|