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  }