github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/cabal/cabal.go (about) 1 package cabal 2 3 import ( 4 "path/filepath" 5 6 "github.com/mitchellh/mapstructure" 7 8 "github.com/fossas/fossa-cli/errors" 9 "github.com/fossas/fossa-cli/exec" 10 "github.com/fossas/fossa-cli/files" 11 "github.com/fossas/fossa-cli/graph" 12 "github.com/fossas/fossa-cli/module" 13 "github.com/fossas/fossa-cli/pkg" 14 ) 15 16 const planRelPath = "dist-newstyle/cache/plan.json" 17 18 // ----- Dep graph retrieval 19 20 func GetDeps(m module.Module) (graph.Deps, error) { 21 plan, err := getSolverPlan(m.Dir) 22 if err != nil { 23 return graph.Deps{}, err 24 } 25 26 return GetDepsPure(plan), nil 27 } 28 29 func GetDepsPure(plan Plan) graph.Deps { 30 // Index a list of install plans by their IDs 31 // this is used when building out the dependency graph 32 var installPlans = make(map[string]InstallPlan) 33 for _, p := range plan.InstallPlans { 34 installPlans[p.Id] = p 35 } 36 37 var dependencyGraph = make(map[pkg.ID]pkg.Package) 38 var directDependencies []pkg.Import 39 40 // Build the entire dependency graph, keeping track of our direct 41 // dependencies 42 for _, p := range plan.InstallPlans { 43 var builtPackage = installPlanToPackage(installPlans, p) 44 45 if isDirectDependency(p) { 46 directDependencies = append(directDependencies, builtPackage.Imports...) 47 } 48 49 dependencyGraph[builtPackage.ID] = builtPackage 50 } 51 52 return graph.Deps{ 53 Direct: directDependencies, 54 Transitive: dependencyGraph, 55 } 56 } 57 58 59 // ----- Types 60 61 type Plan struct { 62 InstallPlans []InstallPlan `mapstructure:"install-plan"` 63 } 64 65 type InstallPlan struct { 66 // There are two types of install plans: `pre-existing` and `configured`. 67 // `pre-existing` corresponds to system-level libraries, and `configured` 68 // is for globally-installed and project-local dependencies. To 69 // differentiate, the `style` field is used. 70 Type string `mapstructure:"type"` 71 Id string `mapstructure:"id"` 72 Name string `mapstructure:"pkg-name"` 73 Version string `mapstructure:"pkg-version"` 74 75 Components map[string]Component `mapstructure:"components"` // Not always present 76 Depends []string `mapstructure:"depends"` // Dependencies can be defined here or in Components.*.Depends 77 // This field only exists for packages with Type `configured`. It can 78 // contain one of two values: `global` for globally-installed dependencies, 79 // or `local` for project-local dependencies 80 Style string `mapstructure:"style"` // Only exists for packages with type `configured` 81 } 82 83 func installPlanToID(plan InstallPlan) pkg.ID { 84 return pkg.ID{ 85 Type: pkg.Haskell, 86 Name: plan.Name, 87 Revision: plan.Version, 88 } 89 } 90 91 func isDirectDependency(plan InstallPlan) bool { 92 // See documentation on InstallPlan 93 return plan.Type == "configured" && plan.Style == "local" 94 } 95 96 type Component struct { 97 Depends []string `mapstructure:"depends"` 98 } 99 100 // ----- Command invocation 101 102 func getSolverPlan(dir string) (Plan, error) { 103 cabalPlanPath := filepath.Join(dir, planRelPath) 104 105 // If plan.json doesn't exist, generate it 106 if exists, _ := files.Exists(cabalPlanPath); !exists { 107 _, _, err := exec.Run(exec.Cmd{ 108 Name: "cabal", 109 Argv: []string{"v2-build", "--dry-run"}, 110 Dir: dir, 111 }) 112 if err != nil { 113 return Plan{}, err 114 } 115 } 116 117 if exists, _ := files.Exists(cabalPlanPath); !exists { 118 // TODO: fallback to another strategy? 119 return Plan{}, errors.New("couldn't find or generate cabal solver plan") 120 } 121 122 // Parse cabal v2-build's build plan 123 var rawPlan map[string]interface{} 124 var plan Plan 125 126 if err := files.ReadJSON(&rawPlan, cabalPlanPath); err != nil { 127 return Plan{}, err 128 } 129 if err := mapstructure.Decode(rawPlan, &plan); err != nil { 130 return Plan{}, err 131 } 132 133 return plan, nil 134 } 135 136 func installPlanToPackage(installPlans map[string]InstallPlan, plan InstallPlan) pkg.Package { 137 var imports []pkg.Import 138 139 for _, depId := range plan.Depends { 140 dep := installPlans[depId] 141 142 imports = append(imports, pkg.Import{ 143 Resolved: installPlanToID(dep), 144 }) 145 } 146 147 for _, component := range plan.Components { 148 for _, depId := range component.Depends { 149 dep := installPlans[depId] 150 151 imports = append(imports, pkg.Import{ 152 Resolved: installPlanToID(dep), 153 }) 154 } 155 } 156 157 return pkg.Package{ 158 ID: installPlanToID(plan), 159 Imports: imports, 160 } 161 }