github.com/aca02djr/gb@v0.4.1/cmd/gb-vendor/fetch.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "net/url" 7 "path/filepath" 8 "runtime" 9 "sort" 10 11 "go/build" 12 13 "github.com/constabulary/gb" 14 "github.com/constabulary/gb/cmd" 15 "github.com/constabulary/gb/fileutils" 16 "github.com/constabulary/gb/vendor" 17 ) 18 19 var ( 20 // gb vendor fetch command flags 21 22 branch string 23 24 // revision (commit) 25 revision string 26 27 tag string 28 29 noRecurse bool // Container variable to house the value of the no-recurse flag. 30 31 recurse bool // should we fetch recursively 32 insecure bool // Allow the use of insecure protocols 33 ) 34 35 func addFetchFlags(fs *flag.FlagSet) { 36 fs.StringVar(&branch, "branch", "", "branch of the package") 37 fs.StringVar(&revision, "revision", "", "revision of the package") 38 fs.StringVar(&tag, "tag", "", "tag of the package") 39 fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively") 40 fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols") 41 } 42 43 var cmdFetch = &cmd.Command{ 44 Name: "fetch", 45 UsageLine: "fetch [-branch branch | -revision rev | -tag tag] [-precaire] [-no-recurse] importpath", 46 Short: "fetch a remote dependency", 47 Long: `fetch vendors an upstream import path. 48 49 The import path may include a url scheme. This may be useful when fetching dependencies 50 from private repositories that cannot be probed. 51 52 Flags: 53 -branch branch 54 fetch from the name branch. If not supplied the default upstream 55 branch will be used. 56 -no-recurse 57 do not fetch recursively. 58 -tag tag 59 fetch the specified tag. If not supplied the default upstream 60 branch will be used. 61 -revision rev 62 fetch the specific revision from the branch (if supplied). If no 63 revision supplied, the latest available will be supplied. 64 -precaire 65 allow the use of insecure protocols. 66 67 `, 68 Run: func(ctx *gb.Context, args []string) error { 69 switch len(args) { 70 case 0: 71 return fmt.Errorf("fetch: import path missing") 72 case 1: 73 path := args[0] 74 recurse = !noRecurse 75 return fetch(ctx, path, recurse) 76 default: 77 return fmt.Errorf("more than one import path supplied") 78 } 79 }, 80 AddFlags: addFetchFlags, 81 } 82 83 func fetch(ctx *gb.Context, path string, recurse bool) error { 84 m, err := vendor.ReadManifest(manifestFile(ctx)) 85 if err != nil { 86 return fmt.Errorf("could not load manifest: %v", err) 87 } 88 89 repo, extra, err := vendor.DeduceRemoteRepo(path, insecure) 90 if err != nil { 91 return err 92 } 93 94 // strip of any scheme portion from the path, it is already 95 // encoded in the repo. 96 path = stripscheme(path) 97 98 if m.HasImportpath(path) { 99 return fmt.Errorf("%s is already vendored", path) 100 } 101 102 wc, err := repo.Checkout(branch, tag, revision) 103 104 if err != nil { 105 return err 106 } 107 108 rev, err := wc.Revision() 109 if err != nil { 110 return err 111 } 112 113 b, err := wc.Branch() 114 if err != nil { 115 return err 116 } 117 118 dep := vendor.Dependency{ 119 Importpath: path, 120 Repository: repo.URL(), 121 Revision: rev, 122 Branch: b, 123 Path: extra, 124 } 125 126 if err := m.AddDependency(dep); err != nil { 127 return err 128 } 129 130 dst := filepath.Join(ctx.Projectdir(), "vendor", "src", dep.Importpath) 131 src := filepath.Join(wc.Dir(), dep.Path) 132 133 if err := fileutils.Copypath(dst, src); err != nil { 134 return err 135 } 136 137 if err := vendor.WriteManifest(manifestFile(ctx), m); err != nil { 138 return err 139 } 140 141 if err := wc.Destroy(); err != nil { 142 return err 143 } 144 145 if !recurse { 146 return nil 147 } 148 149 // if we are recursing, overwrite branch, tag and revision 150 // values so recursive fetching checks out from HEAD. 151 branch = "" 152 tag = "" 153 revision = "" 154 155 for done := false; !done; { 156 157 paths := []struct { 158 Root, Prefix string 159 }{ 160 {filepath.Join(runtime.GOROOT(), "src"), ""}, 161 {filepath.Join(ctx.Projectdir(), "src"), ""}, 162 } 163 m, err := vendor.ReadManifest(manifestFile(ctx)) 164 if err != nil { 165 return err 166 } 167 for _, d := range m.Dependencies { 168 paths = append(paths, struct{ Root, Prefix string }{filepath.Join(ctx.Projectdir(), "vendor", "src", filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)}) 169 } 170 171 dsm, err := vendor.LoadPaths(paths...) 172 if err != nil { 173 return err 174 } 175 176 is, ok := dsm[filepath.Join(ctx.Projectdir(), "vendor", "src", path)] 177 if !ok { 178 return fmt.Errorf("unable to locate depset for %q", path) 179 } 180 181 missing := findMissing(pkgs(is.Pkgs), dsm) 182 switch len(missing) { 183 case 0: 184 done = true 185 default: 186 187 // sort keys in ascending order, so the shortest missing import path 188 // with be fetched first. 189 keys := keys(missing) 190 sort.Strings(keys) 191 pkg := keys[0] 192 fmt.Println("fetching recursive dependency", pkg) 193 if err := fetch(ctx, pkg, false); err != nil { 194 return err 195 } 196 } 197 } 198 199 return nil 200 } 201 202 func keys(m map[string]bool) []string { 203 var s []string 204 for k := range m { 205 s = append(s, k) 206 } 207 return s 208 } 209 210 func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg { 211 var p []*vendor.Pkg 212 for _, v := range m { 213 p = append(p, v) 214 } 215 return p 216 } 217 218 func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool { 219 missing := make(map[string]bool) 220 imports := make(map[string]*vendor.Pkg) 221 for _, s := range dsm { 222 for _, p := range s.Pkgs { 223 imports[p.ImportPath] = p 224 } 225 } 226 227 // make fake C package for cgo 228 imports["C"] = &vendor.Pkg{ 229 Depset: nil, // probably a bad idea 230 Package: &build.Package{ 231 Name: "C", 232 }, 233 } 234 stk := make(map[string]bool) 235 push := func(v string) { 236 if stk[v] { 237 panic(fmt.Sprintln("import loop:", v, stk)) 238 } 239 stk[v] = true 240 } 241 pop := func(v string) { 242 if !stk[v] { 243 panic(fmt.Sprintln("impossible pop:", v, stk)) 244 } 245 delete(stk, v) 246 } 247 248 // checked records import paths who's dependencies are all present 249 checked := make(map[string]bool) 250 251 var fn func(string) 252 fn = func(importpath string) { 253 p, ok := imports[importpath] 254 if !ok { 255 missing[importpath] = true 256 return 257 } 258 259 // have we already walked this arm, if so, skip it 260 if checked[importpath] { 261 return 262 } 263 264 sz := len(missing) 265 push(importpath) 266 for _, i := range p.Imports { 267 if i == importpath { 268 continue 269 } 270 fn(i) 271 } 272 273 // if the size of the missing map has not changed 274 // this entire subtree is complete, mark it as such 275 if len(missing) == sz { 276 checked[importpath] = true 277 } 278 pop(importpath) 279 } 280 for _, pkg := range pkgs { 281 fn(pkg.ImportPath) 282 } 283 return missing 284 } 285 286 // stripscheme removes any scheme components from url like paths. 287 func stripscheme(path string) string { 288 u, err := url.Parse(path) 289 if err != nil { 290 panic(err) 291 } 292 return u.Host + u.Path 293 }