Skip to content

Commit

Permalink
Fix test dependencies referenced as compile scope and skip excluded t…
Browse files Browse the repository at this point in the history
…ypes and their transitive dependencies if not referenced elsewhere

Signed-off-by: Kevin Conner <kev.conner@gmail.com>
  • Loading branch information
knrc committed Jan 8, 2023
1 parent 1c8bf72 commit 0dc02a0
Show file tree
Hide file tree
Showing 17 changed files with 433 additions and 119 deletions.
108 changes: 63 additions & 45 deletions src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -266,6 +267,8 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo implements Contextu
@org.apache.maven.plugins.annotations.Component
private ProjectBuilder mavenProjectBuilder;

private Set<String> excludeTypesSet;

/**
* Various messages sent to console.
*/
Expand Down Expand Up @@ -307,30 +310,6 @@ protected MavenProject getProject() {
return project;
}

protected boolean shouldInclude(Artifact artifact) {
if (artifact.getScope() == null) {
return false;
}
if (excludeTypes != null) {
final boolean shouldExclude = Arrays.asList(excludeTypes).contains(artifact.getType());
if (shouldExclude) {
return false;
}
}
if (includeCompileScope && "compile".equals(artifact.getScope())) {
return true;
} else if (includeProvidedScope && "provided".equals(artifact.getScope())) {
return true;
} else if (includeRuntimeScope && "runtime".equals(artifact.getScope())) {
return true;
} else if (includeTestScope && "test".equals(artifact.getScope())) {
return true;
} else if (includeSystemScope && "system".equals(artifact.getScope())) {
return true;
}
return false;
}

