Skip to content

Commit

Permalink
Remove code generating resolved PURLs, fixes CycloneDX#311
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Conner <[email protected]>
  • Loading branch information
knrc committed Apr 25, 2023
1 parent 50b3f44 commit f154d43
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 71 deletions.
10 changes: 6 additions & 4 deletions src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.cyclonedx.exception.GeneratorException;
import org.cyclonedx.generators.json.BomJsonGenerator;
import org.cyclonedx.generators.xml.BomXmlGenerator;
import org.cyclonedx.maven.ProjectDependenciesConverter.BomDependencies;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
Expand Down Expand Up @@ -356,7 +357,7 @@ private void saveBomToFile(String bomString, String extension, Parser bomParser)
}
}

protected Map<String, Dependency> extractBOMDependencies(MavenProject mavenProject) throws MojoExecutionException {
protected BomDependencies extractBOMDependencies(MavenProject mavenProject) throws MojoExecutionException {
ProjectDependenciesConverter.MavenDependencyScopes include = new ProjectDependenciesConverter.MavenDependencyScopes(includeCompileScope, includeProvidedScope, includeRuntimeScope, includeTestScope, includeSystemScope);
return projectDependenciesConverter.extractBOMDependencies(mavenProject, include, excludeTypes);
}
Expand Down Expand Up @@ -402,9 +403,10 @@ protected void logParameters() {
}
}

