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
Kevin Conner authored and knrc committed Feb 7, 2023
1 parent 48072a3 commit 4e0d962
Show file tree
Hide file tree
Showing 17 changed files with 435 additions and 97 deletions.
112 changes: 65 additions & 47 deletions src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
Expand Up @@ -71,15 +71,16 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
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;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -237,6 +238,8 @@ public abstract class BaseCycloneDxMojo extends AbstractMojo {
@org.apache.maven.plugins.annotations.Component
private ProjectBuilder mavenProjectBuilder;

private Set<String> excludeTypesSet;

/**
* Various messages sent to console.
*/
Expand All @@ -257,30 +260,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 @@ -658,7 +637,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 @@ -720,7 +699,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 @@ -734,33 +722,51 @@ 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 LinkedList<>(dependencies.values());
final Deque<Dependency> toProcess = new ArrayDeque<>(dependencies.values());
while (!toProcess.isEmpty()) {
final Dependency dependency = toProcess.remove();
if ((dependency.getDependencies() == null) || dependency.getDependencies().isEmpty()) {
Expand All @@ -772,8 +778,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 @@ -836,16 +842,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
43 changes: 20 additions & 23 deletions src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
Expand Up @@ -163,34 +163,31 @@ protected boolean analyze(final Set<Component> components, final Set<Dependency>
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
if (!componentRefs.contains(component.getBomRef())) {
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;
}
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;
}
component.setScope(componentScope);
componentRefs.add(component.getBomRef());
components.add(component);

projectComponentRefs.add(component.getBomRef());
}
component.setScope(componentScope);
componentRefs.add(component.getBomRef());
components.add(component);

projectComponentRefs.add(component.getBomRef());
}
}
if (schemaVersion().getVersion() >= 1.2) {
projectDependencies.addAll(buildDependencyGraph(componentRefs, mavenProject));
projectDependencies.addAll(buildDependencyGraph(mavenProject));
dependencies.addAll(projectDependencies);
}
}
Expand Down
16 changes: 7 additions & 9 deletions src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
Expand Up @@ -111,19 +111,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
if (!componentRefs.contains(component.getBomRef())) {
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
14 changes: 6 additions & 8 deletions src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
Expand Up @@ -66,17 +66,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
if (!componentRefs.contains(component.getBomRef())) {
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 4e0d962

Please sign in to comment.