github.com/rogpeppe/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  }