github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/okbuck/okbuck.go (about)

     1  package okbuck
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  
     7  	"github.com/fossas/fossa-cli/graph"
     8  	"github.com/fossas/fossa-cli/pkg"
     9  )
    10  
    11  // OkBuck defines an interface for all OkBuck tool implementations.
    12  type OkBuck interface {
    13  	Deps(string) (graph.Deps, error)
    14  }
    15  
    16  // Setup implements OkBuck and defines how to retrieve OkBuck output.
    17  type Setup struct {
    18  	Target string
    19  	Cmd    func(...string) (string, error)
    20  }
    21  
    22  // Target represents an OkBuck build target.
    23  type Target struct {
    24  	BinaryJar        string `json:"binaryJar"`
    25  	Aar              string `json:"aar"`
    26  	MavenCoordinates string `json:"mavenCoords"`
    27  }
    28  
    29  // Dependency holds revision information after parsing output.
    30  type Dependency struct {
    31  	Jar      string
    32  	Name     string
    33  	Revision string
    34  }
    35  
    36  // New creates a new OkBuck instance that calls the okbuck build tool directly.
    37  func New(target string) OkBuck {
    38  	return Setup{
    39  		Target: target,
    40  		Cmd:    Cmd,
    41  	}
    42  }
    43  
    44  // Deps finds the dependencies of an OkBuck target or classpath using
    45  // the supplied command and returns the dependency graph.
    46  func (b Setup) Deps(classpath string) (graph.Deps, error) {
    47  	out, err := b.Cmd("targets", b.Target, "--json")
    48  	if err != nil {
    49  		return graph.Deps{}, err
    50  	}
    51  
    52  	var targets []Target
    53  	err = json.Unmarshal([]byte(out), &targets)
    54  	if err != nil {
    55  		return graph.Deps{}, err
    56  	}
    57  
    58  	dependencies := depsFromTargets(targets)
    59  
    60  	if classpath != "" {
    61  		out, err := b.Cmd("audit", "classpath", classpath)
    62  		if err != nil {
    63  			return graph.Deps{}, err
    64  		}
    65  		jars := classpathJars(out)
    66  		return graphFromJars(jars, dependencies), nil
    67  	}
    68  
    69  	depGraph := graph.Deps{
    70  		Direct:     []pkg.Import{},
    71  		Transitive: make(map[pkg.ID]pkg.Package),
    72  	}
    73  	for _, dep := range dependencies {
    74  		id := pkg.ID{
    75  			Type:     pkg.Maven,
    76  			Name:     dep.Name,
    77  			Revision: dep.Revision,
    78  		}
    79  		depGraph.Direct = append(depGraph.Direct, pkg.Import{Resolved: id})
    80  		depGraph.Transitive[id] = pkg.Package{
    81  			ID:      id,
    82  			Imports: []pkg.Import{},
    83  		}
    84  	}
    85  
    86  	return depGraph, nil
    87  }
    88  
    89  // depsFromTargets takes an OkBuck target list and creates a list of
    90  // Dependencies that can be used to build a graph.Deps object.
    91  func depsFromTargets(targets []Target) []Dependency {
    92  	var deps []Dependency
    93  	for _, target := range targets {
    94  		splitDep := strings.Split(target.MavenCoordinates, ":")
    95  		if len(splitDep) == 4 {
    96  			deps = append(deps, Dependency{
    97  				Jar:      conditionJar(target),
    98  				Name:     splitDep[0] + ":" + splitDep[1],
    99  				Revision: splitDep[3],
   100  			})
   101  		}
   102  	}
   103  	return deps
   104  }
   105  
   106  // classpathJars reads output from `buck audit classpath` and
   107  // finds the unique jar identifies. Examples:
   108  // .okbuck/android/constraint/__package.aar#aar_prebuilt_jar__/classes.jar --> package.aar
   109  // .okbuck/android/constraint/__package.jar__/package.jar --> package.jar
   110  func classpathJars(cmdOut string) []string {
   111  	jarList := strings.Split(cmdOut, "\n")
   112  	deps := []string{}
   113  	for _, jar := range jarList {
   114  		jarFromLine := strings.Split(jar, "_")
   115  		if len(jarFromLine) >= 3 {
   116  			jarFromLine = strings.Split(jarFromLine[2], "#")
   117  		}
   118  		deps = append(deps, jarFromLine[0])
   119  	}
   120  	return deps
   121  }
   122  
   123  func graphFromJars(jarList []string, dependencies []Dependency) graph.Deps {
   124  	depGraph := graph.Deps{
   125  		Direct:     []pkg.Import{},
   126  		Transitive: make(map[pkg.ID]pkg.Package),
   127  	}
   128  
   129  	jarMap := mapJars(dependencies)
   130  	for _, jar := range jarList {
   131  		dep := jarMap[jar]
   132  		if dep != (Dependency{}) {
   133  			id := pkg.ID{
   134  				Type:     pkg.Maven,
   135  				Name:     dep.Name,
   136  				Revision: dep.Revision,
   137  			}
   138  			depGraph.Direct = append(depGraph.Direct, pkg.Import{Resolved: id})
   139  			depGraph.Transitive[id] = pkg.Package{
   140  				ID:      id,
   141  				Imports: []pkg.Import{},
   142  			}
   143  		}
   144  	}
   145  	return depGraph
   146  }
   147  
   148  // mapJars creates a mapping of a jar to the full Dependency.
   149  func mapJars(dependencies []Dependency) map[string]Dependency {
   150  	jarMap := make(map[string]Dependency)
   151  	for _, dependency := range dependencies {
   152  		jarMap[dependency.Jar] = dependency
   153  	}
   154  	return jarMap
   155  }
   156  
   157  // conditionJar determines what an OkBuck targets jar is.
   158  // "aar" : ":package.aar__downloaded" -> package.aar
   159  // "binaryJar" : ":package.jar__downloaded" -> package.jar
   160  func conditionJar(target Target) string {
   161  	jar := target.BinaryJar
   162  	if target.Aar != "" {
   163  		jar = target.Aar
   164  	}
   165  	jar = strings.TrimPrefix(jar, ":")
   166  	jar = strings.Split(jar, "_")[0]
   167  	return jar
   168  }