Skip to content

Change SBT detector graph parser library to support newer versions #1431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions detectable/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies {
implementation 'org.tomlj:tomlj:1.0.0'
implementation 'com.moandjiezana.toml:toml4j:0.7.2'
implementation 'com.paypal.digraph:digraph-parser:1.0'
implementation 'guru.nidi:graphviz-java:0.18.1'

testImplementation 'org.skyscreamer:jsonassert:1.5.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import java.util.List;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.paypal.digraph.parser.GraphParser;
import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.parse.Parser;
import com.blackduck.integration.bdio.graph.DependencyGraph;
import com.blackduck.integration.bdio.model.dependency.Dependency;
import com.blackduck.integration.detectable.ExecutableTarget;
Expand Down Expand Up @@ -56,13 +56,13 @@ public Extraction extract(File directory, ExecutableTarget sbt, String sbtComman

Extraction.Builder extraction = new Extraction.Builder();
for (File dotGraph : dotGraphs) {
GraphParser graphParser = new GraphParser(FileUtils.openInputStream(dotGraph));
Set<String> rootIDs = sbtRootNodeFinder.determineRootIDs(graphParser);
MutableGraph mutableGraph = new Parser().read(dotGraph);
Set<String> rootIDs = sbtRootNodeFinder.determineRootIDs(mutableGraph);
File projectFolder = dotGraph.getParentFile().getParentFile();//typically found in project-folder/target/<>.dot so .parent.parent == project folder

if (rootIDs.size() == 1) {
String projectId = rootIDs.stream().findFirst().get();
DependencyGraph graph = sbtGraphParserTransformer.transformDotToGraph(graphParser, projectId);
DependencyGraph graph = sbtGraphParserTransformer.transformDotToGraph(projectId, mutableGraph);
Dependency projectDependency = graphNodeParser.nodeToDependency(projectId);
extraction.codeLocations(new CodeLocation(graph, projectDependency.getExternalId(), projectFolder));
if (projectFolder.equals(directory)) {
Expand All @@ -72,7 +72,7 @@ public Extraction extract(File directory, ExecutableTarget sbt, String sbtComman
} else {
logger.warn("Unable to determine which node was the project in an SBT graph: " + dotGraph.toString());
logger.warn("This may mean you have extraneous dependencies and should consider removing them. The dependencies are: " + String.join(",", rootIDs));
DependencyGraph graph = sbtGraphParserTransformer.transformDotToGraph(graphParser, rootIDs);
DependencyGraph graph = sbtGraphParserTransformer.transformDotToGraph(rootIDs, mutableGraph);
extraction.codeLocations(new CodeLocation(graph, projectFolder));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public List<File> parseGeneratedGraphFiles(List<String> dotOutput) {

@Nullable
private String parseDotGraphFromLine(String line) {
final String DOT_PREFIX = "[info] Wrote dependency graph to '";
if (line.startsWith(DOT_PREFIX)) {
final String DOT_PREFIX = "Wrote dependency graph to '";
if (line.contains(DOT_PREFIX)) {
final String DOT_SUFFIX = "'";
return StringUtils.substringBetween(line, DOT_PREFIX, DOT_SUFFIX);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.blackduck.integration.detectable.detectables.sbt.dot;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import guru.nidi.graphviz.model.Link;
import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.model.MutableNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.paypal.digraph.parser.GraphEdge;
import com.paypal.digraph.parser.GraphElement;
import com.paypal.digraph.parser.GraphParser;
import com.blackduck.integration.bdio.graph.BasicDependencyGraph;
import com.blackduck.integration.bdio.graph.DependencyGraph;
import com.blackduck.integration.bdio.model.dependency.Dependency;
Expand All @@ -21,26 +23,41 @@ public SbtGraphParserTransformer(SbtDotGraphNodeParser sbtDotGraphNodeParser) {
this.sbtDotGraphNodeParser = sbtDotGraphNodeParser;
}

public DependencyGraph transformDotToGraph(GraphParser graphParser, String projectNodeId) {
public DependencyGraph transformDotToGraph(String projectNodeId, MutableGraph mutableGraph) {
DependencyGraph graph = new BasicDependencyGraph();

Set<String> evictedIds = graphParser.getEdges().values().stream()
.filter(
edge -> edge.getAttribute("label") != null
&& edge.getAttribute("label").toString().toLowerCase().contains("evicted")
)
.map(GraphEdge::getNode1)
.map(GraphElement::getId)
.collect(Collectors.toSet());

for (GraphEdge graphEdge : graphParser.getEdges().values()) {
Dependency parent = sbtDotGraphNodeParser.nodeToDependency(graphEdge.getNode1().getId());
Dependency child = sbtDotGraphNodeParser.nodeToDependency(graphEdge.getNode2().getId());

if (projectNodeId.equals(graphEdge.getNode1().getId())) {
graph.addChildToRoot(child);
Set<String> evictedIds = new HashSet<>();
mutableGraph.nodes().forEach(node -> {
node.attrs().forEach(attr -> {
if(attr.getKey().equals("label")) {
if(attr.getValue().toString().toLowerCase().contains("evicted")) {
evictedIds.add(node.name().toString());
}
}
});
node.links().forEach(link -> {
link.attrs().forEach(attr -> {
if(attr.getKey().equals("label")) {
if(attr.getValue().toString().toLowerCase().contains("evicted")) {
evictedIds.add(node.name().toString());
}
}
});
});
});

List<Link> links = mutableGraph.nodes().stream().map(MutableNode::links).flatMap(List::stream).collect(Collectors.toList());
for (Link link : links) {
String parentNode = normalizeDependency(link.asLinkTarget().name().toString());
String childNode = normalizeDependency(link.asLinkSource().name().toString());

Dependency parent = sbtDotGraphNodeParser.nodeToDependency(parentNode);
Dependency child = sbtDotGraphNodeParser.nodeToDependency(childNode);

if (projectNodeId.equals(parentNode)) {
graph.addDirectDependency(child);
} else {
if (!evictedIds.contains(graphEdge.getNode2().getId())) {
if (!evictedIds.contains(childNode)) {
graph.addChildWithParent(child, parent);
}
}
Expand All @@ -49,18 +66,32 @@ public DependencyGraph transformDotToGraph(GraphParser graphParser, String proje
return graph;
}

public DependencyGraph transformDotToGraph(GraphParser graphParser, Set<String> projectNodeIds) {
public DependencyGraph transformDotToGraph(Set<String> projectNodeIds, MutableGraph mutableGraph) {
DependencyGraph graph = new BasicDependencyGraph();

for (GraphEdge graphEdge : graphParser.getEdges().values()) {
Dependency parent = sbtDotGraphNodeParser.nodeToDependency(graphEdge.getNode1().getId());
Dependency child = sbtDotGraphNodeParser.nodeToDependency(graphEdge.getNode2().getId());
if (projectNodeIds.contains(graphEdge.getNode1().getId())) {
graph.addChildToRoot(parent);
List<Link> links = mutableGraph.nodes().stream().map(MutableNode::links).flatMap(List::stream).collect(Collectors.toList());
for (Link link : links) {
String parentNode = normalizeDependency(link.asLinkTarget().name().toString());
String childNode = normalizeDependency(link.asLinkSource().name().toString());

Dependency parent = sbtDotGraphNodeParser.nodeToDependency(parentNode);
Dependency child = sbtDotGraphNodeParser.nodeToDependency(childNode);

if (projectNodeIds.contains(parentNode)) {
graph.addDirectDependency(parent);
}

graph.addChildWithParent(child, parent);

}

return graph;
}

private String normalizeDependency(String dependency) {
if(dependency.startsWith("--")) {
return dependency.substring(2);
}
return dependency;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import java.util.HashSet;
import java.util.Set;
import java.util.List;
import java.util.stream.Collectors;

import guru.nidi.graphviz.attribute.Label;
import guru.nidi.graphviz.model.MutableNode;
import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.model.LinkSource;
import guru.nidi.graphviz.model.Link;
import org.apache.commons.collections4.SetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.paypal.digraph.parser.GraphEdge;
import com.paypal.digraph.parser.GraphElement;
import com.paypal.digraph.parser.GraphParser;
import com.blackduck.integration.detectable.detectable.exception.DetectableException;

public class SbtRootNodeFinder {
Expand All @@ -21,12 +24,16 @@ public SbtRootNodeFinder(SbtDotGraphNodeParser sbtDotGraphNodeParser) {
this.sbtDotGraphNodeParser = sbtDotGraphNodeParser;
}

public Set<String> determineRootIDs(GraphParser graphParser) throws DetectableException {
Set<String> nodeIdsUsedInDestination = graphParser.getEdges().values().stream()
.map(GraphEdge::getNode2)
.map(GraphElement::getId)
.collect(Collectors.toSet());
Set<String> allNodeIds = new HashSet<>(graphParser.getNodes().keySet());
public Set<String> determineRootIDs(MutableGraph mutableGraph) throws DetectableException {
Set<String> nodeIdsUsedInDestination = mutableGraph.nodes().stream()
.map(MutableNode::links)
.flatMap(List::stream)
.map(Link::asLinkSource)
.map(LinkSource::name)
.map(Label::value)
.collect(Collectors.toSet());

Set<String> allNodeIds = mutableGraph.nodes().stream().map(MutableNode::name).map(Label::value).collect(Collectors.toSet());
return SetUtils.difference(allNodeIds, nodeIdsUsedInDestination);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.blackduck.integration.detectable.detectables.sbt.unit;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.model.MutableNode;
import guru.nidi.graphviz.parse.Parser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.paypal.digraph.parser.GraphNode;
import com.paypal.digraph.parser.GraphParser;
import com.blackduck.integration.bdio.model.dependency.Dependency;
import com.blackduck.integration.bdio.model.externalid.ExternalId;
import com.blackduck.integration.bdio.model.externalid.ExternalIdFactory;
Expand All @@ -18,7 +19,7 @@
public class SbtDotGraphNodeParserTest {

@Test
public void canParseSimpleGraph() {
public void canParseSimpleGraph() throws IOException {
String simpleGraph = "digraph \"dependency-graph\" {\n"
+ " graph[rankdir=\"LR\"]\n"
+ " edge [\n"
Expand All @@ -28,15 +29,13 @@ public void canParseSimpleGraph() {
+ "\n"
+ "}";
InputStream stream = new ByteArrayInputStream(simpleGraph.getBytes(StandardCharsets.UTF_8));
GraphParser graphParser = new GraphParser(stream);
MutableGraph mutableGraph = new Parser().read(stream);

Map.Entry<String, GraphNode> node = graphParser.getNodes().entrySet().stream().findFirst().get();
MutableNode node = mutableGraph.nodes().stream().findFirst().get();
SbtDotGraphNodeParser nodeParser = new SbtDotGraphNodeParser(new ExternalIdFactory());
Dependency dependencyFromKey = nodeParser.nodeToDependency(node.getKey());
Dependency dependencyFromId = nodeParser.nodeToDependency(node.getValue().getId());
Dependency dependency = nodeParser.nodeToDependency(node.name().toString());

assertDependency(dependencyFromId, "org.scalameta", "scalafmtroot_2.13", "2.7.5-SNAPSHOT");
assertDependency(dependencyFromKey, "org.scalameta", "scalafmtroot_2.13", "2.7.5-SNAPSHOT");
assertDependency(dependency, "org.scalameta", "scalafmtroot_2.13", "2.7.5-SNAPSHOT");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
package com.blackduck.integration.detectable.detectables.sbt.unit;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Set;

import guru.nidi.graphviz.model.MutableGraph;
import guru.nidi.graphviz.parse.Parser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.paypal.digraph.parser.GraphParser;
import com.blackduck.integration.bdio.model.externalid.ExternalIdFactory;
import com.blackduck.integration.detectable.detectable.exception.DetectableException;
import com.blackduck.integration.detectable.detectables.sbt.dot.SbtDotGraphNodeParser;
import com.blackduck.integration.detectable.detectables.sbt.dot.SbtRootNodeFinder;

public class SbtRootNodeFinderTest {

private GraphParser createGraphParser(String actualGraph) {
private MutableGraph createMutableGraph(String actualGraph) throws IOException {
String simpleGraph = "digraph \"dependency-graph\" {\n"
+ " graph[rankdir=\"LR\"]\n"
+ " edge [\n"
+ " arrowtail=\"none\"\n"
+ " ]\n"
+ actualGraph + "\n"
+ "\n"
+ "}";
+ " graph[rankdir=\"LR\"]\n"
+ " edge [\n"
+ " arrowtail=\"none\"\n"
+ " ]\n"
+ actualGraph + "\n"
+ "\n"
+ "}";
InputStream stream = new ByteArrayInputStream(simpleGraph.getBytes(StandardCharsets.UTF_8));
return new GraphParser(stream);
return new Parser().read(stream);
}

private String node(String org, String name, String version) {
Expand All @@ -38,47 +40,47 @@ private String edge(String fromOrg, String fromName, String fromVersion, String
}

@Test
public void projectFoundFromSingleNode() throws DetectableException {
GraphParser graphParser = createGraphParser(node("one-org", "one-name", "one-version"));
public void projectFoundFromSingleNode() throws DetectableException, IOException {
MutableGraph mutableGraph = createMutableGraph(node("one-org", "one-name", "one-version"));
SbtRootNodeFinder projectMatcher = new SbtRootNodeFinder(new SbtDotGraphNodeParser(new ExternalIdFactory()));
Set<String> projectId = projectMatcher.determineRootIDs(graphParser);
Set<String> projectId = projectMatcher.determineRootIDs(mutableGraph);
Assertions.assertEquals(1, projectId.size());
Assertions.assertEquals("\"one-org:one-name:one-version\"", projectId.stream().findFirst().get());
Assertions.assertEquals("one-org:one-name:one-version", projectId.stream().findFirst().get());
}

@Test
public void projectFoundFromTwoNodesWhereProjectIsSecond() throws DetectableException {
GraphParser graphParser = createGraphParser(node("two-org", "two-name", "two-version") +
public void projectFoundFromTwoNodesWhereProjectIsSecond() throws DetectableException, IOException {
MutableGraph mutableGraph = createMutableGraph(node("two-org", "two-name", "two-version") +
node("one-org", "one-name", "one-version") +
edge("one-org", "one-name", "one-version", "two-org", "two-name", "two-version"));
SbtRootNodeFinder projectMatcher = new SbtRootNodeFinder(new SbtDotGraphNodeParser(new ExternalIdFactory()));
Set<String> projectId = projectMatcher.determineRootIDs(graphParser);
Set<String> projectId = projectMatcher.determineRootIDs(mutableGraph);
Assertions.assertEquals(1, projectId.size());
Assertions.assertEquals("\"one-org:one-name:one-version\"", projectId.stream().findFirst().get());
Assertions.assertEquals("one-org:one-name:one-version", projectId.stream().findFirst().get());
}

@Test
public void multipleFoundWithNoEdges() throws DetectableException {
GraphParser graphParser = createGraphParser(node("one-org", "one-name", "one-version") +
public void multipleFoundWithNoEdges() throws DetectableException, IOException {
MutableGraph mutableGraph = createMutableGraph(node("one-org", "one-name", "one-version") +
node("one-org", "one-name", "two-version"));
SbtRootNodeFinder projectMatcher = new SbtRootNodeFinder(new SbtDotGraphNodeParser(new ExternalIdFactory()));
Set<String> projectId = projectMatcher.determineRootIDs(graphParser);
Set<String> projectId = projectMatcher.determineRootIDs(mutableGraph);
Assertions.assertEquals(2, projectId.size());
Assertions.assertTrue(projectId.contains("\"one-org:one-name:one-version\""));
Assertions.assertTrue(projectId.contains("\"one-org:one-name:two-version\""));
Assertions.assertTrue(projectId.contains("one-org:one-name:one-version"));
Assertions.assertTrue(projectId.contains("one-org:one-name:two-version"));
}

@Test
public void multipleFoundWithEdge() throws DetectableException {
GraphParser graphParser = createGraphParser(node("one-org", "one-name", "one-version") +
public void multipleFoundWithEdge() throws DetectableException, IOException {
MutableGraph mutableGraph = createMutableGraph(node("one-org", "one-name", "one-version") +
node("one-org", "one-name", "two-version") +
node("one-org", "one-name", "three-version") + //should not be reported
edge("one-org", "one-name", "one-version", "one-org", "one-name", "three-version"));
SbtRootNodeFinder projectMatcher = new SbtRootNodeFinder(new SbtDotGraphNodeParser(new ExternalIdFactory()));
Set<String> projectId = projectMatcher.determineRootIDs(graphParser);
Set<String> projectId = projectMatcher.determineRootIDs(mutableGraph);
Assertions.assertEquals(2, projectId.size());
Assertions.assertTrue(projectId.contains("\"one-org:one-name:one-version\""));
Assertions.assertTrue(projectId.contains("\"one-org:one-name:two-version\""));
Assertions.assertTrue(projectId.contains("one-org:one-name:one-version"));
Assertions.assertTrue(projectId.contains("one-org:one-name:two-version"));
}

}
Loading