github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/stack/stack.go (about) 1 package stack 2 3 import ( 4 "strconv" 5 "strings" 6 7 "github.com/fossas/fossa-cli/exec" 8 "github.com/fossas/fossa-cli/graph" 9 "github.com/fossas/fossa-cli/module" 10 "github.com/fossas/fossa-cli/pkg" 11 ) 12 13 // ----- Dep graph retrieval 14 15 func GetDeps(m module.Module) (graph.Deps, error) { 16 stackAllDeps, err := GetStackDependencies(m.Dir, 0) 17 if err != nil { 18 return graph.Deps{}, err 19 } 20 21 ghcPkgDeps, err := GetGhcPkgDepMap() 22 if err != nil { 23 return graph.Deps{}, err 24 } 25 26 stackImmediateDeps, err := GetStackDependencies(m.Dir, 1) 27 if err != nil { 28 return graph.Deps{}, err 29 } 30 31 return GetDepsPure(stackAllDeps, stackImmediateDeps, ghcPkgDeps), nil 32 } 33 34 func GetDepsPure(stackAllDeps []Dep, stackImmediateDeps []Dep, depMap GhcPkgDeps) graph.Deps { 35 // Our direct dependencies 36 var depGraph = make(map[pkg.ID]pkg.Package) 37 38 // Build out the full graph 39 for _, stackDep := range stackAllDeps { 40 pkgID := DepToPkgId(stackDep) 41 42 var imports []pkg.Import 43 44 for _, ghcPkgDep := range depMap[DepToCanonical(stackDep)] { 45 imports = append(imports, pkg.Import{ 46 Resolved: DepToPkgId(DepFromCanonical(ghcPkgDep)), 47 }) 48 } 49 50 depGraph[pkgID] = pkg.Package{ 51 ID: pkgID, 52 Imports: imports, 53 } 54 } 55 56 var directImports []pkg.Import 57 58 // Build our direct dependencies 59 for _, stackDep := range stackImmediateDeps { 60 pkgID := DepToPkgId(stackDep) 61 62 directImports = append(directImports, pkg.Import{ 63 Resolved: pkgID, 64 }) 65 } 66 67 return graph.Deps{ 68 Direct: directImports, 69 Transitive: depGraph, 70 } 71 } 72 73 // ----- Types 74 75 // A stack dependency 76 type Dep struct { 77 Name string 78 Version string 79 } 80 81 func DepToPkgId(dep Dep) pkg.ID { 82 return pkg.ID{ 83 Type: pkg.Haskell, 84 Name: dep.Name, 85 Revision: dep.Version, 86 } 87 } 88 89 // Canonical string representation of a ghc-pkg package of the form: 90 // package-name-0.0.1.0 91 type Canonical struct { 92 Identifier string 93 } 94 95 func DepToCanonical(dep Dep) Canonical { 96 return Canonical{Identifier: dep.Name + "-" + dep.Version} 97 } 98 99 func DepFromCanonical(canonical Canonical) Dep { 100 ix := strings.LastIndex(canonical.Identifier, "-") 101 return Dep{ 102 Name: canonical.Identifier[:ix], 103 Version: canonical.Identifier[ix+1:], 104 } 105 } 106 107 // A mapping of ghc-pkg packages to their dependencies 108 type GhcPkgDeps = map[Canonical][]Canonical 109 110 // ----- Command output parsing 111 112 func ParseStackDependencies(output string) []Dep { 113 // Stack ls dependencies outputs deps in the form: 114 // packageone 0.0.1.0 115 // packagetwo 0.0.1.0 116 // ... 117 118 var deps []Dep 119 120 for _, line := range strings.Split(output, "\n") { 121 var dep = strings.Split(line, " ") 122 123 if len(dep) < 2 { 124 continue 125 } 126 127 var name = dep[0] 128 var version = dep[1] 129 130 deps = append(deps, Dep{ 131 Name: name, 132 Version: version, 133 }) 134 } 135 136 return deps 137 } 138 139 func ParseGhcPkgDepMap(output string) GhcPkgDeps { 140 // ghc-pkg dot outputs deps in the form: 141 // digraph { 142 // "packageone-0.0.1.0" -> "packagetwo-0.0.1.0" 143 // "packageone-0.0.1.0" -> "packagethree-0.0.1.0" 144 // } 145 deps := make(GhcPkgDeps) 146 147 lines := strings.Split(output, "\n") 148 149 for _, line := range lines { 150 line = strings.ReplaceAll(line, "\"", "") 151 152 split := strings.Split(line, " -> ") 153 if len(split) < 2 { 154 continue // The first and last lines are "digraph {" and "}", so they won't have a dep 155 } 156 157 from := Canonical{Identifier: split[0]} 158 to := Canonical{Identifier: split[1]} 159 160 cur := deps[from] 161 cur = append(cur, to) 162 deps[from] = cur 163 } 164 165 return deps 166 } 167 168 // ----- Command invocation 169 170 func GetStackDependencies(dir string, depth int) ([]Dep, error) { 171 args := []string{"ls", "dependencies"} 172 173 if depth != 0 { 174 args = append(args, "--depth", strconv.Itoa(depth)) 175 } 176 177 stdout, _, err := exec.Run(exec.Cmd{ 178 Name: "stack", 179 Argv: args, 180 Dir: dir, 181 }) 182 if err != nil { 183 return nil, err 184 } 185 186 return ParseStackDependencies(stdout), nil 187 } 188 189 func GetGhcPkgDepMap() (GhcPkgDeps, error) { 190 stdout, _, err := exec.Run(exec.Cmd{ 191 Name: "stack", 192 Argv: []string{"exec", "--", "ghc-pkg", "dot"}, 193 }) 194 if err != nil { 195 return nil, err 196 } 197 198 return ParseGhcPkgDepMap(stdout), nil 199 }