| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "go/build" |
| "log" |
| "net/url" |
| "path/filepath" |
| "runtime" |
| "sort" |
| |
| "github.com/FiloSottile/gvt/gbvendor" |
| "github.com/constabulary/gb/fileutils" |
| ) |
| |
| var ( |
| branch string |
| revision string // revision (commit) |
| tag string |
| noRecurse bool |
| insecure bool // Allow the use of insecure protocols |
| |
| recurse bool // should we fetch recursively |
| ) |
| |
| func addFetchFlags(fs *flag.FlagSet) { |
| fs.StringVar(&branch, "branch", "", "branch of the package") |
| fs.StringVar(&revision, "revision", "", "revision of the package") |
| fs.StringVar(&tag, "tag", "", "tag of the package") |
| fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively") |
| fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols") |
| } |
| |
| var cmdFetch = &Command{ |
| Name: "fetch", |
| UsageLine: "fetch [-branch branch] [-revision rev | -tag tag] [-precaire] [-no-recurse] importpath", |
| Short: "fetch a remote dependency", |
| Long: `fetch vendors an upstream import path. |
| |
| The import path may include a url scheme. This may be useful when fetching dependencies |
| from private repositories that cannot be probed. |
| |
| Flags: |
| -branch branch |
| fetch from the named branch. Will also be used by gvt update. |
| If not supplied the default upstream branch will be used. |
| -no-recurse |
| do not fetch recursively. |
| -tag tag |
| fetch the specified tag. |
| -revision rev |
| fetch the specific revision from the branch or repository. |
| If no revision supplied, the latest available will be fetched. |
| -precaire |
| allow the use of insecure protocols. |
| |
| `, |
| Run: func(args []string) error { |
| switch len(args) { |
| case 0: |
| return fmt.Errorf("fetch: import path missing") |
| case 1: |
| path := args[0] |
| recurse = !noRecurse |
| return fetch(path, recurse) |
| default: |
| return fmt.Errorf("more than one import path supplied") |
| } |
| }, |
| AddFlags: addFetchFlags, |
| } |
| |
| func fetch(path string, recurse bool) error { |
| m, err := vendor.ReadManifest(manifestFile()) |
| if err != nil { |
| return fmt.Errorf("could not load manifest: %v", err) |
| } |
| |
| repo, extra, err := vendor.DeduceRemoteRepo(path, insecure) |
| if err != nil { |
| return err |
| } |
| |
| // strip of any scheme portion from the path, it is already |
| // encoded in the repo. |
| path = stripscheme(path) |
| |
| if m.HasImportpath(path) { |
| return fmt.Errorf("%s is already vendored", path) |
| } |
| |
| wc, err := repo.Checkout(branch, tag, revision) |
| |
| if err != nil { |
| return err |
| } |
| |
| rev, err := wc.Revision() |
| if err != nil { |
| return err |
| } |
| |
| branch, err := wc.Branch() |
| if err != nil { |
| return err |
| } |
| |
| dep := vendor.Dependency{ |
| Importpath: path, |
| Repository: repo.URL(), |
| Revision: rev, |
| Branch: branch, |
| Path: extra, |
| } |
| |
| if err := m.AddDependency(dep); err != nil { |
| return err |
| } |
| |
| dst := filepath.Join(vendorDir(), dep.Importpath) |
| src := filepath.Join(wc.Dir(), dep.Path) |
| |
| if err := fileutils.Copypath(dst, src); err != nil { |
| return err |
| } |
| |
| if err := vendor.WriteManifest(manifestFile(), m); err != nil { |
| return err |
| } |
| |
| if err := wc.Destroy(); err != nil { |
| return err |
| } |
| |
| if !recurse { |
| return nil |
| } |
| |
| // if we are recursing, overwrite branch, tag and revision |
| // values so recursive fetching checks out from HEAD. |
| branch = "" |
| tag = "" |
| revision = "" |
| |
| for done := false; !done; { |
| |
| paths := []struct { |
| Root, Prefix string |
| }{ |
| {filepath.Join(runtime.GOROOT(), "src"), ""}, |
| } |
| m, err := vendor.ReadManifest(manifestFile()) |
| if err != nil { |
| return err |
| } |
| for _, d := range m.Dependencies { |
| paths = append(paths, struct{ Root, Prefix string }{filepath.Join(vendorDir(), filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)}) |
| } |
| |
| dsm, err := vendor.LoadPaths(paths...) |
| if err != nil { |
| return err |
| } |
| |
| is, ok := dsm[filepath.Join(vendorDir(), path)] |
| if !ok { |
| return fmt.Errorf("unable to locate depset for %q", path) |
| } |
| |
| missing := findMissing(pkgs(is.Pkgs), dsm) |
| switch len(missing) { |
| case 0: |
| done = true |
| default: |
| |
| // sort keys in ascending order, so the shortest missing import path |
| // with be fetched first. |
| keys := keys(missing) |
| sort.Strings(keys) |
| pkg := keys[0] |
| log.Printf("fetching recursive dependency %s", pkg) |
| if err := fetch(pkg, false); err != nil { |
| return err |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func keys(m map[string]bool) []string { |
| var s []string |
| for k := range m { |
| s = append(s, k) |
| } |
| return s |
| } |
| |
| func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg { |
| var p []*vendor.Pkg |
| for _, v := range m { |
| p = append(p, v) |
| } |
| return p |
| } |
| |
| func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool { |
| missing := make(map[string]bool) |
| imports := make(map[string]*vendor.Pkg) |
| for _, s := range dsm { |
| for _, p := range s.Pkgs { |
| imports[p.ImportPath] = p |
| } |
| } |
| |
| // make fake C package for cgo |
| imports["C"] = &vendor.Pkg{ |
| Depset: nil, // probably a bad idea |
| Package: &build.Package{ |
| Name: "C", |
| }, |
| } |
| stk := make(map[string]bool) |
| push := func(v string) { |
| if stk[v] { |
| panic(fmt.Sprintln("import loop:", v, stk)) |
| } |
| stk[v] = true |
| } |
| pop := func(v string) { |
| if !stk[v] { |
| panic(fmt.Sprintln("impossible pop:", v, stk)) |
| } |
| delete(stk, v) |
| } |
| |
| // checked records import paths who's dependencies are all present |
| checked := make(map[string]bool) |
| |
| var fn func(string) |
| fn = func(importpath string) { |
| p, ok := imports[importpath] |
| if !ok { |
| missing[importpath] = true |
| return |
| } |
| |
| // have we already walked this arm, if so, skip it |
| if checked[importpath] { |
| return |
| } |
| |
| sz := len(missing) |
| push(importpath) |
| for _, i := range p.Imports { |
| if i == importpath { |
| continue |
| } |
| fn(i) |
| } |
| |
| // if the size of the missing map has not changed |
| // this entire subtree is complete, mark it as such |
| if len(missing) == sz { |
| checked[importpath] = true |
| } |
| pop(importpath) |
| } |
| for _, pkg := range pkgs { |
| fn(pkg.ImportPath) |
| } |
| return missing |
| } |
| |
| // stripscheme removes any scheme components from url like paths. |
| func stripscheme(path string) string { |
| u, err := url.Parse(path) |
| if err != nil { |
| panic(err) |
| } |
| return u.Host + u.Path |
| } |