From 3fd3494fc6ca54db5d6181e5205ad9049a0604ff Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Tue, 24 Oct 2023 09:35:49 +0200 Subject: [PATCH] Added an option to record all direct dependencies of each component when generating a manifest --- .../quarkus/domino/DependencyTreeVisitor.java | 11 ++ .../domino/LoggingDependencyTreeVisitor.java | 14 ++ .../domino/ProjectDependencyConfig.java | 10 ++ .../domino/ProjectDependencyConfigImpl.java | 24 +++- .../domino/ProjectDependencyResolver.java | 103 +++++++++++++-- .../domino/manifest/ManifestGenerator.java | 2 +- .../quarkus/domino/manifest/Playground.java | 55 ++++++++ .../PurgingDependencyTreeVisitor.java | 125 +++++++++++++++--- .../SbomGeneratingDependencyVisitor.java | 8 ++ .../domino/manifest/SbomGenerator.java | 34 ++--- .../io/quarkus/domino/processor/NodeTask.java | 2 +- .../java/io/quarkus/domino/cli/Report.java | 14 +- .../maven/DependenciesToBuildMojo.java | 22 +++ 13 files changed, 380 insertions(+), 44 deletions(-) create mode 100644 domino/api/src/main/java/io/quarkus/domino/manifest/Playground.java diff --git a/domino/api/src/main/java/io/quarkus/domino/DependencyTreeVisitor.java b/domino/api/src/main/java/io/quarkus/domino/DependencyTreeVisitor.java index 1d76032e..c5fe956c 100644 --- a/domino/api/src/main/java/io/quarkus/domino/DependencyTreeVisitor.java +++ b/domino/api/src/main/java/io/quarkus/domino/DependencyTreeVisitor.java @@ -28,6 +28,17 @@ interface DependencyVisit { void enterDependency(DependencyVisit visit); + /** + * In case the Maven artifact resolver was configured to return verbose dependency graphs, + * this method will be called to indicate the current dependency graph node has a dependency + * on another node with the passed in coordinates whose dependencies will be walked over + * in a different branch of the graph. + * + * @param coords artifact coordinates of a dependency + */ + default void linkDependency(ArtifactCoords coords) { + } + void leaveDependency(DependencyVisit visit); void enterParentPom(DependencyVisit visit); diff --git a/domino/api/src/main/java/io/quarkus/domino/LoggingDependencyTreeVisitor.java b/domino/api/src/main/java/io/quarkus/domino/LoggingDependencyTreeVisitor.java index cdc48f22..e1f78b95 100644 --- a/domino/api/src/main/java/io/quarkus/domino/LoggingDependencyTreeVisitor.java +++ b/domino/api/src/main/java/io/quarkus/domino/LoggingDependencyTreeVisitor.java @@ -72,6 +72,20 @@ public void enterDependency(DependencyVisit visit) { logComment(sb.toString()); } + @Override + public void linkDependency(ArtifactCoords coords) { + if (!loggingEnabled) { + return; + } + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i <= level; ++i) { + sb.append(" "); + } + sb.append(coords.toCompactCoords()); + sb.append(" [linked]"); + logComment(sb.toString()); + } + @Override public void leaveDependency(DependencyVisit visit) { if (!loggingEnabled) { diff --git a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfig.java b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfig.java index 9f8111e3..820f997e 100644 --- a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfig.java +++ b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfig.java @@ -104,6 +104,14 @@ public interface ProjectDependencyConfig { */ int getLevel(); + /** + * Whether verbose dependency graphs should be enabled in the underlying dependency resolver. + * This means exposing information about winning nodes during conflict resolution. + * + * @return whether verbose dependency graphs are enabled in the resolver + */ + boolean isVerboseGraphs(); + /** * Whether to log the coordinates of the artifacts captured down to the depth specified. The default is true. * @@ -306,6 +314,8 @@ default Mutable setExcludeKeys(Collection artifactKeys) { Mutable setLevel(int level); + Mutable setVerboseGraphs(boolean verboseGraphs); + Mutable setLogArtifactsToBuild(boolean logArtifactsToBuild); Mutable setLogModulesToBuild(boolean logModulesToBuild); diff --git a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfigImpl.java b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfigImpl.java index fe3e1dc3..259ad43a 100644 --- a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfigImpl.java +++ b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyConfigImpl.java @@ -8,6 +8,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; +import org.eclipse.aether.util.artifact.JavaScopes; @JsonInclude(JsonInclude.Include.NON_DEFAULT) public class ProjectDependencyConfigImpl implements ProjectDependencyConfig { @@ -27,6 +29,7 @@ public class ProjectDependencyConfigImpl implements ProjectDependencyConfig { private final boolean excludeParentPoms; private final boolean excludeBomImports; private final int level; + private final boolean verboseGraphs; private final boolean logArtifactsToBuild; private final boolean logModulesToBuild; private final boolean logTrees; @@ -65,6 +68,7 @@ private ProjectDependencyConfigImpl(ProjectDependencyConfig other) { excludeParentPoms = other.isExcludeParentPoms(); excludeBomImports = other.isExcludeBomImports(); level = other.getLevel(); + verboseGraphs = other.isVerboseGraphs(); logArtifactsToBuild = other.isLogArtifactsToBuild(); logModulesToBuild = other.isLogModulesToBuild(); logTrees = other.isLogTrees(); @@ -159,6 +163,11 @@ public int getLevel() { return level; } + @Override + public boolean isVerboseGraphs() { + return verboseGraphs; + } + @Override public boolean isLogArtifactsToBuild() { return logArtifactsToBuild; @@ -263,11 +272,12 @@ static class Builder implements ProjectDependencyConfig.Mutable { private Collection includeArtifacts = new ArrayList<>(); private Collection includePatterns = new ArrayList<>(); private Collection excludePatterns = new ArrayList<>(); - private Collection excludeScopes = List.of("provided", "test"); + private Collection excludeScopes = Set.of(JavaScopes.PROVIDED, JavaScopes.TEST); private boolean includeNonManaged = true; private boolean excludeParentPoms; private boolean excludeBomImports; private int level = -1; + private boolean verboseGraphs; private boolean logArtifactsToBuild; private boolean logModulesToBuild; private boolean logTrees; @@ -304,6 +314,7 @@ static class Builder implements ProjectDependencyConfig.Mutable { excludeParentPoms = other.isExcludeParentPoms(); excludeBomImports = other.isExcludeBomImports(); level = other.getLevel(); + verboseGraphs = other.isVerboseGraphs(); logArtifactsToBuild = other.isLogArtifactsToBuild(); logModulesToBuild = other.isLogModulesToBuild(); logTrees = other.isLogTrees(); @@ -394,6 +405,11 @@ public int getLevel() { return level; } + @Override + public boolean isVerboseGraphs() { + return verboseGraphs; + } + @Override public boolean isLogArtifactsToBuild() { return logArtifactsToBuild; @@ -581,6 +597,12 @@ public Mutable setLevel(int level) { return this; } + @Override + public Mutable setVerboseGraphs(boolean verboseGraphs) { + this.verboseGraphs = verboseGraphs; + return this; + } + @Override public Mutable setLogArtifactsToBuild(boolean logArtifactsToBuild) { this.logArtifactsToBuild = logArtifactsToBuild; diff --git a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyResolver.java b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyResolver.java index 146f6017..46b369b1 100644 --- a/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyResolver.java +++ b/domino/api/src/main/java/io/quarkus/domino/ProjectDependencyResolver.java @@ -9,6 +9,7 @@ import io.quarkus.bom.decomposer.ReleaseIdDetector; import io.quarkus.bom.decomposer.ReleaseIdFactory; import io.quarkus.bom.decomposer.ScmRevisionResolver; +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; @@ -18,6 +19,7 @@ import io.quarkus.domino.scm.ScmRepository; import io.quarkus.domino.scm.ScmRevision; import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; @@ -49,12 +51,15 @@ import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.model.Profile; +import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.Repository; @@ -155,14 +160,55 @@ public ProjectDependencyResolver build() { private MavenArtifactResolver getInitializedResolver() { if (resolver == null) { try { + var mvnConfig = BootstrapMavenContext.config(); if (depConfig == null || depConfig.getProjectDir() == null) { - return MavenArtifactResolver.builder().setWorkspaceDiscovery(false).build(); + mvnConfig.setWorkspaceDiscovery(false); + } else { + mvnConfig.setCurrentProject(depConfig.getProjectDir().toString()) + .setEffectiveModelBuilder(true) + .setPreferPomsFromWorkspace(true); } - return MavenArtifactResolver.builder() - .setCurrentProject(depConfig.getProjectDir().toString()) - .setEffectiveModelBuilder(true) - .setPreferPomsFromWorkspace(true) - .build(); + var mvnCtx = new BootstrapMavenContext(mvnConfig); + + if (depConfig.isVerboseGraphs()) { + var session = new DefaultRepositorySystemSession(mvnCtx.getRepositorySystemSession()); + session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); + session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); + + mvnConfig.setRepositorySystemSession(session) + .setRepositorySystem(mvnCtx.getRepositorySystem()) + .setRemoteRepositoryManager(mvnCtx.getRemoteRepositoryManager()) + .setRemoteRepositories(mvnCtx.getRemoteRepositories()) + .setCurrentProject(mvnCtx.getCurrentProject()); + + mvnCtx = new BootstrapMavenContext(mvnConfig); + } + + return new MavenArtifactResolver(mvnCtx); + } catch (BootstrapMavenException e) { + throw new IllegalStateException("Failed to initialize the Maven artifact resolver", e); + } + } else if (depConfig != null + && depConfig.isVerboseGraphs() + && (Boolean.FALSE.equals( + resolver.getSession().getConfigProperties() + .getOrDefault(ConflictResolver.CONFIG_PROP_VERBOSE, false)) + || Boolean.FALSE.equals(resolver.getSession().getConfigProperties() + .getOrDefault(DependencyManagerUtils.CONFIG_PROP_VERBOSE, false)))) { + var mvnCtx = resolver.getMavenContext(); + try { + var session = new DefaultRepositorySystemSession(mvnCtx.getRepositorySystemSession()); + session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); + session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); + + mvnCtx = new BootstrapMavenContext( + BootstrapMavenContext.config().setRepositorySystemSession(session) + .setRepositorySystem(mvnCtx.getRepositorySystem()) + .setRemoteRepositoryManager(mvnCtx.getRemoteRepositoryManager()) + .setRemoteRepositories(mvnCtx.getRemoteRepositories()) + .setCurrentProject(mvnCtx.getCurrentProject())); + + return new MavenArtifactResolver(mvnCtx); } catch (BootstrapMavenException e) { throw new IllegalStateException("Failed to initialize the Maven artifact resolver", e); } @@ -682,9 +728,39 @@ private DependencyNode collectDependencies(ArtifactCoords coords, List constraints; + if (descr.getManagedDependencies().isEmpty()) { + constraints = managedDeps; + } else { + final Map map = new LinkedHashMap<>(); + constraints = new ArrayList<>(managedDeps.size()); + for (var d : managedDeps) { + var art = d.getArtifact(); + map.put(ArtifactKey.of(art.getGroupId(), art.getArtifactId(), art.getClassifier(), art.getExtension()), d); + constraints.add(d); + } + for (var d : descr.getManagedDependencies()) { + var art = d.getArtifact(); + if (!map.containsKey( + ArtifactKey.of(art.getGroupId(), art.getArtifactId(), art.getClassifier(), art.getExtension()))) { + constraints.add(d); + } + } + } + final List directDeps = new ArrayList<>(descr.getDependencies().size()); + for (var d : descr.getDependencies()) { + if (excludeScopes.contains(d.getScope()) + || d.isOptional() && !config.isIncludeOptionalDeps()) { + continue; + } + directDeps.add(d); + } + var aggregatedRepos = resolver.aggregateRepositories(resolver.getRepositories(), + resolver.newResolutionRepositories(descr.getRepositories())); + root = resolver.getSystem().collectDependencies(resolver.getSession(), - resolver.newCollectManagedRequest(a, List.of(), managedDeps, List.of(), List.of(), - excludeScopes)) + MavenArtifactResolver.newCollectRequest(a, directDeps, managedDeps, List.of(), aggregatedRepos)) .getRoot(); // if the dependencies are not found, make sure the artifact actually exists if (root.getChildren().isEmpty()) { @@ -1068,6 +1144,17 @@ private void log(String msg) { } private void processNodes(DependencyNode node, int level, boolean remaining) { + + // in case the resolver was configured to return verbose trees, check whether this node survived conflict resolution + final DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER); + if (winner != null) { + final ArtifactCoords coords = toCoords(winner.getArtifact()); + for (DependencyTreeVisitor v : treeVisitors) { + v.linkDependency(coords); + } + return; + } + final ArtifactCoords coords = toCoords(node.getArtifact()); if (isExcluded(coords)) { return; diff --git a/domino/api/src/main/java/io/quarkus/domino/manifest/ManifestGenerator.java b/domino/api/src/main/java/io/quarkus/domino/manifest/ManifestGenerator.java index 7b272ec7..f44444d3 100644 --- a/domino/api/src/main/java/io/quarkus/domino/manifest/ManifestGenerator.java +++ b/domino/api/src/main/java/io/quarkus/domino/manifest/ManifestGenerator.java @@ -310,7 +310,7 @@ static boolean resolveLicenseInfo(LicenseChoice licenseChoice, LicenseChoice lic } static Version schemaVersion() { - return Version.VERSION_14; + return Version.VERSION_15; } private static boolean doesComponentHaveExternalReference(final Component component, final ExternalReference.Type type) { diff --git a/domino/api/src/main/java/io/quarkus/domino/manifest/Playground.java b/domino/api/src/main/java/io/quarkus/domino/manifest/Playground.java new file mode 100644 index 00000000..948cb58b --- /dev/null +++ b/domino/api/src/main/java/io/quarkus/domino/manifest/Playground.java @@ -0,0 +1,55 @@ +package io.quarkus.domino.manifest; + +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.domino.ProjectDependencyConfig; +import io.quarkus.domino.ProjectDependencyResolver; +import io.quarkus.maven.dependency.ArtifactCoords; +import java.util.List; +import java.util.Set; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; + +public class Playground { + + public static void main(String[] args) throws Exception { + + var mvnCtx = new BootstrapMavenContext(BootstrapMavenContext.config().setWorkspaceDiscovery(false)); + var session = new DefaultRepositorySystemSession(mvnCtx.getRepositorySystemSession()); + if (true) { + session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); + session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); + } + + mvnCtx = new BootstrapMavenContext(BootstrapMavenContext.config() + .setRepositorySystem(mvnCtx.getRepositorySystem()) + .setRepositorySystemSession(session) + .setRemoteRepositoryManager(mvnCtx.getRemoteRepositoryManager()) + .setRemoteRepositories(mvnCtx.getRemoteRepositories())); + + var resolver = new MavenArtifactResolver(mvnCtx); + + ProjectDependencyResolver.builder() + .setArtifactResolver(resolver) + .setDependencyConfig(ProjectDependencyConfig.builder() + /* @formatter:off + .setProjectArtifacts(List.of( + ArtifactCoords.jar("org.acme", "acme-lib-b", "1.0.0-SNAPSHOT"), + ArtifactCoords.jar("org.acme", "acme-lib-a", "1.0.0-SNAPSHOT"))) + @formatter:on */ + .setProjectArtifacts(List.of( + ArtifactCoords.jar("io.quarkus", "quarkus-core-deployment", "3.2.6.Final"), + ArtifactCoords.jar("io.quarkus", "quarkus-core", "3.2.6.Final"))) + //.setIncludeOptionalDeps(true) + .setLegacyScmLocator(true) + .setExcludeScopes(Set.of("test")) + .build()) + //.addDependencyTreeVisitor(new LoggingDependencyTreeVisitor(MessageWriter.info(), false, null)) + .addDependencyTreeVisitor(new SbomGeneratingDependencyVisitor( + SbomGenerator.builder() + .setArtifactResolver(resolver))) + .build() + .resolveDependencies(); + } +} diff --git a/domino/api/src/main/java/io/quarkus/domino/manifest/PurgingDependencyTreeVisitor.java b/domino/api/src/main/java/io/quarkus/domino/manifest/PurgingDependencyTreeVisitor.java index 98c98305..740729dd 100644 --- a/domino/api/src/main/java/io/quarkus/domino/manifest/PurgingDependencyTreeVisitor.java +++ b/domino/api/src/main/java/io/quarkus/domino/manifest/PurgingDependencyTreeVisitor.java @@ -18,14 +18,18 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import org.eclipse.aether.repository.RemoteRepository; +import org.jboss.logging.Logger; public class PurgingDependencyTreeVisitor implements DependencyTreeVisitor { + private static final Logger log = Logger.getLogger(PurgingDependencyTreeVisitor.class); + private final AtomicLong nodesTotal = new AtomicLong(); private final AtomicLong uniqueNodesTotal = new AtomicLong(); private List roots; private ArrayDeque branch; private Map> nodeVariations; + private Map treeComponents; @Override public void beforeAllRoots() { @@ -41,19 +45,30 @@ public void afterAllRoots() { purge(); } - public List getRoots() { + List getRoots() { return new ArrayList<>(roots); } private void purge() { - //System.out.println("Roots total: " + roots.size()); - //System.out.println("Nodes total: " + nodesTotal); + log.infof("Roots total: %s", roots.size()); + log.infof("Nodes total: %s", nodesTotal); for (VisitedComponentImpl root : roots) { // we want to process each tree separately due to possible variations across different trees var treeProcessor = newTreeProcessor(); treeProcessor.addRoot(root); - treeProcessor.schedule().join(); + var results = treeProcessor.schedule().join(); + boolean failures = false; + for (var result : results) { + if (result.isFailure()) { + failures = true; + log.error("Failed to process " + result.getNode().getArtifactCoords(), result.getException()); + } + } + if (failures) { + throw new RuntimeException( + "Failed to record dependency graph, see the errors logged above for more detailed information"); + } } nodeVariations = null; branch = null; @@ -69,12 +84,12 @@ private void purge() { } } } - //System.out.println("Unique roots total: " + roots.size()); - //System.out.println("Unique nodes total: " + uniqueNodesTotal); + log.infof("Unique roots total: %s", roots.size()); + log.infof("Unique nodes total: %s", uniqueNodesTotal); } private ParallelTreeProcessor newTreeProcessor() { - return ParallelTreeProcessor.with(new NodeProcessor() { + return ParallelTreeProcessor.with(new NodeProcessor<>() { @Override public Long getNodeId(VisitedComponentImpl node) { return node.getIndex(); @@ -93,16 +108,15 @@ public Function 1) { for (VisitedComponentImpl variation : variations) { - if (variation.getBomRef() == null) { + if (!variation.hasBomRefsSet()) { continue; } processedVariations++; if (variation.hasMatchingDirectDeps(currentNode)) { - if (currentNode.getParent() == null) { - // root of the tree + if (currentNode.isRoot()) { currentNode.setBomRef(variation.getBomRef()); } else { - currentNode.getParent().addChild(variation); + currentNode.swap(variation); currentNode = variation; } break; @@ -121,12 +135,17 @@ public Function(); roots.add(enterNode(visit)); } @Override public void leaveRootArtifact(DependencyVisit visit) { leaveNode(); + for (var c : treeComponents.values()) { + c.resolveLinkedDependencies(treeComponents); + } + treeComponents = null; } @Override @@ -134,6 +153,14 @@ public void enterDependency(DependencyVisit visit) { enterNode(visit); } + @Override + public void linkDependency(ArtifactCoords coords) { + var parent = branch.peek(); + if (parent != null) { + parent.linkDependency(coords); + } + } + @Override public void leaveDependency(DependencyVisit visit) { leaveNode(); @@ -163,6 +190,7 @@ private VisitedComponentImpl enterNode(DependencyVisit visit) { parent.addChild(current); } branch.push(current); + treeComponents.put(visit.getCoords(), current); return current; } @@ -170,13 +198,15 @@ private void leaveNode() { branch.pop(); } - private static class VisitedComponentImpl implements VisitedComponent { + private class VisitedComponentImpl implements VisitedComponent { private final long index; private final VisitedComponentImpl parent; private final ScmRevision revision; private final ArtifactCoords coords; private final List repos; private final Map children = new HashMap<>(); + private List linkedDeps; + private List linkedParents; private String bomRef; private PackageURL purl; @@ -192,14 +222,59 @@ private long getIndex() { return index; } - private VisitedComponentImpl getParent() { - return parent; + private boolean isRoot() { + return parent == null; } private void addChild(VisitedComponentImpl c) { children.put(c.coords, c); } + private void linkDependency(ArtifactCoords coords) { + if (linkedDeps == null) { + linkedDeps = new ArrayList<>(); + } + linkedDeps.add(coords); + } + + private void linkParent(VisitedComponentImpl parent) { + if (linkedParents == null) { + linkedParents = new ArrayList<>(); + } + linkedParents.add(parent); + } + + private void resolveLinkedDependencies(Map treeComponents) { + if (linkedDeps != null) { + log.debugf("Resolving linked dependencies of %s", coords.toCompactCoords()); + for (var linked : linkedDeps) { + var c = treeComponents.get(linked); + if (c == null) { + throw new IllegalStateException("Failed to resolve linked dependency " + linked.toCompactCoords() + + " of " + this.coords.toCompactCoords() + " among " + treeComponents.keySet()); + } + log.debugf("- %s", c.coords.toCompactCoords()); + addChild(c); + c.linkParent(this); + } + linkedDeps = null; + } + } + + private void swap(VisitedComponentImpl other) { + if (!coords.equals(other.coords)) { + throw new IllegalArgumentException("Expected " + coords + " but got " + other.coords); + } + if (parent != null) { + parent.addChild(other); + } + if (linkedParents != null) { + for (var p : linkedParents) { + p.addChild(other); + } + } + } + private boolean hasMatchingDirectDeps(VisitedComponentImpl other) { if (!coords.equals(other.coords)) { throw new IllegalArgumentException( @@ -208,13 +283,12 @@ private boolean hasMatchingDirectDeps(VisitedComponentImpl other) { if (children.size() != other.children.size()) { return false; } - for (Map.Entry c : children.entrySet()) { - var child = c.getValue(); + for (var child : children.values()) { if (child.bomRef == null) { throw new IllegalStateException( coords + " node has not yet processed dependency on " + child.getArtifactCoords()); } - var otherChild = other.children.get(c.getKey()); + var otherChild = other.children.get(child.getArtifactCoords()); if (otherChild == null) { return false; } @@ -263,6 +337,18 @@ private void setBomRef(String bomRef) { this.bomRef = bomRef; } + private boolean hasBomRefsSet() { + if (bomRef == null) { + return false; + } + for (var c : children.values()) { + if (c.bomRef == null) { + return false; + } + } + return true; + } + private void initializeBomRef(long processedVariations) { if (processedVariations == 0) { bomRef = getPurl().toString(); @@ -284,6 +370,11 @@ private void initializeBomRef(long processedVariations) { sb.append(coords.getVersion()).append('#').append(processedVariations); bomRef = sb.toString(); } + + @Override + public String toString() { + return coords.toString(); + } } static PackageURL getPurl(ArtifactCoords coords) { diff --git a/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGeneratingDependencyVisitor.java b/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGeneratingDependencyVisitor.java index a94b1d8e..7d604a8c 100644 --- a/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGeneratingDependencyVisitor.java +++ b/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGeneratingDependencyVisitor.java @@ -153,6 +153,14 @@ public void enterDependency(DependencyVisit visit) { } } + @Override + public void linkDependency(ArtifactCoords coords) { + treeBuilder.linkDependency(coords); + if (validatingTreeRecorder != null) { + validatingTreeRecorder.linkDependency(coords); + } + } + @Override public void leaveDependency(DependencyVisit visit) { treeBuilder.leaveDependency(visit); diff --git a/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGenerator.java b/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGenerator.java index ec4c5eae..63265cb9 100644 --- a/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGenerator.java +++ b/domino/api/src/main/java/io/quarkus/domino/manifest/SbomGenerator.java @@ -368,23 +368,27 @@ private void addToolInfo(Metadata metadata) { } tool.setName(sb.append("SBOM Generator").toString()); - final byte[] bytes; - try { - bytes = Files.readAllBytes(toolLocation); - } catch (IOException e) { - log.warn("Failed to read the tool's binary", e); - return; - } + if (!Files.isDirectory(toolLocation)) { + final byte[] bytes; + try { + bytes = Files.readAllBytes(toolLocation); + } catch (IOException e) { + log.warn("Failed to read the tool's binary", e); + return; + } - final List hashes = new ArrayList<>(HASH_ALGS.size()); - for (String alg : HASH_ALGS) { - var hash = getHash(alg, bytes); - if (hash != null) { - hashes.add(hash); + final List hashes = new ArrayList<>(HASH_ALGS.size()); + for (String alg : HASH_ALGS) { + var hash = getHash(alg, bytes); + if (hash != null) { + hashes.add(hash); + } } - } - if (hashes != null) { - tool.setHashes(hashes); + if (!hashes.isEmpty()) { + tool.setHashes(hashes); + } + } else { + log.warn("skipping tool hashing because " + toolLocation + " appears to be a directory"); } } diff --git a/domino/api/src/main/java/io/quarkus/domino/processor/NodeTask.java b/domino/api/src/main/java/io/quarkus/domino/processor/NodeTask.java index 8df07c4b..33e2a789 100644 --- a/domino/api/src/main/java/io/quarkus/domino/processor/NodeTask.java +++ b/domino/api/src/main/java/io/quarkus/domino/processor/NodeTask.java @@ -86,7 +86,7 @@ public CompletableFuture> schedule() { for (NodeTask t : dependencies.values()) { deps[ti++] = t.schedule(); } - return cf = CompletableFuture.allOf(deps).> thenApplyAsync((v) -> { + return cf = CompletableFuture.allOf(deps).thenApplyAsync((v) -> { final Map> dependencyResults = new HashMap<>(deps.length); for (int i = 0; i < deps.length; ++i) { final TaskResult depResult = deps[i].getNow(null); diff --git a/domino/app/src/main/java/io/quarkus/domino/cli/Report.java b/domino/app/src/main/java/io/quarkus/domino/cli/Report.java index d01889b3..700fc730 100644 --- a/domino/app/src/main/java/io/quarkus/domino/cli/Report.java +++ b/domino/app/src/main/java/io/quarkus/domino/cli/Report.java @@ -11,6 +11,10 @@ @CommandLine.Command(name = "report") public class Report extends BaseDepsToBuildCommand { + private static final String MANIFEST_DEPS_NONE = "none"; + private static final String MANIFEST_DEPS_TREE = "tree"; + private static final String MANIFEST_DEPS_GRAPH = "graph"; + @CommandLine.Option(names = { "--manifest" }, description = "Generate an SBOM with dependency trees", defaultValue = "false") public boolean manifest; @@ -19,6 +23,10 @@ public class Report extends BaseDepsToBuildCommand { "--flat-manifest" }, description = "Generate an SBOM without dependency tree information", defaultValue = "false") public boolean flatManifest; + @CommandLine.Option(names = { + "--manifest-dependencies" }, description = "Strategy to manifest dependencies: none, tree (the default, based on the default conflict free dependency trees returned by the Maven resolver), graph (records all direct dependencies of each artifact)", defaultValue = "false") + public String manifestDependencies; + @CommandLine.Option(names = { "--enable-sbom-transformers" }, description = "Apply SBOM transformers found on the classpath", defaultValue = "false") public boolean enableSbomTransformers; @@ -41,6 +49,9 @@ protected void initConfig(ProjectDependencyConfig.Mutable config) { if (excludeParentPoms == null) { config.setExcludeParentPoms(true); } + if (!flatManifest) { + config.setVerboseGraphs(MANIFEST_DEPS_GRAPH.equals(manifestDependencies)); + } } } @@ -57,7 +68,8 @@ protected void initResolver(ProjectDependencyResolver.Builder resolverBuilder) { .setOutputFile(outputFile) .setProductInfo(resolverBuilder.getDependencyConfig().getProductInfo()) .setEnableTransformers(enableSbomTransformers) - .setRecordDependencies(!flatManifest), + .setRecordDependencies( + !(flatManifest || MANIFEST_DEPS_NONE.equals(manifestDependencies))), resolverBuilder.getDependencyConfig())); } } diff --git a/maven-plugin/src/main/java/io/quarkus/bom/decomposer/maven/DependenciesToBuildMojo.java b/maven-plugin/src/main/java/io/quarkus/bom/decomposer/maven/DependenciesToBuildMojo.java index 58885b8c..dd6e99e9 100644 --- a/maven-plugin/src/main/java/io/quarkus/bom/decomposer/maven/DependenciesToBuildMojo.java +++ b/maven-plugin/src/main/java/io/quarkus/bom/decomposer/maven/DependenciesToBuildMojo.java @@ -193,6 +193,20 @@ public class DependenciesToBuildMojo extends AbstractMojo { @Parameter(required = false, property = "manifest") boolean manifest; + /** + * Indicates whether to record artifact dependencies in the manifest and if so, which strategy to use. + * Supported values are: + *
  • none - do not record dependencies at all
  • + *
  • tree - record default (conflicts resolved) dependency trees returned by Maven artifact resolver (the default)
  • + *
  • graph - record all direct dependencies of each artifact
  • + */ + @Parameter(required = false, property = "manifestDependencies", defaultValue = "tree") + String manifestDependencies; + + /** + * @deprecated in favor of {@link #manifestDependencies} + */ + @Deprecated(forRemoval = true) @Parameter(required = false, property = "flatManifest") boolean flatManifest; @@ -321,6 +335,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (includeNonManaged != null) { depsConfigBuilder.setIncludeNonManaged(includeNonManaged); } + if ("none".equals(manifestDependencies)) { + flatManifest = true; + } else if (manifestDependencies.equals("graph")) { + depsConfigBuilder.setVerboseGraphs(true); + } else if (!manifestDependencies.equals("tree")) { + throw new MojoExecutionException("Unrecognized value '" + manifestDependencies + + "' for parameter manifestDependencies. Supported values include graph, tree, none"); + } final ProjectDependencyConfig dependencyConfig = depsConfigBuilder.build(); final ProjectDependencyResolver.Builder depsResolver = ProjectDependencyResolver.builder() .setArtifactResolver(resolver)