github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/carthage/cartfile.go (about) 1 package carthage 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 8 "github.com/apex/log" 9 "github.com/rveen/ogdl" 10 11 "github.com/fossas/fossa-cli/files" 12 "github.com/fossas/fossa-cli/pkg" 13 ) 14 15 type Package struct { 16 Name string // Name of project 17 Version string // Version of project 18 Dependencies []Requirement 19 Dir string // directory of cartfile 20 } 21 22 type Requirement struct { 23 Origin string // "github" || "git" || "binary" 24 Name string 25 Revision string // tag/branch/commit 26 CheckoutName string // if github repo, i.e. "Quick/Nimble", Name will be "Nimble" 27 } 28 29 // Attempt to construct a Package from a dep given the parent directory 30 func (r Requirement) Package(dir string) (Package, error) { 31 log.Debugf("Attempting to build Cartfile with dep %#v", r.CheckoutName) 32 var resolvedCartfile Package 33 34 requirementDirectory := filepath.Join(dir, "Carthage/Checkouts", r.CheckoutName) 35 36 log.Debugf("Checking for Cartfile.resolved at: %#v", requirementDirectory) 37 hasResolvedCartfile, err := files.Exists(filepath.Join(requirementDirectory, "Cartfile.resolved")) 38 39 if err != nil { 40 log.Debugf("Error checking for resolved cartfile: %#v, %#v", requirementDirectory, err.Error()) 41 return resolvedCartfile, err 42 } 43 44 if !hasResolvedCartfile { 45 log.Debugf("Cartfile.resolved missing in: %#v, exiting.", requirementDirectory) 46 return resolvedCartfile, fmt.Errorf("Cartfile.resolved missing in: %#v", requirementDirectory) 47 } 48 49 // get current Cartfile.resolved 50 resolvedCartfile, cartfileErr := FromResolvedCartfile(r.CheckoutName, requirementDirectory) 51 if cartfileErr != nil { 52 log.Debugf("Error parsing Cartfile.resolved at %#v: %#v", requirementDirectory, err.Error()) 53 return resolvedCartfile, cartfileErr 54 } 55 56 return resolvedCartfile, nil 57 } 58 59 func (r Requirement) String() string { 60 return r.Origin + r.Name + r.Revision 61 } 62 63 func fullName(origin string, url string) string { 64 if origin == "github" { 65 return "https://github.com/" + url 66 } else { 67 return url 68 } 69 } 70 71 /* 72 From the docs: Cartfiles are a restricted subset of the Ordered Graph Data Language, and any standard OGDL tool should be able to parse them. 73 Because of this, we use an OGDL parsing library (rveen/ogdl), which parses a cartfile like so: 74 _ 75 github 76 Quick/Nimble 77 v7.1.3 78 github 79 facebook/ios-snapshot-test-case 80 2.1.4 81 github 82 facebook/yoga 83 1.9.0 84 github 85 jspahrsummers/xcconfigs 86 0.9 87 */ 88 func FromResolvedCartfile(projectName string, dir string) (Package, error) { 89 cartfilePath := filepath.Join(dir, "Cartfile.resolved") 90 checkoutsDir := filepath.Join(dir, "Carthage/Checkouts") 91 log.Debugf("Parsing Cartfile.resolved at %#v", cartfilePath) 92 var cartfile Package 93 cartfileGraph := ogdl.FromFile(cartfilePath) 94 95 if cartfileGraph == nil { 96 log.Debugf("Done parsing empty Cartfile.resolved") 97 return cartfile, nil 98 } 99 100 var allDependencies []Requirement 101 102 // Parse the OGDL 103 for _, originGraph := range cartfileGraph.Out { 104 if originGraph.ThisType() != "string" { 105 log.Warn("Malformed Origin in Cartfile") 106 } 107 108 origin := originGraph.ThisString() 109 110 urlGraph := originGraph.Out[0] // only need to know about first elem 111 name := fullName(origin, urlGraph.ThisString()) 112 113 var checkoutName string 114 if origin == "github" { 115 splitName := strings.Split(name, "/") 116 checkoutName = splitName[len(splitName)-1] // ex. Quick/Nimble 117 } else { 118 checkoutName = name 119 } 120 121 depCheckoutsDir := filepath.Join(checkoutsDir, checkoutName) 122 depCheckoutsDirExists, err := files.ExistsFolder(depCheckoutsDir) 123 124 if err != nil { 125 log.Warnf("Error checking for existence of dir: %#v", depCheckoutsDir) 126 continue 127 } 128 129 if !depCheckoutsDirExists { 130 log.Debugf("Checkouts folder doesn't exist for %#v. Skipping dependency.", checkoutName) 131 continue 132 } 133 134 revisionGraph := urlGraph.Out[0] // only need to know about first elem 135 136 allDependencies = append(allDependencies, Requirement{ 137 Origin: origin, 138 Name: name, 139 CheckoutName: checkoutName, 140 Revision: revisionGraph.ThisString(), 141 }) 142 } 143 144 cartfile.Dependencies = allDependencies 145 cartfile.Name = projectName 146 cartfile.Dir = dir 147 148 log.Debugf("Done parsing Cartfile.resolved") 149 return cartfile, nil 150 } 151 152 func RecurseDeps(pkgMap map[pkg.ID]pkg.Package, p Package) { 153 log.Debugf("Searching Carthage deps for project %#v", p.Name) 154 for _, dep := range p.Dependencies { 155 // Construct ID. 156 id := pkg.ID{ 157 Type: pkg.Carthage, 158 Name: dep.Name, 159 Revision: dep.Revision, 160 } 161 // Don't process duplicates. 162 _, ok := pkgMap[id] 163 if ok { 164 continue 165 } 166 167 // Get direct imports. 168 var imports []pkg.Import 169 170 // Get Transitive Dep Info 171 newPackage, err := dep.Package(p.Dir) 172 173 if err != nil { 174 log.Debugf("Error parsing Cartfile.resolved at %#v: %#v. Continuing...", p.Dir, err.Error()) 175 } else { 176 for _, i := range newPackage.Dependencies { 177 imports = append(imports, pkg.Import{ 178 Target: i.String(), 179 Resolved: pkg.ID{ 180 Type: pkg.Carthage, 181 Name: i.Name, 182 Revision: i.Revision, 183 }, 184 }) 185 } 186 } 187 // Update map. 188 pkgMap[id] = pkg.Package{ 189 ID: id, 190 Imports: imports, 191 } 192 // Recurse in imports. 193 RecurseDeps(pkgMap, newPackage) 194 } 195 }