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  }