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  }