/**
* Converts a MavenProject into a Metadata object.
*
Expand Down Expand Up @@ -708,7 +687,7 @@ private void validateBom(final Bom bom) {
for (Entry<String, Component> entry: components.entrySet()) {
final String componentRef = entry.getKey();
if (!dependencyRefs.contains(componentRef)) {
getLog().info("CycloneDX: Component missing top level dependency entry, pruning component from bom: " + componentRef);
getLog().info("CycloneDX: Component not used in dependency graph, pruning component from bom: " + componentRef);
final Component component = entry.getValue();
if (component != null) {
bom.getComponents().remove(component);
Expand Down Expand Up @@ -770,7 +749,16 @@ private Component.Type resolveProjectType() {
return Component.Type.LIBRARY;
}

protected Set<Dependency> buildDependencyGraph(final Set<String> componentRefs, final MavenProject mavenProject) throws MojoExecutionException {
private Set<String> getExcludeTypesSet() {
if (excludeTypesSet == null) {
excludeTypesSet = Collections.emptySet();
} else {
excludeTypesSet = new HashSet<>(Arrays.asList(excludeTypes));
}
return excludeTypesSet;
}

protected Set<Dependency> buildDependencyGraph(final MavenProject mavenProject) throws MojoExecutionException {
final Map<Dependency, Dependency> dependencies = new LinkedHashMap<>();
final Collection<String> scope = new HashSet<>();
if (includeCompileScope) scope.add("compile");
Expand All @@ -784,30 +772,48 @@ protected Set<Dependency> buildDependencyGraph(final Set<String> componentRefs,

try {
final DependencyNode rootNode = dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, artifactFilter);
buildDependencyGraphNode(componentRefs, dependencies, rootNode, null);
final CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor();
final Map<String, DependencyNode> excludedNodes = new HashMap<>();
buildDependencyGraphNode(dependencies, rootNode, null, excludedNodes);
final CollectingDependencyNodeVisitor visitor = new CollectingDependencyNodeVisitor() {
@Override
public boolean visit(final DependencyNode node) {
// Do not visit excluded nodes
if (isExcludedNode(node)) {
return false;
} else {
return super.visit(node);
}
}
};
rootNode.accept(visitor);
for (final DependencyNode dependencyNode : visitor.getNodes()) {
buildDependencyGraphNode(componentRefs, dependencies, dependencyNode, null);
buildDependencyGraphNode(dependencies, dependencyNode, null, excludedNodes);
}

/*
Test and Runtime scope artifacts may conceal transitive compile dependencies.
Test artifacts will conceal other artifacts if includeTestScope is false, whereas Runtime artifacs
will conceal other artifacts if both incldueTestScope and includeRuntimeScope are false.
*/
if (includeCompileScope && !includeTestScope) {
if ((includeCompileScope && !includeTestScope) || !excludedNodes.isEmpty()){
final ProjectBuildingRequest testBuildingRequest = getProjectBuildingRequest(mavenProject);
final DependencyNode testRootNode = dependencyCollectorBuilder.collectDependencyGraph(testBuildingRequest, null);
final Map<String, DependencyNode> concealedNodes = new HashMap<>();
final Map<String, DependencyNode> concealedEmptyNodes = new HashMap<>();
for (DependencyNode child: testRootNode.getChildren()) {
if (Artifact.SCOPE_TEST.equals(child.getArtifact().getScope())) {
collectNodes(concealedNodes, concealedEmptyNodes, child);
if (includeCompileScope && !includeTestScope) {
final DependencyNode testRootNode = dependencyCollectorBuilder.collectDependencyGraph(testBuildingRequest, null);
for (DependencyNode child: testRootNode.getChildren()) {
if (Artifact.SCOPE_TEST.equals(child.getArtifact().getScope())) {
collectNodes(concealedNodes, concealedEmptyNodes, child);
}
}
if (!includeRuntimeScope) {
collectRuntimeNodes(concealedNodes, concealedEmptyNodes, testRootNode);
}
}
if (!includeRuntimeScope) {
collectRuntimeNodes(concealedNodes, concealedEmptyNodes, testRootNode);
if (!excludedNodes.isEmpty()) {
for (DependencyNode excluded: excludedNodes.values()) {
collectNodes(concealedNodes, concealedEmptyNodes, excluded);
}
}

final Deque<Dependency> toProcess = new ArrayDeque<>(dependencies.values());
Expand All @@ -822,8 +828,8 @@ protected Set<Dependency> buildDependencyGraph(final Set<String> componentRefs,
if (concealedNode != null) {
getLog().info("CycloneDX: Populating concealed node: " + purl);
for (DependencyNode child: concealedNode.getChildren()) {
buildDependencyGraphNode(componentRefs, dependencies, child, dependency);
buildDependencyGraphNode(componentRefs, dependencies, child, null);
buildDependencyGraphNode(dependencies, child, dependency, excludedNodes);
buildDependencyGraphNode(dependencies, child, null, excludedNodes);
}
final Dependency topLevelDependency = dependencies.get(dependency);
if (topLevelDependency.getDependencies() != null) {
Expand Down Expand Up @@ -886,16 +892,28 @@ private void collectNodes(Map<String, DependencyNode> concealedNodes, Map<String
}
}

private void buildDependencyGraphNode(final Set<String> componentRefs, final Map<Dependency, Dependency> dependencies, final DependencyNode artifactNode, final Dependency parent) {
private boolean isExcludedNode(final DependencyNode node) {
final String type = node.getArtifact().getType();
return ((type == null) || getExcludeTypesSet().contains(type));
}

private void buildDependencyGraphNode(final Map<Dependency, Dependency> dependencies, final DependencyNode artifactNode, final Dependency parent,
final Map<String, DependencyNode> excludedNodes) {
final String purl = generatePackageUrl(artifactNode.getArtifact());
final Dependency dependency = new Dependency(purl);
if (componentRefs.contains(purl)) {
addDependencyToGraph(dependencies, parent, dependency);
} else {
getLog().warn("CycloneDX: Could not locate component " + purl + " in componentRefs");
// If this is an excluded type then track in case it is hiding a transitive dependency
if (isExcludedNode(artifactNode)) {
excludedNodes.put(purl, artifactNode);
return;
}
// When adding concealed nodes we may inadvertently pull in runtime artifacts
if (!includeTestScope && !includeRuntimeScope && Artifact.SCOPE_RUNTIME.equals(artifactNode.getArtifact().getScope())) {
return;
}

final Dependency dependency = new Dependency(purl);
addDependencyToGraph(dependencies, parent, dependency);
for (final DependencyNode childrenNode : artifactNode.getChildren()) {
buildDependencyGraphNode(componentRefs, dependencies, childrenNode, dependency);
buildDependencyGraphNode(dependencies, childrenNode, dependency, excludedNodes);
}
}

Expand Down
55 changes: 20 additions & 35 deletions src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,53 +127,38 @@ protected boolean analyze(final Set<Component> components, final Set<Dependency>
if (! mavenProject.isExecutionRoot()) {
// DO NOT include root project as it's already been included as a bom metadata component
// Also, ensure that only one project component with the same bom-ref exists in the BOM
boolean found = false;
for (String s : componentRefs) {
if (s != null && s.equals(projectBomComponent.getBomRef())) {
found = true;
}
}
if (!found) {
if (!componentRefs.contains(projectBomComponent.getBomRef())) {
components.add(projectBomComponent);
}
}
componentRefs.add(projectBomComponent.getBomRef());

for (final Artifact artifact : mavenProject.getArtifacts()) {
if (shouldInclude(artifact)) {
final Component component = convert(artifact);

// ensure that only one component with the same bom-ref exists in the BOM
boolean found = false;
for (String s : componentRefs) {
if (s != null && s.equals(component.getBomRef())) {
found = true;
final Component component = convert(artifact);

// ensure that only one component with the same bom-ref exists in the BOM
if (!componentRefs.contains(component.getBomRef())) {
Component.Scope componentScope = null;
for (ProjectDependencyAnalysis dependencyAnalysis : dependencyAnalysisMap.values()) {
Component.Scope currentProjectScope = getComponentScope(component, artifact, dependencyAnalysis);
// Set scope to required if the component is used in any project
if (Component.Scope.REQUIRED.equals(currentProjectScope)) {
componentScope = currentProjectScope;
break;
} else if (componentScope == null && currentProjectScope != null) {
// Set optional or excluded scope
componentScope = currentProjectScope;
}
}
if (!found) {
Component.Scope componentScope = null;
for (String projectId : dependencyAnalysisMap.keySet()) {
ProjectDependencyAnalysis dependencyAnalysis = dependencyAnalysisMap.get(projectId);
Component.Scope currentProjectScope = getComponentScope(component, artifact, dependencyAnalysis);
// Set scope to required if the component is used in any project
if (Component.Scope.REQUIRED.equals(currentProjectScope)) {
componentScope = currentProjectScope;
break;
} else if (componentScope == null && currentProjectScope != null) {
// Set optional or excluded scope
componentScope = currentProjectScope;
}
}
component.setScope(componentScope);
componentRefs.add(component.getBomRef());
components.add(component);
component.setScope(componentScope);
componentRefs.add(component.getBomRef());
components.add(component);

projectComponentRefs.add(component.getBomRef());
}
projectComponentRefs.add(component.getBomRef());
}
}
if (schemaVersion().getVersion() >= 1.2) {
projectDependencies.addAll(buildDependencyGraph(componentRefs, mavenProject));
projectDependencies.addAll(buildDependencyGraph(mavenProject));
dependencies.addAll(projectDependencies);
}
}
Expand Down
22 changes: 7 additions & 15 deletions src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,25 +60,17 @@ protected boolean analyze(final Set<Component> components, final Set<Dependency>
componentRefs.add(bomComponent.getBomRef());

for (final Artifact artifact : getProject().getArtifacts()) {
if (shouldInclude(artifact)) {
final Component component = convert(artifact);
// ensure that only one component with the same bom-ref exists in the BOM
boolean found = false;
for (String s : componentRefs) {
if (s != null && s.equals(component.getBomRef())) {
found = true;
}
}
if (!found) {
component.setScope(getComponentScope(component, artifact, dependencyAnalysis));
componentRefs.add(component.getBomRef());
components.add(component);
}
final Component component = convert(artifact);
// ensure that only one component with the same bom-ref exists in the BOM
if (!componentRefs.contains(component.getBomRef())) {
component.setScope(getComponentScope(component, artifact, dependencyAnalysis));
componentRefs.add(component.getBomRef());
components.add(component);
}
}
}
if (schemaVersion().getVersion() >= 1.2) {
dependencies.addAll(buildDependencyGraph(componentRefs, null));
dependencies.addAll(buildDependencyGraph(null));
}
return true;
}
Expand Down
20 changes: 6 additions & 14 deletions src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,15 @@ protected boolean analyze(Set<Component> components, Set<Dependency> dependencie
}
getLog().info("Analyzing " + mavenProject.getArtifactId());
for (final Artifact artifact : mavenProject.getArtifacts()) {
if (shouldInclude(artifact)) {
final Component component = convert(artifact);
// ensure that only one component with the same bom-ref exists in the BOM
boolean found = false;
for (String s : componentRefs) {
if (s != null && s.equals(component.getBomRef())) {
found = true;
}
}
if (!found) {
componentRefs.add(component.getBomRef());
components.add(component);
}
final Component component = convert(artifact);
// ensure that only one component with the same bom-ref exists in the BOM
if (!componentRefs.contains(component.getBomRef())) {
componentRefs.add(component.getBomRef());
components.add(component);
}
}
if (schemaVersion().getVersion() >= 1.2) {
dependencies.addAll(buildDependencyGraph(componentRefs, mavenProject));
dependencies.addAll(buildDependencyGraph(mavenProject));
}
}
return true;
Expand Down

0 comments on commit 0dc02a0

Please sign in to comment.