From b33f07e7e57dd1253a11ee6a8d1c14eb7b7846d6 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Tue, 19 Nov 2024 18:13:26 +0100 Subject: [PATCH 1/2] Fix conditional dependency resolution when runtime-deployment dependencies are not mirrored --- ...itionMatchesConditionalDependencyTest.java | 9 + .../bootstrap/resolver/TsArtifact.java | 44 +++- .../bootstrap/resolver/TsQuarkusExt.java | 10 + ...nditionalDependenciesDevModelTestCase.java | 2 +- ...nTransitiveDeploymentArtifactTestCase.java | 64 ++++++ ...ditionalDependenciesProdModelTestCase.java | 2 +- ...endenciesRuntimeOnlyProdModelTestCase.java | 2 +- ...lDependencyWithExtraConditionTestCase.java | 2 +- .../ApplicationDependencyTreeResolver.java | 126 +++++------ .../IncubatingApplicationModelResolver.java | 212 ++++++++---------- .../maven/OrderedDependencyVisitor.java | 105 +++++++++ .../maven/OrderedDependencyVisitorTest.java | 123 ++++++++++ 12 files changed, 496 insertions(+), 205 deletions(-) create mode 100644 independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDirectDependencyOnTransitiveDeploymentArtifactTestCase.java create mode 100644 independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java create mode 100644 independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/DependencyConditionMatchesConditionalDependencyTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/DependencyConditionMatchesConditionalDependencyTest.java index 04435597762f5..51aa5660bdc3a 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/DependencyConditionMatchesConditionalDependencyTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/DependencyConditionMatchesConditionalDependencyTest.java @@ -5,14 +5,23 @@ import java.util.HashMap; import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsQuarkusExt; import io.quarkus.bootstrap.resolver.maven.IncubatingApplicationModelResolver; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.deployment.runnerjar.BootstrapFromOriginalJarTestBase; import io.quarkus.maven.dependency.ResolvedDependency; public class DependencyConditionMatchesConditionalDependencyTest extends BootstrapFromOriginalJarTestBase { + @Override + protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { + var resolver = super.newAppModelResolver(currentProject); + // resolver.setIncubatingModelResolver(true); + return resolver; + } + @Override protected TsArtifact composeApplication() { diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java index c13b3c844e6de..6b20f05b9efee 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.List; import java.util.Properties; -import java.util.function.Supplier; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; @@ -139,6 +138,10 @@ public TsArtifact addDependency(TsArtifact dep) { return addDependency(new TsDependency(dep)); } + public TsArtifact addDependency(TsArtifact dep, boolean optional) { + return addDependency(new TsDependency(dep, optional)); + } + public TsArtifact addDependency(TsArtifact dep, TsArtifact... excludes) { return addDependency(new TsDependency(dep).exclude(excludes)); } @@ -152,23 +155,23 @@ public TsArtifact addDependency(TsQuarkusExt dep, String scope) { } public TsArtifact addDependency(TsQuarkusExt dep, boolean optional) { - return addDependency(dep, () -> new TsDependency(dep.getRuntime(), optional)); + return addDependency(dep, new TsDependency(dep.getRuntime(), optional)); } public TsArtifact addDependency(TsQuarkusExt dep, String scope, boolean optional) { - return addDependency(dep, () -> new TsDependency(dep.getRuntime(), scope, optional)); + return addDependency(dep, new TsDependency(dep.getRuntime(), scope, optional)); } public TsArtifact addDependency(TsQuarkusExt dep, TsArtifact... excludes) { - return addDependency(dep, () -> new TsDependency(dep.getRuntime(), false).exclude(excludes)); + return addDependency(dep, new TsDependency(dep.getRuntime(), false).exclude(excludes)); } - private TsArtifact addDependency(TsQuarkusExt dep, Supplier dependencyFactory) { + private TsArtifact addDependency(TsQuarkusExt extDep, TsDependency dep) { if (extDeps.isEmpty()) { extDeps = new ArrayList<>(1); } - extDeps.add(dep); - return addDependency(dependencyFactory.get()); + extDeps.add(extDep); + return addDependency(dep); } public TsArtifact addDependency(TsDependency dep) { @@ -179,6 +182,33 @@ public TsArtifact addDependency(TsDependency dep) { return this; } + /** + * Adds a dependency as the first in the list. + * + * @param dep dependency to add + * @return this artifact + */ + public TsArtifact addFirstDependency(TsDependency dep) { + if (deps.isEmpty()) { + deps = new ArrayList<>(); + deps.add(dep); + } else { + deps.add(dep); + Collections.rotate(deps, 1); + } + return this; + } + + /** + * Adds a dependency as the first in the list. + * + * @param dep dependency to add + * @return this artifact + */ + public TsArtifact addFirstDependency(TsArtifact dep) { + return addFirstDependency(new TsDependency(dep)); + } + public TsArtifact addManagedDependency(TsArtifact a) { return addManagedDependency(new TsDependency(a)); } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java index e5bc50e9a7489..f018ecb455821 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java @@ -64,6 +64,16 @@ public TsQuarkusExt setDependencyCondition(TsQuarkusExt... exts) { return setDescriptorProp(BootstrapConstants.DEPENDENCY_CONDITION, buf.toString()); } + public TsQuarkusExt setDependencyCondition(TsArtifact... exts) { + final StringBuilder buf = new StringBuilder(); + int i = 0; + buf.append(exts[i++].getKey()); + while (i < exts.length) { + buf.append(' ').append(exts[i++].getKey()); + } + return setDescriptorProp(BootstrapConstants.DEPENDENCY_CONDITION, buf.toString()); + } + public TsQuarkusExt setDescriptorProp(String name, String value) { rtDescr.set(name, value); return this; diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDevModelTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDevModelTestCase.java index 20c22683e39fa..3450c3b3f0113 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDevModelTestCase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDevModelTestCase.java @@ -13,7 +13,7 @@ public class ConditionalDependenciesDevModelTestCase extends CollectDependencies @Override protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { var resolver = super.newAppModelResolver(currentProject); - resolver.setIncubatingModelResolver(false); + //resolver.setIncubatingModelResolver(false); return resolver; } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDirectDependencyOnTransitiveDeploymentArtifactTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDirectDependencyOnTransitiveDeploymentArtifactTestCase.java new file mode 100644 index 0000000000000..3ce00cea79e82 --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesDirectDependencyOnTransitiveDeploymentArtifactTestCase.java @@ -0,0 +1,64 @@ +package io.quarkus.bootstrap.resolver.test; + +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.maven.dependency.DependencyFlags; + +public class ConditionalDependenciesDirectDependencyOnTransitiveDeploymentArtifactTestCase extends CollectDependenciesBase { + + @Override + protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { + var resolver = super.newAppModelResolver(currentProject); + //resolver.setIncubatingModelResolver(true); + return resolver; + } + + @Override + protected void setupDependencies() { + + final TsQuarkusExt quarkusCore = new TsQuarkusExt("quarkus-core"); + install(quarkusCore); + + TsArtifact nettyNioClient = TsArtifact.jar("netty-nio-client"); + + final TsQuarkusExt nettyClientInternalExt = new TsQuarkusExt("netty-client-internal"); + nettyClientInternalExt.addDependency(quarkusCore); + nettyClientInternalExt.getRuntime().addDependency(nettyNioClient, true); + nettyClientInternalExt.setDependencyCondition(nettyNioClient); + install(nettyClientInternalExt, false); + addCollectedDep(nettyClientInternalExt.getRuntime(), + DependencyFlags.RUNTIME_CP | DependencyFlags.DEPLOYMENT_CP | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(nettyClientInternalExt.getDeployment()); + + final TsQuarkusExt commonExt = new TsQuarkusExt("common"); + commonExt.addDependency(quarkusCore); + commonExt.getRuntime().addDependency(nettyNioClient, true); + commonExt.getRuntime().addDependency(nettyClientInternalExt.getRuntime(), true); + commonExt.getDeployment().addDependency(nettyClientInternalExt.getDeployment(), true); + commonExt.setConditionalDeps(nettyClientInternalExt); + install(commonExt, false); + addCollectedDep(commonExt.getRuntime(), + DependencyFlags.RUNTIME_CP | DependencyFlags.DEPLOYMENT_CP | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(commonExt.getDeployment()); + + final TsQuarkusExt sqsExt = new TsQuarkusExt("sqs"); + sqsExt.addDependency(quarkusCore); + sqsExt.getRuntime().addDependency(commonExt.getRuntime()); + sqsExt.getRuntime().addDependency(nettyNioClient, true); + sqsExt.getDeployment().addFirstDependency(commonExt.getDeployment()); + addCollectedDep(sqsExt.getRuntime(), + DependencyFlags.RUNTIME_CP | DependencyFlags.DEPLOYMENT_CP | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(sqsExt.getDeployment()); + + final TsQuarkusExt messagingSqsExt = new TsQuarkusExt("messaging-sqs"); + messagingSqsExt.addDependency(quarkusCore); + messagingSqsExt.addDependency(sqsExt); + messagingSqsExt.getDeployment().addDependency(commonExt.getDeployment()); // this line breaks it + + installAsDep(messagingSqsExt); + installAsDep(nettyNioClient); + } +} diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesProdModelTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesProdModelTestCase.java index 7a470e4f666cc..31a2161630566 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesProdModelTestCase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesProdModelTestCase.java @@ -12,7 +12,7 @@ public class ConditionalDependenciesProdModelTestCase extends CollectDependencie @Override protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { var resolver = super.newAppModelResolver(currentProject); - resolver.setIncubatingModelResolver(false); + //resolver.setIncubatingModelResolver(false); return resolver; } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesRuntimeOnlyProdModelTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesRuntimeOnlyProdModelTestCase.java index cf246b9a85f2f..e33307941a83a 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesRuntimeOnlyProdModelTestCase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ConditionalDependenciesRuntimeOnlyProdModelTestCase.java @@ -14,7 +14,7 @@ public class ConditionalDependenciesRuntimeOnlyProdModelTestCase extends Collect @Override protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { var resolver = super.newAppModelResolver(currentProject); - resolver.setIncubatingModelResolver(false); + //resolver.setIncubatingModelResolver(false); resolver.setRuntimeModelOnly(runtimeOnly); return resolver; } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DevModeConditionalDependencyWithExtraConditionTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DevModeConditionalDependencyWithExtraConditionTestCase.java index 77ecac97bf92f..18312969db978 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DevModeConditionalDependencyWithExtraConditionTestCase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DevModeConditionalDependencyWithExtraConditionTestCase.java @@ -12,7 +12,7 @@ public class DevModeConditionalDependencyWithExtraConditionTestCase extends Coll @Override protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { var resolver = super.newAppModelResolver(currentProject); - resolver.setIncubatingModelResolver(false); + //resolver.setIncubatingModelResolver(false); return resolver; } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java index 6505fab69b4cd..5a9420b20a433 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java @@ -78,8 +78,6 @@ public class ApplicationDependencyTreeResolver { // this is a temporary option, to enable the previous way of initializing runtime classpath dependencies private static final boolean CONVERGED_TREE_ONLY = PropertyUtils.getBoolean("quarkus.bootstrap.converged-tree-only", false); - private static final Artifact[] NO_ARTIFACTS = new Artifact[0]; - public static ApplicationDependencyTreeResolver newInstance() { return new ApplicationDependencyTreeResolver(); } @@ -90,7 +88,7 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { private byte walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS; private final List topExtensionDeps = new ArrayList<>(); - private ExtensionDependency lastVisitedRuntimeExtNode; + private ExtensionDependency currentTopLevelExtension; private final Map allExtensions = new HashMap<>(); private List conditionalDepsToProcess = new ArrayList<>(); private final Deque> exclusionStack = new ArrayDeque<>(); @@ -433,7 +431,6 @@ private void visitRuntimeDependencies(List list) { private void visitRuntimeDependency(DependencyNode node) { final byte prevWalkingFlags = walkingFlags; - final ExtensionDependency prevLastVisitedRtExtNode = lastVisitedRuntimeExtNode; final boolean popExclusions = !node.getDependency().getExclusions().isEmpty(); if (popExclusions) { @@ -502,7 +499,6 @@ private void visitRuntimeDependency(DependencyNode node) { exclusionStack.pollLast(); } walkingFlags = prevWalkingFlags; - lastVisitedRuntimeExtNode = prevLastVisitedRtExtNode; } private ExtensionDependency getExtensionDependencyOrNull(DependencyNode node, Artifact artifact) @@ -539,11 +535,10 @@ private void visitExtensionDependency(ExtensionDependency extDep) if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); topExtensionDeps.add(extDep); - } - if (lastVisitedRuntimeExtNode != null) { - lastVisitedRuntimeExtNode.addExtensionDependency(extDep); - } - lastVisitedRuntimeExtNode = extDep; + currentTopLevelExtension = extDep; + } else if (currentTopLevelExtension != null) { + currentTopLevelExtension.addExtensionDependency(extDep); + } // else it'd be an unexpected situation } private void collectConditionalDependencies(ExtensionDependency dependent) @@ -622,8 +617,8 @@ private void injectDeploymentDependencies(ExtensionDependency extDep) clearReloadable(deploymentNode); } - final List deploymentDeps = deploymentNode.getChildren(); - if (!replaceDirectDepBranch(extDep, deploymentDeps)) { + extDep.replaceRuntimeExtensionNodes(deploymentNode); + if (!extDep.presentInTargetGraph) { throw new BootstrapDependencyProcessingException( "Quarkus extension deployment artifact " + deploymentNode.getArtifact() + " does not appear to depend on the corresponding runtime artifact " @@ -634,7 +629,7 @@ private void injectDeploymentDependencies(ExtensionDependency extDep) runtimeNode.setData(QUARKUS_RUNTIME_ARTIFACT, runtimeNode.getArtifact()); runtimeNode.setArtifact(deploymentNode.getArtifact()); runtimeNode.getDependency().setArtifact(deploymentNode.getArtifact()); - runtimeNode.setChildren(deploymentDeps); + runtimeNode.setChildren(deploymentNode.getChildren()); } private void clearReloadable(DependencyNode node) { @@ -647,61 +642,6 @@ private void clearReloadable(DependencyNode node) { } } - private boolean replaceDirectDepBranch(ExtensionDependency extDep, List deploymentDeps) - throws BootstrapDependencyProcessingException { - int i = 0; - DependencyNode inserted = null; - while (i < deploymentDeps.size()) { - final Artifact a = deploymentDeps.get(i).getArtifact(); - if (a == null) { - continue; - } - if (isSameKey(extDep.info.runtimeArtifact, a)) { - // we are not comparing the version in the above condition because the runtime version - // may appear to be different then the deployment one and that's ok - // e.g. the version of the runtime artifact could be managed by a BOM - // but overridden by the user in the project config. The way the deployment deps - // are resolved here, the deployment version of the runtime artifact will be the one from the BOM. - inserted = new DefaultDependencyNode(extDep.runtimeNode); - inserted.setChildren(extDep.runtimeNode.getChildren()); - deploymentDeps.set(i, inserted); - break; - } - ++i; - } - if (inserted == null) { - return false; - } - - if (extDep.runtimeExtensionDeps != null) { - for (ExtensionDependency dep : extDep.runtimeExtensionDeps) { - for (DependencyNode deploymentDep : deploymentDeps) { - if (deploymentDep == inserted) { - continue; - } - if (replaceRuntimeBranch(dep, deploymentDep.getChildren())) { - break; - } - } - } - } - - return true; - } - - private boolean replaceRuntimeBranch(ExtensionDependency extNode, List deploymentNodes) - throws BootstrapDependencyProcessingException { - if (replaceDirectDepBranch(extNode, deploymentNodes)) { - return true; - } - for (DependencyNode deploymentNode : deploymentNodes) { - if (replaceRuntimeBranch(extNode, deploymentNode.getChildren())) { - return true; - } - } - return false; - } - private DependencyNode collectDependencies(Artifact artifact, Collection exclusions, List repos) { final CollectRequest request; @@ -797,9 +737,10 @@ static ExtensionDependency get(DependencyNode node) { final DependencyNode runtimeNode; final Collection exclusions; boolean conditionalDepsQueued; - private List runtimeExtensionDeps; + private List extDeps; + private boolean presentInTargetGraph; - ExtensionDependency(ExtensionInfo info, DependencyNode node, Collection exclusions) { + private ExtensionDependency(ExtensionInfo info, DependencyNode node, Collection exclusions) { this.runtimeNode = node; this.info = info; this.exclusions = exclusions; @@ -814,11 +755,47 @@ static ExtensionDependency get(DependencyNode node) { } } - void addExtensionDependency(ExtensionDependency dep) { - if (runtimeExtensionDeps == null) { - runtimeExtensionDeps = new ArrayList<>(); + private void addExtensionDependency(ExtensionDependency dep) { + if (extDeps == null) { + extDeps = new ArrayList<>(); + } + extDeps.add(dep); + } + + private void replaceRuntimeExtensionNodes(DependencyNode deploymentNode) { + var deploymentVisitor = new OrderedDependencyVisitor(deploymentNode); + // skip the root node + deploymentVisitor.next(); + int nodesToReplace = extDeps == null ? 1 : extDeps.size() + 1; + while (deploymentVisitor.hasNext() && nodesToReplace > 0) { + deploymentVisitor.next(); + if (replaceRuntimeNode(deploymentVisitor)) { + --nodesToReplace; + } else if (extDeps != null) { + for (int i = 0; i < extDeps.size(); ++i) { + if (extDeps.get(i).replaceRuntimeNode(deploymentVisitor)) { + --nodesToReplace; + break; + } + } + } + } + } + + private boolean replaceRuntimeNode(OrderedDependencyVisitor depVisitor) { + if (!presentInTargetGraph && isSameKey(runtimeNode.getArtifact(), depVisitor.getCurrent().getArtifact())) { + // we are not comparing the version in the above condition because the runtime version + // may appear to be different from the deployment one and that's ok + // e.g. the version of the runtime artifact could be managed by a BOM + // but overridden by the user in the project config. The way the deployment deps + // are resolved here, the deployment version of the runtime artifact will be the one from the BOM. + var inserted = new DefaultDependencyNode(runtimeNode); + inserted.setChildren(runtimeNode.getChildren()); + depVisitor.replaceCurrent(inserted); + presentInTargetGraph = true; + return true; } - runtimeExtensionDeps.add(dep); + return false; } } @@ -866,6 +843,7 @@ void activate() { } else { currentChildren.addAll(originalNode.getChildren()); } + currentTopLevelExtension = null; visitRuntimeDependency(rtNode); dependent.runtimeNode.getChildren().add(rtNode); } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java index 95e1a93821119..aec70164caa23 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java @@ -71,9 +71,10 @@ public class IncubatingApplicationModelResolver { private static final String INCUBATING_MODEL_RESOLVER = "quarkus.bootstrap.incubating-model-resolver"; /* @formatter:off */ - private static final byte COLLECT_TOP_EXTENSION_RUNTIME_NODES = 0b001; - private static final byte COLLECT_DIRECT_DEPS = 0b010; - private static final byte COLLECT_RELOADABLE_MODULES = 0b100; + private static final byte COLLECT_TOP_EXTENSION_RUNTIME_NODES = 0b0001; + private static final byte COLLECT_DIRECT_DEPS = 0b0010; + private static final byte COLLECT_RELOADABLE_MODULES = 0b0100; + private static final byte COLLECT_DEPLOYMENT_INJECTION_POINTS = 0b1000; /* @formatter:on */ private static final Artifact[] NO_ARTIFACTS = new Artifact[0]; @@ -126,7 +127,7 @@ public static IncubatingApplicationModelResolver newInstance() { private final ExtensionInfo EXT_INFO_NONE = new ExtensionInfo(); - private final List topExtensionDeps = new ArrayList<>(); + private final List deploymentInjectionPoints = new ArrayList<>(); private final Map allExtensions = new ConcurrentHashMap<>(); private Collection conditionalDepsToProcess = new ConcurrentLinkedDeque<>(); @@ -201,11 +202,10 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver DependencyNode root = resolveRuntimeDeps(collectRtDepsRequest); processRuntimeDeps(root); - final List activatedConditionalDeps = activateConditionalDeps(); - + activateConditionalDeps(); // resolve and inject deployment dependency branches for the top (first met) runtime extension nodes if (!runtimeModelOnly) { - injectDeployment(activatedConditionalDeps); + injectDeploymentDeps(); } root = normalize(resolver.getSession(), root); populateModelBuilder(root); @@ -232,11 +232,10 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver } } - private List activateConditionalDeps() { + private void activateConditionalDeps() { if (conditionalDepsToProcess.isEmpty()) { - return List.of(); + return; } - var activatedConditionalDeps = new ArrayList(); boolean checkDependencyConditions = true; while (!conditionalDepsToProcess.isEmpty() && checkDependencyConditions) { checkDependencyConditions = false; @@ -245,7 +244,6 @@ private List activateConditionalDeps() { for (ConditionalDependency cd : unsatisfiedConditionalDeps) { if (cd.isSatisfied()) { cd.activate(); - activatedConditionalDeps.add(cd); // if a dependency was activated, the remaining not satisfied conditions should be checked again checkDependencyConditions = true; } else { @@ -253,7 +251,6 @@ private List activateConditionalDeps() { } } } - return activatedConditionalDeps; } private void populateModelBuilder(DependencyNode root) { @@ -270,43 +267,29 @@ private void populateModelBuilder(DependencyNode root) { } } - private void injectDeployment(List activatedConditionalDeps) { - final ConcurrentLinkedDeque injectQueue = new ConcurrentLinkedDeque<>(); - // non-conditional deployment branches should be added before the activated conditional ones to have consistent - // dependency graph structures - collectDeploymentDeps(injectQueue); - if (!activatedConditionalDeps.isEmpty()) { - collectConditionalDeploymentDeps(activatedConditionalDeps, injectQueue); - } - for (var inject : injectQueue) { - inject.run(); + private void injectDeploymentDeps() { + for (var dep : collectDeploymentDeps()) { + dep.injectDeploymentDependency(); } } - private void collectConditionalDeploymentDeps(List activatedConditionalDeps, - ConcurrentLinkedDeque injectQueue) { + private Collection collectDeploymentDeps() { + final ConcurrentLinkedDeque injectQueue = new ConcurrentLinkedDeque<>(); var taskRunner = new ModelResolutionTaskRunner(); - for (ConditionalDependency cd : activatedConditionalDeps) { - injectDeploymentDep(taskRunner, cd.getExtensionDependency(), injectQueue, true); + for (AppDep extDep : deploymentInjectionPoints) { + injectDeploymentDep(taskRunner, extDep, injectQueue); } taskRunner.waitForCompletion(); + return injectQueue; } - private void collectDeploymentDeps(ConcurrentLinkedDeque injectQueue) { - var taskRunner = new ModelResolutionTaskRunner(); - for (ExtensionDependency extDep : topExtensionDeps) { - injectDeploymentDep(taskRunner, extDep, injectQueue, false); - } - taskRunner.waitForCompletion(); - } - - private void injectDeploymentDep(ModelResolutionTaskRunner taskRunner, ExtensionDependency extDep, - ConcurrentLinkedDeque injectQueue, boolean conditionalDep) { + private void injectDeploymentDep(ModelResolutionTaskRunner taskRunner, AppDep extDep, + ConcurrentLinkedDeque injectQueue) { taskRunner.run(() -> { - var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact)); + var resolvedDep = appBuilder.getDependency(getKey(extDep.ext.info.deploymentArtifact)); if (resolvedDep == null) { - extDep.collectDeploymentDeps(); - injectQueue.add(() -> extDep.injectDeploymentNode(conditionalDep ? extDep.getParentDeploymentNode() : null)); + extDep.ext.collectDeploymentDeps(); + injectQueue.add(extDep); } else { // if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath // in which case we also clear the reloadable flag on it, in case it's coming from the workspace @@ -468,7 +451,7 @@ private boolean isRuntimeArtifact(ArtifactKey key) { private void processRuntimeDeps(DependencyNode root) { final AppDep appRoot = new AppDep(root); - appRoot.walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS; + appRoot.walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS | COLLECT_DEPLOYMENT_INJECTION_POINTS; if (collectReloadableModules) { appRoot.walkingFlags |= COLLECT_RELOADABLE_MODULES; } @@ -615,18 +598,8 @@ void setChildFlags() { void setFlags(byte walkingFlags) { + this.walkingFlags = walkingFlags; resolvedDep.addDependencies(allDeps); - if (ext != null) { - var parentExtDep = parent; - while (parentExtDep != null) { - if (parentExtDep.ext != null) { - parentExtDep.ext.addExtensionDependency(ext); - break; - } - parentExtDep = parentExtDep.parent; - } - ext.info.ensureActivated(appBuilder); - } var existingDep = appBuilder.getDependency(resolvedDep.getKey()); if (existingDep == null) { @@ -637,13 +610,29 @@ void setFlags(byte walkingFlags) { } else if (existingDep != resolvedDep) { throw new IllegalStateException(node.getArtifact() + " is already in the model"); } - this.walkingFlags = walkingFlags; resolvedDep.setDirect(isWalkingFlagOn(COLLECT_DIRECT_DEPS)); - if (ext != null && isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { - resolvedDep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); - clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); - topExtensionDeps.add(ext); + if (ext != null) { + ext.info.ensureActivated(appBuilder); + if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { + resolvedDep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); + } + if (isWalkingFlagOn(COLLECT_DEPLOYMENT_INJECTION_POINTS)) { + clearWalkingFlag(COLLECT_DEPLOYMENT_INJECTION_POINTS); + ext.extDeps = new ArrayList<>(); + deploymentInjectionPoints.add(this); + } else if (!ext.presentInTargetGraph) { + var parentExtDep = parent; + while (parentExtDep != null) { + if (parentExtDep.ext != null && parentExtDep.ext.extDeps != null) { + parentExtDep.ext.addExtensionDependency(ext); + break; + } + parentExtDep = parentExtDep.parent; + } + } + ext.info.ensureActivated(appBuilder); } if (isWalkingFlagOn(COLLECT_RELOADABLE_MODULES)) { if (resolvedDep.getWorkspaceModule() != null @@ -745,6 +734,13 @@ private void collectConditionalDependencies() conditionalDep.conditionalDep.collectConditionalDependencies(); } } + + private void injectDeploymentDependency() { + // if the parent is an extension then add the deployment node as a dependency of the parent's deployment node + // (that would happen when injecting conditional dependencies) + // otherwise, the runtime module is going to be replaced with the deployment node + ext.injectDependencyDependency(parent == null ? null : (parent.ext == null ? null : parent.ext.deploymentNode)); + } } private ExtensionInfo getExtensionInfoOrNull(Artifact artifact, List repos) @@ -848,7 +844,7 @@ static ExtensionDependency get(DependencyNode node) { boolean conditionalDepsQueued; private List extDeps; private DependencyNode deploymentNode; - private DependencyNode parentNode; + private boolean presentInTargetGraph; ExtensionDependency(ExtensionInfo info, DependencyNode node, Collection exclusions) { this.runtimeNode = node; @@ -865,17 +861,6 @@ static ExtensionDependency get(DependencyNode node) { } } - DependencyNode getParentDeploymentNode() { - if (parentNode == null) { - return null; - } - var ext = ExtensionDependency.get(parentNode); - if (ext == null) { - return null; - } - return ext.deploymentNode == null ? ext.parentNode : ext.deploymentNode; - } - void addExtensionDependency(ExtensionDependency dep) { if (extDeps == null) { extDeps = new ArrayList<>(); @@ -894,7 +879,9 @@ private void collectDeploymentDeps() + "or the artifact does not have any dependencies while at least a dependency on the runtime artifact " + info.runtimeArtifact + " is expected"); } - if (!replaceDirectDepBranch(deploymentNode, true)) { + + replaceRuntimeExtensionNodes(deploymentNode); + if (!presentInTargetGraph) { throw new BootstrapDependencyProcessingException( "Quarkus extension deployment artifact " + deploymentNode.getArtifact() + " does not appear to depend on the corresponding runtime artifact " @@ -902,7 +889,7 @@ private void collectDeploymentDeps() } } - private void injectDeploymentNode(DependencyNode parentDeploymentNode) { + private void injectDependencyDependency(DependencyNode parentDeploymentNode) { if (parentDeploymentNode == null) { runtimeNode.setData(QUARKUS_RUNTIME_ARTIFACT, runtimeNode.getArtifact()); runtimeNode.setArtifact(deploymentNode.getArtifact()); @@ -912,59 +899,43 @@ private void injectDeploymentNode(DependencyNode parentDeploymentNode) { } } - private boolean replaceDirectDepBranch(DependencyNode parentNode, boolean replaceRuntimeNode) { - int i = 0; - DependencyNode inserted = null; - var childNodes = parentNode.getChildren(); - while (i < childNodes.size()) { - var node = childNodes.get(i); - final Artifact a = node.getArtifact(); - if (a != null && !hasWinner(node) && isSameKey(info.runtimeArtifact, a)) { - // we are not comparing the version in the above condition because the runtime version - // may appear to be different from the deployment one and that's ok - // e.g. the version of the runtime artifact could be managed by a BOM - // but overridden by the user in the project config. The way the deployment deps - // are resolved here, the deployment version of the runtime artifact will be the one from the BOM. - if (replaceRuntimeNode) { - inserted = new DefaultDependencyNode(runtimeNode); - inserted.setChildren(runtimeNode.getChildren()); - childNodes.set(i, inserted); - } else { - inserted = runtimeNode; - } - if (this.deploymentNode == null && this.parentNode == null) { - this.parentNode = parentNode; - } - break; + void replaceRuntimeExtensionNodes(DependencyNode deploymentNode) { + var deploymentVisitor = new OrderedDependencyVisitor(deploymentNode); + // skip the root node + deploymentVisitor.next(); + int nodesToReplace = extDeps == null ? 1 : extDeps.size() + 1; + while (deploymentVisitor.hasNext() && nodesToReplace > 0) { + var node = deploymentVisitor.next(); + if (hasWinner(node)) { + continue; } - ++i; - } - if (inserted == null) { - return false; - } - - if (extDeps != null) { - var depQueue = new ArrayList<>(childNodes); - var exts = new ArrayList<>(extDeps); - for (int j = 0; j < depQueue.size(); ++j) { - var depNode = depQueue.get(j); - if (hasWinner(depNode)) { - continue; - } - for (int k = 0; k < exts.size(); ++k) { - if (exts.get(k).replaceDirectDepBranch(depNode, replaceRuntimeNode && depNode != inserted)) { - exts.remove(k); + if (replaceRuntimeNode(deploymentVisitor)) { + --nodesToReplace; + } else if (extDeps != null) { + for (int i = 0; i < extDeps.size(); ++i) { + if (extDeps.get(i).replaceRuntimeNode(deploymentVisitor)) { + --nodesToReplace; break; } } - if (exts.isEmpty()) { - break; - } - depQueue.addAll(depNode.getChildren()); } } + } - return true; + private boolean replaceRuntimeNode(OrderedDependencyVisitor depVisitor) { + if (!presentInTargetGraph && isSameKey(runtimeNode.getArtifact(), depVisitor.getCurrent().getArtifact())) { + // we are not comparing the version in the above condition because the runtime version + // may appear to be different from the deployment one and that's ok + // e.g. the version of the runtime artifact could be managed by a BOM + // but overridden by the user in the project config. The way the deployment deps + // are resolved here, the deployment version of the runtime artifact will be the one from the BOM. + var inserted = new DefaultDependencyNode(runtimeNode); + inserted.setChildren(runtimeNode.getChildren()); + depVisitor.replaceCurrent(inserted); + presentInTargetGraph = true; + return true; + } + return false; } } @@ -980,7 +951,6 @@ private ConditionalDependency(ExtensionInfo info, AppDep parent) { rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); rtNode.setRepositories(parent.ext.runtimeNode.getRepositories()); - conditionalDep = new AppDep(parent, rtNode); conditionalDep.ext = new ExtensionDependency(info, rtNode, parent.ext.exclusions); } @@ -1010,13 +980,15 @@ void activate() { if (collectReloadableModules) { conditionalDep.walkingFlags |= COLLECT_RELOADABLE_MODULES; } + conditionalDep.walkingFlags |= COLLECT_DEPLOYMENT_INJECTION_POINTS; + if (conditionalDep.ext.extDeps == null) { + conditionalDep.ext.extDeps = new ArrayList<>(); + } var taskRunner = new ModelResolutionTaskRunner(); conditionalDep.scheduleRuntimeVisit(taskRunner); taskRunner.waitForCompletion(); conditionalDep.setFlags(conditionalDep.walkingFlags); - if (conditionalDep.parent.resolvedDep == null) { - conditionalDep.parent.allDeps.add(conditionalDep.resolvedDep.getArtifactCoords()); - } else { + if (conditionalDep.parent.resolvedDep != null) { conditionalDep.parent.resolvedDep.addDependency(conditionalDep.resolvedDep.getArtifactCoords()); } conditionalDep.parent.ext.runtimeNode.getChildren().add(rtNode); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java new file mode 100644 index 0000000000000..ae23a0f8c98c6 --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitor.java @@ -0,0 +1,105 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.NoSuchElementException; + +import org.eclipse.aether.graph.DependencyNode; + +/** + * Walks a dependency tree by visiting dependencies in the order of their priorities + * from the perspective of version conflict resolution. + */ +class OrderedDependencyVisitor { + + private final Deque> stack = new ArrayDeque<>(); + private List currentList; + private int currentIndex = -1; + private int currentDistance; + private int totalOnCurrentDistance = 1; + private int totalOnNextDistance; + + /** + * The root of the dependency tree + * + * @param root the root of the dependency tree + */ + OrderedDependencyVisitor(DependencyNode root) { + currentList = List.of(root); + } + + /** + * Current dependency. + * + * @return current dependency + */ + DependencyNode getCurrent() { + ensureNonNegativeIndex(); + return currentList.get(currentIndex); + } + + /** + * Returns the current distance (depth) from the root to the level on which the current node is. + * + * @return current depth + */ + int getCurrentDistance() { + ensureNonNegativeIndex(); + return currentDistance; + } + + private void ensureNonNegativeIndex() { + if (currentIndex < 0) { + throw new RuntimeException("The visitor has not been positioned on the first dependency node yet"); + } + } + + /** + * Whether there are still not visited dependencies. + * + * @return true if there are still not visited dependencies, otherwise - false + */ + boolean hasNext() { + return !stack.isEmpty() + || currentIndex + 1 < currentList.size() + || !currentList.get(currentIndex).getChildren().isEmpty(); + } + + /** + * Returns the next dependency. + * + * @return the next dependency + */ + DependencyNode next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (currentIndex >= 0) { + var children = currentList.get(currentIndex).getChildren(); + if (!children.isEmpty()) { + stack.addLast(children); + totalOnNextDistance += children.size(); + } + if (--totalOnCurrentDistance == 0) { + ++currentDistance; + totalOnCurrentDistance = totalOnNextDistance; + totalOnNextDistance = 0; + } + } + if (++currentIndex == currentList.size()) { + currentList = stack.removeFirst(); + currentIndex = 0; + } + return currentList.get(currentIndex); + } + + /** + * Replaces the current dependency in the tree with the argument. + * + * @param newNode dependency node that should replace the current one in the tree + */ + void replaceCurrent(DependencyNode newNode) { + currentList.set(currentIndex, newNode); + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java new file mode 100644 index 0000000000000..71e9b730fd3e2 --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/resolver/maven/OrderedDependencyVisitorTest.java @@ -0,0 +1,123 @@ +package io.quarkus.bootstrap.resolver.maven; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.DefaultDependencyNode; +import org.eclipse.aether.graph.DependencyNode; +import org.junit.jupiter.api.Test; + +public class OrderedDependencyVisitorTest { + + private static final String ORG_ACME = "org.acme"; + private static final String JAR = "jar"; + private static final String VERSION = "1.0"; + + @Test + public void main() { + + var root = newNode("root"); + + // direct dependencies + var colors = newNode("colors"); + var pets = newNode("pets"); + var trees = newNode("trees"); + root.setChildren(List.of(colors, pets, trees)); + + // colors + var red = newNode("red"); + var green = newNode("green"); + var blue = newNode("blue"); + colors.setChildren(List.of(red, green, blue)); + + // pets + var dog = newNode("dog"); + var cat = newNode("cat"); + pets.setChildren(List.of(dog, cat)); + // pets, puppy + var puppy = newNode("puppy"); + dog.setChildren(List.of(puppy)); + + // trees + var pine = newNode("pine"); + trees.setChildren(List.of(pine)); + + // create a visitor + var visitor = new OrderedDependencyVisitor(root); + + // assertions + assertThat(visitor.hasNext()).isTrue(); + + // distance 0 + assertThat(visitor.next()).isSameAs(root); + assertThat(visitor.getCurrent()).isSameAs(root); + assertThat(visitor.getCurrentDistance()).isEqualTo(0); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, colors + assertThat(visitor.next()).isSameAs(colors); + assertThat(visitor.getCurrent()).isSameAs(colors); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, pets + assertThat(visitor.next()).isSameAs(pets); + assertThat(visitor.getCurrent()).isSameAs(pets); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 1, trees + assertThat(visitor.next()).isSameAs(trees); + assertThat(visitor.getCurrent()).isSameAs(trees); + assertThat(visitor.getCurrentDistance()).isEqualTo(1); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, colors, red + assertThat(visitor.next()).isSameAs(red); + assertThat(visitor.getCurrent()).isSameAs(red); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, colors, green + assertThat(visitor.next()).isSameAs(green); + assertThat(visitor.getCurrent()).isSameAs(green); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, colors, blue + assertThat(visitor.next()).isSameAs(blue); + assertThat(visitor.getCurrent()).isSameAs(blue); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, dog + assertThat(visitor.next()).isSameAs(dog); + assertThat(visitor.getCurrent()).isSameAs(dog); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, pets, cat + assertThat(visitor.next()).isSameAs(cat); + assertThat(visitor.getCurrent()).isSameAs(cat); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 2, trees, pine + assertThat(visitor.next()).isSameAs(pine); + assertThat(visitor.getCurrent()).isSameAs(pine); + assertThat(visitor.getCurrentDistance()).isEqualTo(2); + assertThat(visitor.hasNext()).isTrue(); + + // distance 3, pets, dog, puppe + assertThat(visitor.next()).isSameAs(puppy); + assertThat(visitor.getCurrent()).isSameAs(puppy); + assertThat(visitor.getCurrentDistance()).isEqualTo(3); + assertThat(visitor.hasNext()).isFalse(); + } + + private static DependencyNode newNode(String artifactId) { + return new DefaultDependencyNode(new DefaultArtifact(ORG_ACME, artifactId, JAR, VERSION)); + } +} From 5e9bef3429fe716dab6c12895f8e80505a02f029 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Sat, 23 Nov 2024 00:15:26 +0100 Subject: [PATCH 2/2] Minor conditional dependencies doc fixes --- .../asciidoc/conditional-extension-dependencies.adoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/conditional-extension-dependencies.adoc b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc index d806d20c70254..f61ccc4847ea9 100644 --- a/docs/src/main/asciidoc/conditional-extension-dependencies.adoc +++ b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc @@ -23,7 +23,7 @@ A Quarkus extension may declare one or more conditional dependencies on other Qu Let's consider the following scenario as an example: `quarkus-extension-a` has an optional dependency on `quarkus-extension-b` which should be included in a Quarkus application only if `quarkus-extension-c` is found among the application dependencies (direct or transitive). In this case, the presence of `quarkus-extension-c` is the condition which, if satisfied, will trigger inclusion of the `quarkus-extension-b` when Quarkus application dependencies are resolved. -The condition which triggers activation of an extension is configured in the extension's `META-INF/quarkus-extension.properties`, which is included in the runtime artifact of the extension.Extension developers can add the following configuration to express the condition which would have to be satisfied for the extension to be activated: +The condition which triggers activation of an extension is configured in the extension's `META-INF/quarkus-extension.properties`, which is included in the runtime artifact of the extension. Extension developers can add the following configuration to express the condition which would have to be satisfied for the extension to be activated: [source,xml] ---- @@ -182,7 +182,7 @@ In this case, the Maven dependency is not at all required in the `pom.xml` file. == Dev mode-only extension dependencies -Extensions can also declare conditional dependencies on other extensions using Dev mode as the condition or one of the conditions for those dependencies to be activated. +Extensions can also declare conditional dependencies on other extensions using dev mode as the condition or one of the conditions for those dependencies to be activated. Dev mode-only extension dependencies can be configured in the Quarkus extension plugin in the following way: @@ -223,11 +223,11 @@ Dev mode-only extension dependencies can be configured in the Quarkus extension ---- <1> the runtime Quarkus extension artifact ID; <2> the goal that generates the extension descriptor which every Quarkus runtime extension project should be configured with; -<3> the Dev mode conditional dependency configuration element; -<4> the artifact coordinates of conditional dependencies on extensions that should be evaluated only if an application is launched in Dev mode. +<3> the dev mode conditional dependency configuration element; +<4> the artifact coordinates of conditional dependencies on extensions that should be evaluated only if an application is launched in dev mode. The `quarkus-extension-b`, in this example, may or may not define its own condition to be evaluated. -If the `quarkus-extension-b` does not define a dependency condition on its own (there is no dependency condition recorded in its `META-INF/quarkus-extension.properties`), the `quarkus-extension-b` will only be added as a dependency of the `quarkus-extension-a` in Dev mode but not in other modes (prod or test). +If the `quarkus-extension-b` does not define a dependency condition on its own (there is no dependency condition recorded in its `META-INF/quarkus-extension.properties`), the `quarkus-extension-b` will only be added as a dependency of the `quarkus-extension-a` in dev mode but not in other modes (prod or test). -If the `quarkus-extension-b` does define a dependency condition on its own (a dependency condition recorded in its `META-INF/quarkus-extension.properties`), the `quarkus-extension-b` will be added as a dependency of the `quarkus-extension-a` in Dev mode only if its condition is satisfied (the artifacts it requires are present in the application dependency graph). \ No newline at end of file +If the `quarkus-extension-b` does define a dependency condition on its own (a dependency condition recorded in its `META-INF/quarkus-extension.properties`), the `quarkus-extension-b` will be added as a dependency of the `quarkus-extension-a` in dev mode only if its condition is satisfied (the artifacts it requires are present in the application dependency graph). \ No newline at end of file