github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/pipenv/pipenv.go (about) 1 package pipenv 2 3 import ( 4 "encoding/json" 5 6 "github.com/fossas/fossa-cli/errors" 7 "github.com/fossas/fossa-cli/exec" 8 "github.com/fossas/fossa-cli/graph" 9 "github.com/fossas/fossa-cli/pkg" 10 ) 11 12 // Pipenv defines the interface for all pipenv tool implementations. 13 type Pipenv interface { 14 Deps() (graph.Deps, error) 15 } 16 17 // Cmd implements Pipenv by parsing command output. 18 type Cmd struct { 19 Dir string 20 Graph func(dir string) (string, error) 21 } 22 23 // dependency is used to unmarshal the output from `pipenv graph --json-tree`. 24 type dependency struct { 25 Package string `json:"package_name"` 26 Resolved string `json:"installed_version"` 27 Target string `json:"required_version"` 28 Dependencies []dependency 29 } 30 31 // New constructs a Pipenv instance that calls the pipenv build tool. 32 func New(dirname string) Pipenv { 33 return Cmd{ 34 Dir: dirname, 35 Graph: GraphJSON, 36 } 37 } 38 39 // Deps returns the dependencies of a pipenv project. 40 func (p Cmd) Deps() (graph.Deps, error) { 41 depGraph := graph.Deps{} 42 rawJSON, err := p.Graph(p.Dir) 43 if err != nil { 44 return depGraph, err 45 } 46 47 deps, err := getDependencies(rawJSON) 48 if err != nil { 49 return depGraph, err 50 } 51 52 depGraph.Direct = getDirectDeps(deps) 53 depGraph.Transitive = getTransitiveDeps(deps) 54 return depGraph, nil 55 } 56 57 // GraphJSON returns the output from `pipenv graph --json-tree`. 58 func GraphJSON(dirname string) (string, error) { 59 out, _, err := exec.Run(exec.Cmd{ 60 Name: "pipenv", 61 Argv: []string{"graph", "--json-tree"}, 62 Dir: dirname, 63 }) 64 if err != nil { 65 err = errors.Wrap(err, "Could not run `pipenv graph --json-tree` within the current directory") 66 } 67 return out, err 68 } 69 70 func getDependencies(graphJSON string) ([]dependency, error) { 71 var depList []dependency 72 err := json.Unmarshal([]byte(graphJSON), &depList) 73 if err != nil { 74 return nil, errors.Wrap(err, "Could not unmarshal JSON into dependency list") 75 } 76 return depList, nil 77 } 78 79 func getDirectDeps(depList []dependency) []pkg.Import { 80 var imports []pkg.Import 81 for _, dep := range depList { 82 imports = append(imports, pkg.Import{ 83 Target: dep.Target, 84 Resolved: pkg.ID{ 85 Type: pkg.Python, 86 Name: dep.Package, 87 Revision: dep.Resolved, 88 }, 89 }) 90 } 91 return imports 92 } 93 94 func getTransitiveDeps(directDeps []dependency) map[pkg.ID]pkg.Package { 95 graph := make(map[pkg.ID]pkg.Package) 96 for _, dep := range directDeps { 97 id := pkg.ID{ 98 Type: pkg.Python, 99 Name: dep.Package, 100 Revision: dep.Resolved, 101 } 102 103 graph[id] = pkg.Package{ 104 ID: id, 105 Imports: packageImports(dep.Dependencies), 106 } 107 108 flattenDeepDependencies(graph, dep) 109 } 110 111 return graph 112 } 113 114 func flattenDeepDependencies(graph map[pkg.ID]pkg.Package, transDep dependency) { 115 for _, dep := range transDep.Dependencies { 116 id := pkg.ID{ 117 Type: pkg.Python, 118 Name: dep.Package, 119 Revision: dep.Resolved, 120 } 121 // Don't process duplicate transitive dependencies. 122 _, ok := graph[id] 123 if ok { 124 continue 125 } 126 127 graph[id] = pkg.Package{ 128 ID: id, 129 Imports: packageImports(dep.Dependencies), 130 } 131 flattenDeepDependencies(graph, dep) 132 } 133 } 134 135 func packageImports(packageDeps []dependency) []pkg.Import { 136 var imports []pkg.Import 137 for _, i := range packageDeps { 138 imports = append(imports, pkg.Import{ 139 Resolved: pkg.ID{ 140 Type: pkg.Python, 141 Name: i.Package, 142 Revision: i.Resolved, 143 }, 144 }) 145 } 146 return imports 147 }