github.com/aca02djr/gb@v0.4.1/cmd/gb-vendor/fetch.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"net/url"
     7  	"path/filepath"
     8  	"runtime"
     9  	"sort"
    10  
    11  	"go/build"
    12  
    13  	"github.com/constabulary/gb"
    14  	"github.com/constabulary/gb/cmd"
    15  	"github.com/constabulary/gb/fileutils"
    16  	"github.com/constabulary/gb/vendor"
    17  )
    18  
    19  var (
    20  	// gb vendor fetch command flags
    21  
    22  	branch string
    23  
    24  	// revision (commit)
    25  	revision string
    26  
    27  	tag string
    28  
    29  	noRecurse bool // Container variable to house the value of the no-recurse flag.
    30  
    31  	recurse  bool // should we fetch recursively
    32  	insecure bool // Allow the use of insecure protocols
    33  )
    34  
    35  func addFetchFlags(fs *flag.FlagSet) {
    36  	fs.StringVar(&branch, "branch", "", "branch of the package")
    37  	fs.StringVar(&revision, "revision", "", "revision of the package")
    38  	fs.StringVar(&tag, "tag", "", "tag of the package")
    39  	fs.BoolVar(&noRecurse, "no-recurse", false, "do not fetch recursively")
    40  	fs.BoolVar(&insecure, "precaire", false, "allow the use of insecure protocols")
    41  }
    42  
    43  var cmdFetch = &cmd.Command{
    44  	Name:      "fetch",
    45  	UsageLine: "fetch [-branch branch | -revision rev | -tag tag] [-precaire] [-no-recurse] importpath",
    46  	Short:     "fetch a remote dependency",
    47  	Long: `fetch vendors an upstream import path.
    48  
    49  The import path may include a url scheme. This may be useful when fetching dependencies
    50  from private repositories that cannot be probed.
    51  
    52  Flags:
    53  	-branch branch
    54  		fetch from the name branch. If not supplied the default upstream
    55  		branch will be used.
    56  	-no-recurse
    57  		do not fetch recursively.
    58  	-tag tag
    59  		fetch the specified tag. If not supplied the default upstream
    60  		branch will be used.
    61  	-revision rev
    62  		fetch the specific revision from the branch (if supplied). If no
    63  		revision supplied, the latest available will be supplied.
    64  	-precaire
    65  		allow the use of insecure protocols.
    66  
    67  `,
    68  	Run: func(ctx *gb.Context, args []string) error {
    69  		switch len(args) {
    70  		case 0:
    71  			return fmt.Errorf("fetch: import path missing")
    72  		case 1:
    73  			path := args[0]
    74  			recurse = !noRecurse
    75  			return fetch(ctx, path, recurse)
    76  		default:
    77  			return fmt.Errorf("more than one import path supplied")
    78  		}
    79  	},
    80  	AddFlags: addFetchFlags,
    81  }
    82  
    83  func fetch(ctx *gb.Context, path string, recurse bool) error {
    84  	m, err := vendor.ReadManifest(manifestFile(ctx))
    85  	if err != nil {
    86  		return fmt.Errorf("could not load manifest: %v", err)
    87  	}
    88  
    89  	repo, extra, err := vendor.DeduceRemoteRepo(path, insecure)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	// strip of any scheme portion from the path, it is already
    95  	// encoded in the repo.
    96  	path = stripscheme(path)
    97  
    98  	if m.HasImportpath(path) {
    99  		return fmt.Errorf("%s is already vendored", path)
   100  	}
   101  
   102  	wc, err := repo.Checkout(branch, tag, revision)
   103  
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	rev, err := wc.Revision()
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	b, err := wc.Branch()
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	dep := vendor.Dependency{
   119  		Importpath: path,
   120  		Repository: repo.URL(),
   121  		Revision:   rev,
   122  		Branch:     b,
   123  		Path:       extra,
   124  	}
   125  
   126  	if err := m.AddDependency(dep); err != nil {
   127  		return err
   128  	}
   129  
   130  	dst := filepath.Join(ctx.Projectdir(), "vendor", "src", dep.Importpath)
   131  	src := filepath.Join(wc.Dir(), dep.Path)
   132  
   133  	if err := fileutils.Copypath(dst, src); err != nil {
   134  		return err
   135  	}
   136  
   137  	if err := vendor.WriteManifest(manifestFile(ctx), m); err != nil {
   138  		return err
   139  	}
   140  
   141  	if err := wc.Destroy(); err != nil {
   142  		return err
   143  	}
   144  
   145  	if !recurse {
   146  		return nil
   147  	}
   148  
   149  	// if we are recursing, overwrite branch, tag and revision
   150  	// values so recursive fetching checks out from HEAD.
   151  	branch = ""
   152  	tag = ""
   153  	revision = ""
   154  
   155  	for done := false; !done; {
   156  
   157  		paths := []struct {
   158  			Root, Prefix string
   159  		}{
   160  			{filepath.Join(runtime.GOROOT(), "src"), ""},
   161  			{filepath.Join(ctx.Projectdir(), "src"), ""},
   162  		}
   163  		m, err := vendor.ReadManifest(manifestFile(ctx))
   164  		if err != nil {
   165  			return err
   166  		}
   167  		for _, d := range m.Dependencies {
   168  			paths = append(paths, struct{ Root, Prefix string }{filepath.Join(ctx.Projectdir(), "vendor", "src", filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)})
   169  		}
   170  
   171  		dsm, err := vendor.LoadPaths(paths...)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		is, ok := dsm[filepath.Join(ctx.Projectdir(), "vendor", "src", path)]
   177  		if !ok {
   178  			return fmt.Errorf("unable to locate depset for %q", path)
   179  		}
   180  
   181  		missing := findMissing(pkgs(is.Pkgs), dsm)
   182  		switch len(missing) {
   183  		case 0:
   184  			done = true
   185  		default:
   186  
   187  			// sort keys in ascending order, so the shortest missing import path
   188  			// with be fetched first.
   189  			keys := keys(missing)
   190  			sort.Strings(keys)
   191  			pkg := keys[0]
   192  			fmt.Println("fetching recursive dependency", pkg)
   193  			if err := fetch(ctx, pkg, false); err != nil {
   194  				return err
   195  			}
   196  		}
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func keys(m map[string]bool) []string {
   203  	var s []string
   204  	for k := range m {
   205  		s = append(s, k)
   206  	}
   207  	return s
   208  }
   209  
   210  func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg {
   211  	var p []*vendor.Pkg
   212  	for _, v := range m {
   213  		p = append(p, v)
   214  	}
   215  	return p
   216  }
   217  
   218  func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool {
   219  	missing := make(map[string]bool)
   220  	imports := make(map[string]*vendor.Pkg)
   221  	for _, s := range dsm {
   222  		for _, p := range s.Pkgs {
   223  			imports[p.ImportPath] = p
   224  		}
   225  	}
   226  
   227  	// make fake C package for cgo
   228  	imports["C"] = &vendor.Pkg{
   229  		Depset: nil, // probably a bad idea
   230  		Package: &build.Package{
   231  			Name: "C",
   232  		},
   233  	}
   234  	stk := make(map[string]bool)
   235  	push := func(v string) {
   236  		if stk[v] {
   237  			panic(fmt.Sprintln("import loop:", v, stk))
   238  		}
   239  		stk[v] = true
   240  	}
   241  	pop := func(v string) {
   242  		if !stk[v] {
   243  			panic(fmt.Sprintln("impossible pop:", v, stk))
   244  		}
   245  		delete(stk, v)
   246  	}
   247  
   248  	// checked records import paths who's dependencies are all present
   249  	checked := make(map[string]bool)
   250  
   251  	var fn func(string)
   252  	fn = func(importpath string) {
   253  		p, ok := imports[importpath]
   254  		if !ok {
   255  			missing[importpath] = true
   256  			return
   257  		}
   258  
   259  		// have we already walked this arm, if so, skip it
   260  		if checked[importpath] {
   261  			return
   262  		}
   263  
   264  		sz := len(missing)
   265  		push(importpath)
   266  		for _, i := range p.Imports {
   267  			if i == importpath {
   268  				continue
   269  			}
   270  			fn(i)
   271  		}
   272  
   273  		// if the size of the missing map has not changed
   274  		// this entire subtree is complete, mark it as such
   275  		if len(missing) == sz {
   276  			checked[importpath] = true
   277  		}
   278  		pop(importpath)
   279  	}
   280  	for _, pkg := range pkgs {
   281  		fn(pkg.ImportPath)
   282  	}
   283  	return missing
   284  }
   285  
   286  // stripscheme removes any scheme components from url like paths.
   287  func stripscheme(path string) string {
   288  	u, err := url.Parse(path)
   289  	if err != nil {
   290  		panic(err)
   291  	}
   292  	return u.Host + u.Path
   293  }