protected void populateComponents(final Set<String> topLevelComponents, final Map<String, Component> components, final Set<Artifact> artifacts, final ProjectDependencyAnalysis dependencyAnalysis) {
for (Artifact artifact: artifacts) {
final String purl = generatePackageUrl(artifact);
protected void populateComponents(final Set<String> topLevelComponents, final Map<String, Component> components, final Map<String, Artifact> artifacts, final ProjectDependencyAnalysis dependencyAnalysis) {
for (Map.Entry<String, Artifact> entry: artifacts.entrySet()) {
final String purl = entry.getKey();
final Artifact artifact = entry.getValue();
final Component.Scope artifactScope = (dependencyAnalysis != null ? inferComponentScope(artifact, dependencyAnalysis) : null);
final Component component = components.get(purl);
if (component == null) {
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.cyclonedx.maven.ProjectDependenciesConverter.BomDependencies;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;

Expand Down Expand Up @@ -121,13 +122,14 @@ protected String extractComponentsAndDependencies(final Set<String> topLevelComp
continue;
}

final Map<String, Dependency> projectDependencies = extractBOMDependencies(mavenProject);
final BomDependencies bomDependencies = extractBOMDependencies(mavenProject);
final Map<String, Dependency> projectDependencies = bomDependencies.getDependencies();

final Component projectBomComponent = convert(mavenProject.getArtifact());
components.put(projectBomComponent.getPurl(), projectBomComponent);
topLevelComponents.add(projectBomComponent.getPurl());

populateComponents(topLevelComponents, components, mavenProject.getArtifacts(), doProjectDependencyAnalysis(mavenProject));
populateComponents(topLevelComponents, components, bomDependencies.getArtifacts(), doProjectDependencyAnalysis(mavenProject));

projectDependencies.forEach(dependencies::putIfAbsent);
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/cyclonedx/maven/CycloneDxMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzerException;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.cyclonedx.maven.ProjectDependenciesConverter.BomDependencies;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;

Expand Down Expand Up @@ -93,13 +94,14 @@ protected ProjectDependencyAnalysis doProjectDependencyAnalysis(MavenProject mav
protected String extractComponentsAndDependencies(final Set<String> topLevelComponents, final Map<String, Component> components, final Map<String, Dependency> dependencies) throws MojoExecutionException {
getLog().info(MESSAGE_RESOLVING_DEPS);

final Map<String, Dependency> projectDependencies = extractBOMDependencies(getProject());
final BomDependencies bomDependencies = extractBOMDependencies(getProject());
final Map<String, Dependency> projectDependencies = bomDependencies.getDependencies();

final Component projectBomComponent = convert(getProject().getArtifact());
components.put(projectBomComponent.getPurl(), projectBomComponent);
topLevelComponents.add(projectBomComponent.getPurl());

populateComponents(topLevelComponents, components, getProject().getArtifacts(), doProjectDependencyAnalysis(getProject()));
populateComponents(topLevelComponents, components, bomDependencies.getArtifacts(), doProjectDependencyAnalysis(getProject()));

projectDependencies.forEach(dependencies::putIfAbsent);

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.cyclonedx.maven.ProjectDependenciesConverter.BomDependencies;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;

Expand Down Expand Up @@ -64,13 +65,14 @@ protected String extractComponentsAndDependencies(Set<String> topLevelComponents
}
getLog().info("Analyzing " + mavenProject.getArtifactId());

final Map<String, Dependency> projectDependencies = extractBOMDependencies(mavenProject);
final BomDependencies bomDependencies = extractBOMDependencies(mavenProject);
final Map<String, Dependency> projectDependencies = bomDependencies.getDependencies();

final Component projectBomComponent = convert(mavenProject.getArtifact());
components.put(projectBomComponent.getPurl(), projectBomComponent);
topLevelComponents.add(projectBomComponent.getPurl());

populateComponents(topLevelComponents, components, mavenProject.getArtifacts(), null);
populateComponents(topLevelComponents, components, bomDependencies.getArtifacts(), null);

projectDependencies.forEach(dependencies::putIfAbsent);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
*/
package org.cyclonedx.maven;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.DefaultProjectBuildingRequest;
Expand All @@ -34,12 +37,13 @@
import org.eclipse.aether.artifact.ArtifactProperties;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.util.graph.transformer.ConflictResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
Expand All @@ -66,36 +70,47 @@ public class DefaultProjectDependenciesConverter implements ProjectDependenciesC
private MavenDependencyScopes include;

@Override
public Map<String, Dependency> extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludeTypes) throws MojoExecutionException {
public BomDependencies extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludeTypes) throws MojoExecutionException {
this.include = include;
excludeTypesSet = new HashSet<>(Arrays.asList(excludeTypes));

final ProjectBuildingRequest buildingRequest = getProjectBuildingRequest(mavenProject);

final Map<String, String> resolvedPUrls = generateResolvedPUrls(mavenProject);

final Map<String, Dependency> dependencies = new LinkedHashMap<>();
final Map<String, Artifact> mavenArtifacts = new LinkedHashMap<>();
try {
final DelegatingRepositorySystem delegateRepositorySystem = new DelegatingRepositorySystem(aetherRepositorySystem);
final DependencyCollectorBuilder dependencyCollectorBuilder = new DefaultDependencyCollectorBuilder(delegateRepositorySystem);
dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, null);

final org.apache.maven.shared.dependency.graph.DependencyNode mavenRoot = dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, null);
populateArtifactMap(mavenArtifacts, mavenRoot, false);

final CollectResult collectResult = delegateRepositorySystem.getCollectResult();
if (collectResult == null) {
throw new MojoExecutionException("Failed to generate aether dependency graph");
}
final DependencyNode root = collectResult.getRoot();

// Generate the tree, removing excluded and filtered nodes
final Set<String> loggedReplacementPUrls = new HashSet<>();
final Set<String> loggedFilteredArtifacts = new HashSet<>();

buildDependencyGraphNode(dependencies, root, null, null, resolvedPUrls, loggedReplacementPUrls, loggedFilteredArtifacts);
buildDependencyGraphNode(dependencies, root, null, null, loggedFilteredArtifacts);
} catch (DependencyCollectorBuilderException e) {
// When executing makeAggregateBom, some projects may not yet be built. Workaround is to warn on this
// rather than throwing an exception https://github.com/CycloneDX/cyclonedx-maven-plugin/issues/55
logger.warn("An error occurred building dependency graph: " + e.getMessage());
}
return dependencies;
return new BomDependencies(dependencies, mavenArtifacts);
}

private void populateArtifactMap(final Map<String, Artifact> artifactMap, final org.apache.maven.shared.dependency.graph.DependencyNode node, final boolean resolve) {
final Artifact artifact = node.getArtifact();
final String purl = modelConverter.generatePackageUrl(artifact);
artifactMap.putIfAbsent(purl, artifact);

for (org.apache.maven.shared.dependency.graph.DependencyNode child: node.getChildren()) {
populateArtifactMap(artifactMap, child, true);
}
}

private boolean isFilteredNode(final DependencyNode node, final Set<String> loggedFilteredArtifacts) {
Expand Down Expand Up @@ -129,10 +144,10 @@ private boolean isFilteredNode(final DependencyNode node, final Set<String> logg
scoped = Boolean.FALSE;
}
final boolean result = Boolean.FALSE.equals(scoped);
if (result) {
if (result && logger.isDebugEnabled()) {
final String purl = modelConverter.generatePackageUrl(node.getArtifact());
final String key = purl + ":" + originalScope + ":" + node.getDependency().getScope();
if (loggedFilteredArtifacts.add(key) && logger.isDebugEnabled()) {
if (loggedFilteredArtifacts.add(key)) {
logger.debug("Filtering " + purl + " with original scope " + originalScope + " and scope " + node.getDependency().getScope());
}
}
Expand All @@ -145,9 +160,7 @@ private boolean isExcludedNode(final DependencyNode node) {
}

private void buildDependencyGraphNode(final Map<String, Dependency> dependencies, DependencyNode node,
final Dependency parent, final String parentClassifierlessPUrl, final Map<String, String> resolvedPUrls,
final Set<String> loggedReplacementPUrls, final Set<String> loggedFilteredArtifacts) {
String purl = modelConverter.generatePackageUrl(node.getArtifact());
final Dependency parent, final String parentClassifierlessPUrl, final Set<String> loggedFilteredArtifacts) {

if (isExcludedNode(node) || (parent != null && isFilteredNode(node, loggedFilteredArtifacts))) {
return;
Expand All @@ -157,50 +170,26 @@ private void buildDependencyGraphNode(final Map<String, Dependency> dependencies
if (node.getChildren().isEmpty()) {
final Map<?,?> nodeData = node.getData();
final DependencyNode winner = (DependencyNode) nodeData.get(ConflictResolver.NODE_DATA_WINNER);
final String resolvedPurl = resolvedPUrls.get(modelConverter.generateVersionlessPackageUrl(node.getArtifact()));
if (!purl.equals(resolvedPurl)) {
if (!loggedReplacementPUrls.contains(purl)) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing reference to " + purl + " with resolved package url " + resolvedPurl);
}
loggedReplacementPUrls.add(purl);
}
purl = resolvedPurl;
}
if (winner != null) {
node = winner;
}
}

Dependency topDependency = new Dependency(purl);
final Dependency origDependency = dependencies.putIfAbsent(purl, topDependency);
if (origDependency != null) {
topDependency = origDependency;
}
if (parent != null) {
parent.addDependency(new Dependency(purl));
}

final String nodeClassifierlessPUrl = modelConverter.generateClassifierlessPackageUrl(node.getArtifact());
if (!nodeClassifierlessPUrl.equals(parentClassifierlessPUrl)) {
for (final DependencyNode childrenNode : node.getChildren()) {
buildDependencyGraphNode(dependencies, childrenNode, topDependency, nodeClassifierlessPUrl, resolvedPUrls, loggedReplacementPUrls, loggedFilteredArtifacts);
String purl = modelConverter.generatePackageUrl(node.getArtifact());
if (!dependencies.containsKey(purl)) {
Dependency topDependency = new Dependency(purl);
dependencies.put(purl, topDependency);
final String nodeClassifierlessPUrl = modelConverter.generateClassifierlessPackageUrl(node.getArtifact());
if (!nodeClassifierlessPUrl.equals(parentClassifierlessPUrl)) {
for (final DependencyNode childrenNode : node.getChildren()) {
buildDependencyGraphNode(dependencies, childrenNode, topDependency, nodeClassifierlessPUrl, loggedFilteredArtifacts);
}
}
}
}

/**
* Generate a map of versionless purls to their resolved versioned purl
* @return the map of versionless purls to resolved versioned purls
*/
private Map<String, String> generateResolvedPUrls(final MavenProject mavenProject) {
final Map<String, String> resolvedPUrls = new HashMap<>();
final Artifact projectArtifact = mavenProject.getArtifact();
resolvedPUrls.put(modelConverter.generateVersionlessPackageUrl(projectArtifact), modelConverter.generatePackageUrl(projectArtifact));
for (Artifact artifact: mavenProject.getArtifacts()) {
resolvedPUrls.put(modelConverter.generateVersionlessPackageUrl(artifact), modelConverter.generatePackageUrl(artifact));
if (parent != null) {
parent.addDependency(new Dependency(purl));
}
return resolvedPUrls;
}

/**
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/cyclonedx/maven/DelegatingRepositorySystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.deployment.DeployResult;
import org.eclipse.aether.deployment.DeploymentException;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.installation.InstallRequest;
import org.eclipse.aether.installation.InstallResult;
import org.eclipse.aether.installation.InstallationException;
Expand All @@ -35,6 +37,7 @@
import org.eclipse.aether.resolution.VersionRequest;
import org.eclipse.aether.resolution.VersionResolutionException;
import org.eclipse.aether.resolution.VersionResult;
import org.eclipse.aether.util.graph.visitor.TreeDependencyVisitor;

/**
* Maven Resolver (Aether) repository system that delegates to provided system, but keep tracks of
Expand All @@ -58,6 +61,27 @@ public CollectResult getCollectResult() {
public CollectResult collectDependencies(final RepositorySystemSession session, final CollectRequest request)
throws DependencyCollectionException {
collectResult = delegate.collectDependencies(session, request);
final DependencyNode root = collectResult.getRoot();
root.accept(new TreeDependencyVisitor( new DependencyVisitor()
{
@Override
public boolean visitEnter(final DependencyNode node)
{
if (root != node)
try {
final ArtifactResult resolveArtifact = resolveArtifact(session, new ArtifactRequest(node));
node.setArtifact(resolveArtifact.getArtifact());
} catch (ArtifactResolutionException e) {}
return true;
}

@Override
public boolean visitLeave(final DependencyNode dependencyNode)
{
return true;
}
} ) );

return collectResult;
}

Expand Down
31 changes: 18 additions & 13 deletions src/main/java/org/cyclonedx/maven/ProjectDependenciesConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,13 @@
*/
package org.cyclonedx.maven;

import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Dependency;
import org.cyclonedx.model.Metadata;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;

/**
Expand All @@ -36,7 +33,7 @@
*/
public interface ProjectDependenciesConverter {

Map<String, Dependency> extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludes) throws MojoExecutionException;
BomDependencies extractBOMDependencies(MavenProject mavenProject, MavenDependencyScopes include, String[] excludes) throws MojoExecutionException;

/**
* Check consistency between BOM components and BOM dependencies, and cleanup: drop components found while walking the
Expand All @@ -58,15 +55,23 @@ public MavenDependencyScopes(boolean compile, boolean provided, boolean runtime,
this.test = test;
this.system = system;
}
}

public static class BomDependencies {
private final Map<String, Dependency> dependencies;
private final Map<String, Artifact> artifacts;

public BomDependencies(final Map<String, Dependency> dependencies, final Map<String, Artifact> artifacts) {
this.dependencies = dependencies;
this.artifacts = artifacts;
}

public final Map<String, Dependency> getDependencies() {
return dependencies;
}

public ArtifactFilter getArtifactFilter() {
final Collection<String> scope = new HashSet<>();
if (compile) scope.add("compile");
if (provided) scope.add("provided");
if (runtime) scope.add("runtime");
if (system) scope.add("system");
if (test) scope.add("test");
return new CumulativeScopeArtifactFilter(scope);
public final Map<String, Artifact> getArtifacts() {
return artifacts;
}
}
}
Loading

0 comments on commit f154d43

Please sign in to comment.