From 87b971aa09501b411a456729527f653e90096575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Boutemy?= Date: Sun, 26 Feb 2023 15:26:01 +0100 Subject: [PATCH] schema version 1.1 requires components cleanup from dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hervé Boutemy --- .../cyclonedx/maven/BaseCycloneDxMojo.java | 118 +++++++++--------- .../maven/CycloneDxAggregateMojo.java | 13 +- .../org/cyclonedx/maven/CycloneDxMojo.java | 8 +- .../cyclonedx/maven/CycloneDxPackageMojo.java | 8 +- src/test/resources/bom-dependencies/pom.xml | 8 ++ 5 files changed, 85 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java index cad0c2f4..40e9d521 100644 --- a/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/BaseCycloneDxMojo.java @@ -60,7 +60,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -247,14 +246,14 @@ protected Component convert(Artifact artifact) { } /** - * Analyze the project dependencies to fill the BOM components list and their dependencies. + * Analyze the current Maven project to extract the BOM components list and their dependencies. * * @param components the components set to fill * @param dependencies the dependencies set to fill * @return the name of the analysis done to store as a BOM, or {@code null} to not save result. * @throws MojoExecutionException something weird happened... */ - protected abstract String analyze(Set components, Set dependencies) throws MojoExecutionException; + protected abstract String extractComponentsAndDependencies(Set components, Set dependencies) throws MojoExecutionException; public void execute() throws MojoExecutionException { final boolean shouldSkip = Boolean.parseBoolean(System.getProperty("cyclonedx.skip", Boolean.toString(skip))); @@ -267,48 +266,46 @@ public void execute() throws MojoExecutionException { final Set components = new LinkedHashSet<>(); final Set dependencies = new LinkedHashSet<>(); - String analysis = analyze(components, dependencies); + String analysis = extractComponentsAndDependencies(components, dependencies); if (analysis != null) { - generateBom(analysis, components, dependencies); + final Metadata metadata = modelConverter.convert(project, analysis, projectType, schemaVersion(), includeLicenseText); + cleanupBomDependencies(metadata, components, dependencies); + + generateBom(analysis, metadata, components, dependencies); } } - private void generateBom(String analysis, Set components, Set dependencies) throws MojoExecutionException { + private void generateBom(String analysis, Metadata metadata, Set components, Set dependencies) throws MojoExecutionException { try { getLog().info(MESSAGE_CREATING_BOM); final Bom bom = new Bom(); + bom.setComponents(new ArrayList<>(components)); + if (schemaVersion().getVersion() >= 1.1 && includeBomSerialNumber) { bom.setSerialNumber("urn:uuid:" + UUID.randomUUID()); } + if (schemaVersion().getVersion() >= 1.2) { - final Metadata metadata = modelConverter.convert(project, analysis, projectType, schemaVersion(), includeLicenseText); bom.setMetadata(metadata); - } - bom.setComponents(new ArrayList<>(components)); - if (schemaVersion().getVersion() >= 1.2 && dependencies != null && !dependencies.isEmpty()) { bom.setDependencies(new ArrayList<>(dependencies)); - validateBomDependencies(bom); } - if (schemaVersion().getVersion() >= 1.3) { - //if (excludeArtifactId != null && excludeTypes.length > 0) { // TODO - /* + + /*if (schemaVersion().getVersion() >= 1.3) { + if (excludeArtifactId != null && excludeTypes.length > 0) { // TODO final Composition composition = new Composition(); composition.setAggregate(Composition.Aggregate.INCOMPLETE); composition.setDependencies(Collections.singletonList(new Dependency(bom.getMetadata().getComponent().getBomRef()))); bom.setCompositions(Collections.singletonList(composition)); - */ - //} - } + } + }*/ - if (!outputFormat.trim().equalsIgnoreCase("all") - && !outputFormat.trim().equalsIgnoreCase("xml") - && !outputFormat.trim().equalsIgnoreCase("json")) { + if ("all".equalsIgnoreCase(outputFormat) + || "xml".equalsIgnoreCase(outputFormat) + || "json".equalsIgnoreCase(outputFormat)) { + saveBom(bom); + } else { getLog().error("Unsupported output format. Valid options are XML and JSON"); - return; } - - saveBom(bom); - } catch (GeneratorException | ParserConfigurationException | IOException e) { throw new MojoExecutionException("An error occurred executing " + this.getClass().getName() + ": " + e.getMessage(), e); } @@ -316,17 +313,17 @@ private void generateBom(String analysis, Set components, Set components = new HashMap<>(); - components.put(bom.getMetadata().getComponent().getBomRef(), bom.getMetadata().getComponent()); - for (Component component: bom.getComponents()) { - components.put(component.getBomRef(), component); - } + /** + * Check consistency between BOM components and BOM dependencies, and cleanup: drop components found while walking the + * Maven dependency resolution graph but that are finally not kept in the effective dependencies list. + */ + private void cleanupBomDependencies(Metadata metadata, Set components, Set dependencies) { + // map(component ref -> component) + final Map componentRefs = new HashMap<>(); + components.forEach(c -> componentRefs.put(c.getBomRef(), c)); + + // set(dependencies refs) and set(dependencies of dependencies) final Set dependencyRefs = new HashSet<>(); - for (Dependency dependency: bom.getDependencies()) { - dependencyRefs.add(dependency.getRef()); - List childDependencies = dependency.getDependencies(); - if (childDependencies != null) { - childDependencies.forEach(d -> dependencyRefs.add(d.getRef())); + final Set dependsOns = new HashSet<>(); + dependencies.forEach(d -> { + dependencyRefs.add(d.getRef()); + if (d.getDependencies() != null) { + d.getDependencies().forEach(on -> dependsOns.add(on.getRef())); } - } - // Check all components have a top level dependency - for (Entry entry: components.entrySet()) { - final String componentRef = entry.getKey(); - if (!dependencyRefs.contains(componentRef)) { + }); + + // Check all BOM components have an associated BOM dependency + for (Entry entry: componentRefs.entrySet()) { + if (!dependencyRefs.contains(entry.getKey())) { if (getLog().isDebugEnabled()) { - getLog().debug("CycloneDX: Component not used in dependency graph, pruning component from bom: " + componentRef); - } - final Component component = entry.getValue(); - if (component != null) { - bom.getComponents().remove(component); + getLog().debug("Component reference not listed in dependencies, pruning from bom components: " + entry.getKey()); } + components.remove(entry.getValue()); + } else if (!dependsOns.contains(entry.getKey())) { + getLog().warn("BOM dependency listed but is not depended upon: " + entry.getKey()); } } - // Check all transitive dependencies have a component + + // add BOM main component + Component main = metadata.getComponent(); + componentRefs.put(main.getBomRef(), main); + + // Check all BOM dependencies have a BOM component for (String dependencyRef: dependencyRefs) { - if (!components.containsKey(dependencyRef)) { - getLog().warn("CycloneDX: Dependency missing component entry: " + dependencyRef); + if (!componentRefs.containsKey(dependencyRef)) { + getLog().warn("Dependency missing component entry: " + dependencyRef); } } } @@ -406,7 +412,7 @@ private Set getExcludeTypesSet() { return excludeTypesSet; } - protected Set buildDependencyGraph(MavenProject mavenProject) throws MojoExecutionException { + protected Set buildBOMDependencies(MavenProject mavenProject) throws MojoExecutionException { final Map dependencies = new LinkedHashMap<>(); final Collection scope = new HashSet<>(); @@ -485,7 +491,7 @@ public boolean visit(final DependencyNode node) { if (hiddenNode != null) { if (!loggedPurls.contains(purl)) { if (getLog().isDebugEnabled()) { - getLog().debug("CycloneDX: Populating hidden node: " + purl); + getLog().debug("Populating hidden node: " + purl); } loggedPurls.add(purl); } @@ -505,7 +511,7 @@ public boolean visit(final DependencyNode node) { if (mavenProject != null) { // 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 - getLog().warn("An error occurred building dependency graph: " + e.getMessage()); + getLog().warn("An error occurred building Maven dependency graph: " + e.getMessage()); } else { throw new MojoExecutionException("An error occurred building dependency graph", e); } @@ -593,7 +599,7 @@ private void buildDependencyGraphNode(final Map dependen if (!purl.equals(resolvedPurl)) { if (!loggedReplacementPUrls.contains(purl)) { if (getLog().isDebugEnabled()) { - getLog().debug("CycloneDX: replacing reference to " + purl + " with resolved package url " + resolvedPurl); + getLog().debug("replacing reference to " + purl + " with resolved package url " + resolvedPurl); } loggedReplacementPUrls.add(purl); } diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java index bc2e7426..675c7a2a 100644 --- a/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java +++ b/src/main/java/org/cyclonedx/maven/CycloneDxAggregateMojo.java @@ -104,11 +104,11 @@ protected void logAdditionalParameters() { getLog().info("outputReactorProjects : " + outputReactorProjects); } - protected String analyze(final Set components, final Set dependencies) throws MojoExecutionException { + protected String extractComponentsAndDependencies(final Set components, final Set dependencies) throws MojoExecutionException { if (! getProject().isExecutionRoot()) { // non-root project: let parent class create a module-only BOM? if (outputReactorProjects) { - return super.analyze(components, dependencies); + return super.extractComponentsAndDependencies(components, dependencies); } getLog().info("Skipping CycloneDX on non-execution root"); return null; @@ -158,12 +158,13 @@ protected String analyze(final Set components, final Set projectComponentRefs.add(component.getBomRef()); } } - if (schemaVersion().getVersion() >= 1.2) { - projectDependencies.addAll(buildDependencyGraph(mavenProject)); - dependencies.addAll(projectDependencies); - } + + projectDependencies.addAll(buildBOMDependencies(mavenProject)); + dependencies.addAll(projectDependencies); } + addMavenProjectsAsDependencies(reactorProjects, dependencies); + return "makeAggregateBom"; } diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java index 5be014b5..ccec170c 100644 --- a/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java +++ b/src/main/java/org/cyclonedx/maven/CycloneDxMojo.java @@ -90,7 +90,7 @@ protected ProjectDependencyAnalysis doProjectDependencyAnalysis(MavenProject mav return null; } - protected String analyze(final Set components, final Set dependencies) throws MojoExecutionException { + protected String extractComponentsAndDependencies(final Set components, final Set dependencies) throws MojoExecutionException { final Set componentRefs = new LinkedHashSet<>(); getLog().info(MESSAGE_RESOLVING_DEPS); @@ -111,9 +111,9 @@ protected String analyze(final Set components, final Set } } } - if (schemaVersion().getVersion() >= 1.2) { - dependencies.addAll(buildDependencyGraph(null)); - } + + dependencies.addAll(buildBOMDependencies(null)); + return "makeBom"; } diff --git a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java index 974c5b52..21a482de 100644 --- a/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java +++ b/src/main/java/org/cyclonedx/maven/CycloneDxPackageMojo.java @@ -56,7 +56,7 @@ protected boolean shouldInclude(MavenProject mavenProject) { return Arrays.asList(new String[]{"war", "ear"}).contains(mavenProject.getPackaging()); } - protected String analyze(Set components, Set dependencies) throws MojoExecutionException { + protected String extractComponentsAndDependencies(Set components, Set dependencies) throws MojoExecutionException { final Set componentRefs = new LinkedHashSet<>(); getLog().info(MESSAGE_RESOLVING_DEPS); @@ -72,10 +72,10 @@ protected String analyze(Set components, Set dependencies components.add(component); } } - if (schemaVersion().getVersion() >= 1.2) { - dependencies.addAll(buildDependencyGraph(mavenProject)); - } + + dependencies.addAll(buildBOMDependencies(mavenProject)); } + return "makePackageBom"; } } diff --git a/src/test/resources/bom-dependencies/pom.xml b/src/test/resources/bom-dependencies/pom.xml index d7b3e167..59c6d838 100644 --- a/src/test/resources/bom-dependencies/pom.xml +++ b/src/test/resources/bom-dependencies/pom.xml @@ -71,6 +71,14 @@ + + + + maven-jar-plugin + 3.3.0 + + +