github.com/elubow/godep@v0.0.0-20140525002653-983ff9241cea/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 log.Println(pkg.Error.Err) 124 err1 = errors.New("error loading dependencies") 125 continue 126 } 127 if pkg.Standard { 128 continue 129 } 130 vcs, reporoot, err := VCSFromDir(pkg.Dir, filepath.Join(pkg.Root, "src")) 131 if err != nil { 132 log.Println(err) 133 err1 = errors.New("error loading dependencies") 134 continue 135 } 136 if containsPathPrefix(seen, pkg.ImportPath) { 137 continue 138 } 139 seen = append(seen, pkg.ImportPath) 140 id, err := vcs.identify(pkg.Dir) 141 if err != nil { 142 log.Println(err) 143 err1 = errors.New("error loading dependencies") 144 continue 145 } 146 if vcs.isDirty(pkg.Dir, id) { 147 log.Println("dirty working tree:", pkg.Dir) 148 err1 = errors.New("error loading dependencies") 149 continue 150 } 151 comment := vcs.describe(pkg.Dir, id) 152 g.Deps = append(g.Deps, Dependency{ 153 ImportPath: pkg.ImportPath, 154 Rev: id, 155 Comment: comment, 156 dir: pkg.Dir, 157 ws: pkg.Root, 158 root: filepath.ToSlash(reporoot), 159 vcs: vcs, 160 }) 161 } 162 return err1 163 } 164 165 func ReadGodeps(path string, g *Godeps) error { 166 f, err := os.Open(path) 167 if err != nil { 168 return err 169 } 170 return json.NewDecoder(f).Decode(g) 171 } 172 173 func ReadAndLoadGodeps(path string) (*Godeps, error) { 174 g := new(Godeps) 175 err := ReadGodeps(path, g) 176 if err != nil { 177 return nil, err 178 } 179 err = g.loadGoList() 180 if err != nil { 181 return nil, err 182 } 183 184 for i := range g.Deps { 185 d := &g.Deps[i] 186 d.vcs, d.repoRoot, err = VCSForImportPath(d.ImportPath) 187 if err != nil { 188 return nil, err 189 } 190 } 191 return g, nil 192 } 193 194 func (g *Godeps) loadGoList() error { 195 a := []string{g.ImportPath} 196 for _, d := range g.Deps { 197 a = append(a, d.ImportPath) 198 } 199 ps, err := LoadPackages(a...) 200 if err != nil { 201 return err 202 } 203 g.outerRoot = ps[0].Root 204 for i, p := range ps[1:] { 205 g.Deps[i].outerRoot = p.Root 206 } 207 return nil 208 } 209 210 func (g *Godeps) WriteTo(w io.Writer) (int64, error) { 211 b, err := json.MarshalIndent(g, "", "\t") 212 if err != nil { 213 return 0, err 214 } 215 n, err := w.Write(append(b, '\n')) 216 return int64(n), err 217 } 218 219 // readGodepsForImportPath loads the list of dependency packages from 220 // the godeps JSON manifest for importPath. It returns a list 221 // of import paths for the dependencies. 222 func readGodepsForImportPath(importPath string) (deps []Dependency, err error) { 223 for _, root := range filepath.SplitList(os.Getenv("GOPATH")) { 224 dir := filepath.Join(root, "src", filepath.FromSlash(importPath)) 225 loc, isDir := findInParents(dir, "Godeps") 226 if loc != "" && isDir { 227 var g Godeps 228 err = ReadGodeps(filepath.Join(loc, "Godeps", "Godeps.json"), &g) 229 if err != nil { 230 return nil, err 231 } 232 return g.Deps, nil 233 } 234 } 235 return nil, nil 236 } 237 238 // Returns a path to the local copy of d's repository. 239 // E.g. 240 // 241 // ImportPath RepoPath 242 // github.com/kr/s3 $spool/github.com/kr/s3 243 // github.com/lib/pq/oid $spool/github.com/lib/pq 244 func (d Dependency) RepoPath() string { 245 return filepath.Join(spool, "repo", d.repoRoot.Root) 246 } 247 248 // Returns a URL for the remote copy of the repository. 249 func (d Dependency) RemoteURL() string { 250 return d.repoRoot.Repo 251 } 252 253 // Returns the url of a local disk clone of the repo, if any. 254 func (d Dependency) FastRemotePath() string { 255 if d.outerRoot != "" { 256 return d.outerRoot + "/src/" + d.repoRoot.Root 257 } 258 return "" 259 } 260 261 // Returns a path to the checked-out copy of d's commit. 262 func (d Dependency) Workdir() string { 263 return filepath.Join(d.Gopath(), "src", d.ImportPath) 264 } 265 266 // Returns a path to the checked-out copy of d's repo root. 267 func (d Dependency) WorkdirRoot() string { 268 return filepath.Join(d.Gopath(), "src", d.repoRoot.Root) 269 } 270 271 // Returns a path to a parent of Workdir such that using 272 // Gopath in GOPATH makes d available to the go tool. 273 func (d Dependency) Gopath() string { 274 return filepath.Join(spool, "rev", d.Rev[:2], d.Rev[2:]) 275 } 276 277 // Creates an empty repo in d.RepoPath(). 278 func (d Dependency) CreateRepo(fastRemote, mainRemote string) error { 279 if err := os.MkdirAll(d.RepoPath(), 0777); err != nil { 280 return err 281 } 282 if err := d.vcs.create(d.RepoPath()); err != nil { 283 return err 284 } 285 if err := d.link(fastRemote, d.FastRemotePath()); err != nil { 286 return err 287 } 288 return d.link(mainRemote, d.RemoteURL()) 289 } 290 291 func (d Dependency) link(remote, url string) error { 292 return d.vcs.link(d.RepoPath(), remote, url) 293 } 294 295 func (d Dependency) fetchAndCheckout(remote string) error { 296 if err := d.fetch(remote); err != nil { 297 return fmt.Errorf("fetch: %s", err) 298 } 299 if err := d.checkout(); err != nil { 300 return fmt.Errorf("checkout: %s", err) 301 } 302 return nil 303 } 304 305 func (d Dependency) fetch(remote string) error { 306 return d.vcs.fetch(d.RepoPath(), remote) 307 } 308 309 func (d Dependency) checkout() error { 310 dir := d.WorkdirRoot() 311 if exists(dir) { 312 return nil 313 } 314 if !d.vcs.exists(d.RepoPath(), d.Rev) { 315 return fmt.Errorf("unknown rev %s for %s", d.Rev, d.ImportPath) 316 } 317 if err := os.MkdirAll(dir, 0777); err != nil { 318 return err 319 } 320 return d.vcs.checkout(dir, d.Rev, d.RepoPath()) 321 } 322 323 // containsPathPrefix returns whether any string in a 324 // is s or a directory containing s. 325 // For example, pattern ["a"] matches "a" and "a/b" 326 // (but not "ab"). 327 func containsPathPrefix(pats []string, s string) bool { 328 for _, pat := range pats { 329 if pat == s || strings.HasPrefix(s, pat+"/") { 330 return true 331 } 332 } 333 return false 334 } 335 336 func uniq(a []string) []string { 337 i := 0 338 s := "" 339 for _, t := range a { 340 if t != s { 341 a[i] = t 342 i++ 343 s = t 344 } 345 } 346 return a[:i] 347 } 348 349 // goVersion returns the version string of the Go compiler 350 // currently installed, e.g. "go1.1rc3". 351 func goVersion() (string, error) { 352 // Godep might have been compiled with a different 353 // version, so we can't just use runtime.Version here. 354 cmd := exec.Command("go", "version") 355 cmd.Stderr = os.Stderr 356 out, err := cmd.Output() 357 if err != nil { 358 return "", err 359 } 360 s := strings.TrimSpace(string(out)) 361 s = strings.TrimSuffix(s, " "+runtime.GOOS+"/"+runtime.GOARCH) 362 s = strings.TrimPrefix(s, "go version ") 363 return s, nil 364 }