github.com/matthewbelisle-wf/godep@v0.0.0-20140716191328-dba190f14fc8/dep.go (about) 1 package main 2 3 import ( 4 "code.google.com/p/go.tools/go/vcs" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "runtime" 14 "sort" 15 "strings" 16 ) 17 18 // Godeps describes what a package needs to be rebuilt reproducibly. 19 // It's the same information stored in file Godeps. 20 type Godeps struct { 21 ImportPath string 22 GoVersion string 23 Packages []string `json:",omitempty"` // Arguments to save, if any. 24 Deps []Dependency 25 26 outerRoot string 27 } 28 29 // A Dependency is a specific revision of a package. 30 type Dependency struct { 31 ImportPath string 32 Comment string `json:",omitempty"` // Description of commit, if present. 33 Rev string // VCS-specific commit ID. 34 35 // used by command save & update 36 ws string // workspace 37 root string // import path to repo root 38 dir string // full path to package 39 40 // used by command update 41 matched bool // selected for update by command line 42 pkg *Package 43 44 // used by command go 45 outerRoot string // dir, if present, in outer GOPATH 46 repoRoot *vcs.RepoRoot 47 vcs *VCS 48 } 49 50 // pkgs is the list of packages to read dependencies 51 func (g *Godeps) Load(pkgs []*Package) error { 52 var err1 error 53 var path, seen []string 54 for _, p := range pkgs { 55 if p.Standard { 56 log.Println("ignoring stdlib package:", p.ImportPath) 57 continue 58 } 59 if p.Error.Err != "" { 60 log.Println(p.Error.Err) 61 err1 = errors.New("error loading packages") 62 continue 63 } 64 _, reporoot, err := VCSFromDir(p.Dir, filepath.Join(p.Root, "src")) 65 if err != nil { 66 log.Println(err) 67 err1 = errors.New("error loading packages") 68 continue 69 } 70 seen = append(seen, filepath.ToSlash(reporoot)) 71 path = append(path, p.Deps...) 72 } 73 var testImports []string 74 for _, p := range pkgs { 75 testImports = append(testImports, p.TestImports...) 76 testImports = append(testImports, p.XTestImports...) 77 } 78 ps, err := LoadPackages(testImports...) 79 if err != nil { 80 return err 81 } 82 for _, p := range ps { 83 if p.Standard { 84 continue 85 } 86 if p.Error.Err != "" { 87 log.Println(p.Error.Err) 88 err1 = errors.New("error loading packages") 89 continue 90 } 91 path = append(path, p.ImportPath) 92 path = append(path, p.Deps...) 93 } 94 sort.Strings(path) 95 path = uniq(path) 96 97 // Packages using 'godep save -r' contain rewritten 98 // import statements that fool go list into omitting 99 // further dependencies. In that case, the Godeps 100 // manifest has the full list. 101 for _, s := range path { 102 deps, err := readGodepsForImportPath(s) 103 if err != nil { 104 log.Println(err) 105 err1 = errors.New("error loading packages") 106 continue 107 } 108 for _, dep := range deps { 109 path = append(path, dep.ImportPath) 110 } 111 } 112 if err1 != nil { 113 return err1 114 } 115 sort.Strings(path) 116 path = uniq(path) 117 ps, err = LoadPackages(path...) 118 if err != nil { 119 return err 120 } 121 for _, pkg := range ps { 122 if pkg.Error.Err != "" { 123 if strings.HasPrefix(pkg.ImportPath, "appengine") { 124 continue 125 } 126 log.Println(pkg.Error.Err) 127 err1 = errors.New("error loading dependencies") 128 continue 129 } 130 if pkg.Standard { 131 continue 132 } 133 vcs, reporoot, err := VCSFromDir(pkg.Dir, filepath.Join(pkg.Root, "src")) 134 if err != nil { 135 log.Println(err) 136 err1 = errors.New("error loading dependencies") 137 continue 138 } 139 if containsPathPrefix(seen, pkg.ImportPath) { 140 continue 141 } 142 seen = append(seen, pkg.ImportPath) 143 id, err := vcs.identify(pkg.Dir) 144 if err != nil { 145 log.Println(err) 146 err1 = errors.New("error loading dependencies") 147 continue 148 } 149 if vcs.isDirty(pkg.Dir, id) { 150 log.Println("dirty working tree:", pkg.Dir) 151 err1 = errors.New("error loading dependencies") 152 continue 153 } 154 comment := vcs.describe(pkg.Dir, id) 155 g.Deps = append(g.Deps, Dependency{ 156 ImportPath: pkg.ImportPath, 157 Rev: id, 158 Comment: comment, 159 dir: pkg.Dir, 160 ws: pkg.Root, 161 root: filepath.ToSlash(reporoot), 162 vcs: vcs, 163 }) 164 } 165 return err1 166 } 167 168 func ReadGodeps(path string, g *Godeps) error { 169 f, err := os.Open(path) 170 if err != nil { 171 return err 172 } 173 return json.NewDecoder(f).Decode(g) 174 } 175 176 func ReadAndLoadGodeps(path string) (*Godeps, error) { 177 g := new(Godeps) 178 err := ReadGodeps(path, g) 179 if err != nil { 180 return nil, err 181 } 182 err = g.loadGoList() 183 if err != nil { 184 return nil, err 185 } 186 187 for i := range g.Deps { 188 d := &g.Deps[i] 189 d.vcs, d.repoRoot, err = VCSForImportPath(d.ImportPath) 190 if err != nil { 191 return nil, err 192 } 193 } 194 return g, nil 195 } 196 197 func (g *Godeps) loadGoList() error { 198 a := []string{g.ImportPath} 199 for _, d := range g.Deps { 200 a = append(a, d.ImportPath) 201 } 202 ps, err := LoadPackages(a...) 203 if err != nil { 204 return err 205 } 206 g.outerRoot = ps[0].Root 207 for i, p := range ps[1:] { 208 g.Deps[i].outerRoot = p.Root 209 } 210 return nil 211 } 212 213 func (g *Godeps) WriteTo(w io.Writer) (int64, error) { 214 b, err := json.MarshalIndent(g, "", "\t") 215 if err != nil { 216 return 0, err 217 } 218 n, err := w.Write(append(b, '\n')) 219 return int64(n), err 220 } 221 222 // readGodepsForImportPath loads the list of dependency packages from 223 // the godeps JSON manifest for importPath. It returns a list 224 // of import paths for the dependencies. 225 func readGodepsForImportPath(importPath string) (deps []Dependency, err error) { 226 for _, root := range filepath.SplitList(os.Getenv("GOPATH")) { 227 dir := filepath.Join(root, "src", filepath.FromSlash(importPath)) 228 loc, isDir := findInParents(dir, "Godeps") 229 if loc != "" && isDir { 230 var g Godeps 231 err = ReadGodeps(filepath.Join(loc, "Godeps", "Godeps.json"), &g) 232 if err != nil { 233 return nil, err 234 } 235 return g.Deps, nil 236 } 237 } 238 return nil, nil 239 } 240 241 // Returns a path to the local copy of d's repository. 242 // E.g. 243 // 244 // ImportPath RepoPath 245 // github.com/kr/s3 $spool/github.com/kr/s3 246 // github.com/lib/pq/oid $spool/github.com/lib/pq 247 func (d Dependency) RepoPath() string { 248 return filepath.Join(spool, "repo", d.repoRoot.Root) 249 } 250 251 // Returns a URL for the remote copy of the repository. 252 func (d Dependency) RemoteURL() string { 253 return d.repoRoot.Repo 254 } 255 256 // Returns the url of a local disk clone of the repo, if any. 257 func (d Dependency) FastRemotePath() string { 258 if d.outerRoot != "" { 259 return d.outerRoot + "/src/" + d.repoRoot.Root 260 } 261 return "" 262 } 263 264 // Returns a path to the checked-out copy of d's commit. 265 func (d Dependency) Workdir() string { 266 return filepath.Join(d.Gopath(), "src", d.ImportPath) 267 } 268 269 // Returns a path to the checked-out copy of d's repo root. 270 func (d Dependency) WorkdirRoot() string { 271 return filepath.Join(d.Gopath(), "src", d.repoRoot.Root) 272 } 273 274 // Returns a path to a parent of Workdir such that using 275 // Gopath in GOPATH makes d available to the go tool. 276 func (d Dependency) Gopath() string { 277 return filepath.Join(spool, "rev", d.Rev[:2], d.Rev[2:]) 278 } 279 280 // Creates an empty repo in d.RepoPath(). 281 func (d Dependency) CreateRepo(fastRemote, mainRemote string) error { 282 if err := os.MkdirAll(d.RepoPath(), 0777); err != nil { 283 return err 284 } 285 if err := d.vcs.create(d.RepoPath()); err != nil { 286 return err 287 } 288 if err := d.link(fastRemote, d.FastRemotePath()); err != nil { 289 return err 290 } 291 return d.link(mainRemote, d.RemoteURL()) 292 } 293 294 func (d Dependency) link(remote, url string) error { 295 return d.vcs.link(d.RepoPath(), remote, url) 296 } 297 298 func (d Dependency) fetchAndCheckout(remote string) error { 299 if err := d.fetch(remote); err != nil { 300 return fmt.Errorf("fetch: %s", err) 301 } 302 if err := d.checkout(); err != nil { 303 return fmt.Errorf("checkout: %s", err) 304 } 305 return nil 306 } 307 308 func (d Dependency) fetch(remote string) error { 309 return d.vcs.fetch(d.RepoPath(), remote) 310 } 311 312 func (d Dependency) checkout() error { 313 dir := d.WorkdirRoot() 314 if exists(dir) { 315 return nil 316 } 317 if !d.vcs.exists(d.RepoPath(), d.Rev) { 318 return fmt.Errorf("unknown rev %s for %s", d.Rev, d.ImportPath) 319 } 320 if err := os.MkdirAll(dir, 0777); err != nil { 321 return err 322 } 323 return d.vcs.checkout(dir, d.Rev, d.RepoPath()) 324 } 325 326 // containsPathPrefix returns whether any string in a 327 // is s or a directory containing s. 328 // For example, pattern ["a"] matches "a" and "a/b" 329 // (but not "ab"). 330 func containsPathPrefix(pats []string, s string) bool { 331 for _, pat := range pats { 332 if pat == s || strings.HasPrefix(s, pat+"/") { 333 return true 334 } 335 } 336 return false 337 } 338 339 func uniq(a []string) []string { 340 i := 0 341 s := "" 342 for _, t := range a { 343 if t != s { 344 a[i] = t 345 i++ 346 s = t 347 } 348 } 349 return a[:i] 350 } 351 352 // goVersion returns the version string of the Go compiler 353 // currently installed, e.g. "go1.1rc3". 354 func goVersion() (string, error) { 355 // Godep might have been compiled with a different 356 // version, so we can't just use runtime.Version here. 357 cmd := exec.Command("go", "version") 358 cmd.Stderr = os.Stderr 359 out, err := cmd.Output() 360 if err != nil { 361 return "", err 362 } 363 s := strings.TrimSpace(string(out)) 364 s = strings.TrimSuffix(s, " "+runtime.GOOS+"/"+runtime.GOARCH) 365 s = strings.TrimPrefix(s, "go version ") 366 return s, nil 367 }