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