github.com/sc0rp1us/gb@v0.4.1-0.20160319180011-4ba8cf1baa5a/cmd/gb/depset.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"go/build"
     6  	"path/filepath"
     7  	"runtime"
     8  
     9  	"github.com/constabulary/gb"
    10  	"github.com/constabulary/gb/cmd"
    11  	"github.com/constabulary/gb/vendor"
    12  )
    13  
    14  func init() {
    15  	registerCommand(&cmd.Command{
    16  		Name: "depset",
    17  		Run:  depset,
    18  	})
    19  }
    20  
    21  func depset(ctx *gb.Context, args []string) error {
    22  	paths := []struct {
    23  		Root, Prefix string
    24  	}{
    25  		{filepath.Join(runtime.GOROOT(), "src"), ""},
    26  		{filepath.Join(ctx.Projectdir(), "src"), ""},
    27  	}
    28  	m, err := vendor.ReadManifest(filepath.Join("vendor", "manifest"))
    29  	if err != nil {
    30  		return err
    31  	}
    32  	for _, d := range m.Dependencies {
    33  		paths = append(paths, struct{ Root, Prefix string }{filepath.Join(ctx.Projectdir(), "vendor", "src", filepath.FromSlash(d.Importpath)), filepath.FromSlash(d.Importpath)})
    34  	}
    35  
    36  	dsm, err := vendor.LoadPaths(paths...)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	for _, set := range dsm {
    41  		fmt.Printf("%s (%s)\n", set.Root, set.Prefix)
    42  		for _, p := range set.Pkgs {
    43  			fmt.Printf("\t%s (%s)\n", p.ImportPath, p.Name)
    44  			fmt.Printf("\t\timports: %s\n", p.Imports)
    45  		}
    46  	}
    47  
    48  	root := paths[1] // $PROJECT/src
    49  	rs := dsm[root.Root].Pkgs
    50  
    51  	fmt.Println("missing:")
    52  	for missing := range findMissing(pkgs(rs), dsm) {
    53  		fmt.Printf("\t%s\n", missing)
    54  	}
    55  
    56  	fmt.Println("orphaned:")
    57  	for orphan := range findOrphaned(pkgs(rs), dsm) {
    58  		fmt.Printf("\t%s\n", orphan)
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  func keys(m map[string]bool) []string {
    65  	var s []string
    66  	for k := range m {
    67  		s = append(s, k)
    68  	}
    69  	return s
    70  }
    71  
    72  func pkgs(m map[string]*vendor.Pkg) []*vendor.Pkg {
    73  	var p []*vendor.Pkg
    74  	for _, v := range m {
    75  		p = append(p, v)
    76  	}
    77  	return p
    78  }
    79  
    80  func findMissing(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool {
    81  	missing := make(map[string]bool)
    82  	imports := make(map[string]*vendor.Pkg)
    83  	for _, s := range dsm {
    84  		for _, p := range s.Pkgs {
    85  			imports[p.ImportPath] = p
    86  		}
    87  	}
    88  
    89  	// make fake C package for cgo
    90  	imports["C"] = &vendor.Pkg{
    91  		Depset: nil, // probably a bad idea
    92  		Package: &build.Package{
    93  			Name: "C",
    94  		},
    95  	}
    96  	stk := make(map[string]bool)
    97  	push := func(v string) {
    98  		if stk[v] {
    99  			panic(fmt.Sprintln("import loop:", v, stk))
   100  		}
   101  		stk[v] = true
   102  	}
   103  	pop := func(v string) {
   104  		if !stk[v] {
   105  			panic(fmt.Sprintln("impossible pop:", v, stk))
   106  		}
   107  		delete(stk, v)
   108  	}
   109  
   110  	// checked records import paths who's dependencies are all present
   111  	checked := make(map[string]bool)
   112  
   113  	var fn func(string)
   114  	fn = func(importpath string) {
   115  		p, ok := imports[importpath]
   116  		if !ok {
   117  			missing[importpath] = true
   118  			return
   119  		}
   120  
   121  		// have we already walked this arm, if so, skip it
   122  		if checked[importpath] {
   123  			return
   124  		}
   125  
   126  		sz := len(missing)
   127  		push(importpath)
   128  		for _, i := range p.Imports {
   129  			if i == importpath {
   130  				continue
   131  			}
   132  			fn(i)
   133  		}
   134  
   135  		// if the size of the missing map has not changed
   136  		// this entire subtree is complete, mark it as such
   137  		if len(missing) == sz {
   138  			checked[importpath] = true
   139  		}
   140  		pop(importpath)
   141  	}
   142  	for _, pkg := range pkgs {
   143  		fn(pkg.ImportPath)
   144  	}
   145  	return missing
   146  }
   147  
   148  func findOrphaned(pkgs []*vendor.Pkg, dsm map[string]*vendor.Depset) map[string]bool {
   149  	missing := make(map[string]bool)
   150  	imports := make(map[string]*vendor.Pkg)
   151  	for _, s := range dsm {
   152  		for _, p := range s.Pkgs {
   153  			imports[p.ImportPath] = p
   154  		}
   155  	}
   156  
   157  	orphans := make(map[string]bool)
   158  	for k := range dsm {
   159  		orphans[k] = true
   160  	}
   161  
   162  	// make fake C package for cgo
   163  	imports["C"] = &vendor.Pkg{
   164  		Depset: new(vendor.Depset),
   165  		Package: &build.Package{
   166  			Name: "C",
   167  		},
   168  	}
   169  	stk := make(map[string]bool)
   170  	push := func(v string) {
   171  		if stk[v] {
   172  			panic(fmt.Sprintln("import loop:", v, stk))
   173  		}
   174  		stk[v] = true
   175  	}
   176  	pop := func(v string) {
   177  		if !stk[v] {
   178  			panic(fmt.Sprintln("impossible pop:", v, stk))
   179  		}
   180  		delete(stk, v)
   181  	}
   182  
   183  	// checked records import paths who's dependencies are all present
   184  	checked := make(map[string]bool)
   185  
   186  	var fn func(string)
   187  	fn = func(importpath string) {
   188  		p, ok := imports[importpath]
   189  		if !ok {
   190  			missing[importpath] = true
   191  			return
   192  		}
   193  
   194  		// delete the pkg's depset, as it is referenced
   195  		delete(orphans, p.Depset.Root)
   196  
   197  		// have we already walked this arm, if so, skip it
   198  		if checked[importpath] {
   199  			return
   200  		}
   201  
   202  		sz := len(missing)
   203  		push(importpath)
   204  		for _, i := range p.Imports {
   205  			if i == importpath {
   206  				continue
   207  			}
   208  			fn(i)
   209  		}
   210  
   211  		// if the size of the missing map has not changed
   212  		// this entire subtree is complete, mark it as such
   213  		if len(missing) == sz {
   214  			checked[importpath] = true
   215  		}
   216  		pop(importpath)
   217  	}
   218  	for _, pkg := range pkgs {
   219  		fn(pkg.ImportPath)
   220  	}
   221  	return orphans
   222  }