diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java index 2a6cd65081a99..6a500d154f9ea 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/dependency/ConditionalDependenciesEnabler.java @@ -18,7 +18,6 @@ import io.quarkus.gradle.tooling.dependency.ExtensionDependency; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; -import io.quarkus.maven.dependency.GACT; import io.quarkus.runtime.LaunchMode; public class ConditionalDependenciesEnabler { @@ -26,7 +25,7 @@ public class ConditionalDependenciesEnabler { /** * Links dependencies to extensions */ - private final Map>> featureVariants = new HashMap<>(); + private final Map>> featureVariants = new HashMap<>(); /** * Despite its name, only contains extensions which have no conditional dependencies, or have * resolved their conditional dependencies. @@ -133,10 +132,11 @@ private boolean resolveConditionalDependency(Dependency conditionalDep, LaunchMo // Once the dependency is found, reload the extension info from within final ExtensionDependency extensionDependency = DependencyUtils.getExtensionInfoOrNull(project, artifact); // Now check if this conditional dependency is resolved given the latest graph evolution - if (extensionDependency != null && (extensionDependency.getDependencyConditions().isEmpty() - || exist(extensionDependency.getDependencyConditions()))) { + if (extensionDependency == null || + (extensionDependency.getDependencyConditions().isEmpty() || + exist(extensionDependency.getDependencyConditions()))) { satisfied = true; - enableConditionalDependency(extensionDependency.getExtensionId()); + enableConditionalDependency(artifact.getModuleVersion().getId()); break; } } @@ -207,12 +207,12 @@ public boolean exists(ExtensionDependency dependency) { .contains(ArtifactKey.of(dependency.getGroup(), dependency.getName(), null, ArtifactCoords.TYPE_JAR)); } - private static GACT getFeatureKey(ModuleVersionIdentifier version) { - return new GACT(version.getGroup(), version.getName()); + private static ArtifactKey getFeatureKey(ModuleVersionIdentifier version) { + return ArtifactKey.ga(version.getGroup(), version.getName()); } - private static GACT getFeatureKey(Dependency version) { - return new GACT(version.getGroup(), version.getName()); + private static ArtifactKey getFeatureKey(Dependency version) { + return ArtifactKey.ga(version.getGroup(), version.getName()); } private static ArtifactKey getKey(ResolvedArtifact a) { diff --git a/docs/src/main/asciidoc/conditional-extension-dependencies.adoc b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc index f61ccc4847ea9..eeb1fac75896d 100644 --- a/docs/src/main/asciidoc/conditional-extension-dependencies.adoc +++ b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc @@ -9,19 +9,19 @@ include::_attributes.adoc[] :summary: Trigger the inclusion on additional extensions based on certain conditions. :topics: extensions -Quarkus extension dependencies are usually configured in the same way as any other project dependencies in a project's build file, e.g. the Maven `pom.xml` or the Gradle build scripts. However, Quarkus also supports types of dependencies that aren't supported out-of-the-box by Maven and Gradle. Conditional Quarkus extension dependencies falls into that category. +Quarkus extension dependencies are usually configured in the same way as any other project dependencies in a project's build file, for example the Maven `pom.xml` or the Gradle build scripts. However, Quarkus also supports types of dependencies that aren't supported out-of-the-box by Maven and Gradle. Conditional Quarkus extension dependencies is one such example. == Conditional Dependencies -A conditional dependency is a dependency that is activated only if a certain condition is satisfied. If the condition is not satisfied then the dependency **must not** be activated. In that regard, conditional dependencies can be categorized as optional, meaning they may or may not appear in the resulting dependency graph. +A conditional dependency is a dependency that is activated only if a certain condition is satisfied. If the condition is not satisfied then the dependency **will not** be activated. In that regard, conditional dependencies can be categorized as optional, meaning they may or may not appear in the resulting dependency graph. A typical example of a conditional dependency would be a component that should be added to the classpath **only** in case all of its required dependencies are present on the classpath. If one or more of the component's required dependencies aren't available, instead of failing, the component should simply not be added. == Conditional Quarkus Extension Dependencies -A Quarkus extension may declare one or more conditional dependencies on other Quarkus extensions. Conditional dependencies on and from non-extension artifacts aren't supported. +A Quarkus extension may declare one or more conditional dependencies on other Quarkus extensions or regular Maven artifacts. -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. +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: @@ -66,11 +66,13 @@ The condition which triggers activation of an extension is configured in the ext <3> configuration of the dependency condition which will have to be satisfied for this extension to be added to a Quarkus application expressed as a list of artifacts that must be present among the application dependencies; <4> an artifact key (in the format of `groupId:artifactId[::]` but typically simply `:`) of the artifact that must be present among the application dependencies for the condition to be satisfied. -NOTE: In the example above the `artifact` used in the condition configuration happens to be a runtime Quarkus extension artifact but it could as well be any other artifact. There could also be more than one `artifact` element in the body of the `dependencyCondition`. +NOTE: In the example above the `artifact` used in the condition configuration happens to be a runtime Quarkus extension artifact but it could as well be any other artifact. + +The `dependencyCondition` element may contain more than `artifact`, in which case all the configured artifacts must be present on the classpath for the condition to be satisfied. Now, having a dependency condition recorded in the descriptor of the `quarkus-extension-b`, other extensions may declare a conditional dependency on it. -NOTE: extensions with dependency conditions present in their metadata could still appear as regular dependencies in Maven `pom.xml` and Gradle build scripts. +NOTE: extensions with dependency conditions present in their metadata could still appear as regular dependencies in Maven `pom.xml` and Gradle build scripts, in which case their conditions will simply be ignored. A conditional dependency is configured in the runtime artifact of a Quarkus extension. In this example, the `quarkus-extension-a` will declare a conditional dependency on the `quarkus-extension-b`, which can be done in the following two ways. @@ -212,7 +214,7 @@ Dev mode-only extension dependencies can be configured in the Quarkus extension <3> - org.acme:quarkus-extension-b:${b.version} <4> + org.acme:quarkus-extension-b:${b.version} <4> @@ -223,11 +225,58 @@ 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> conditional dependencies that should be evaluated only in dev mode; +<4> the artifact coordinates of a conditional dependency. 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 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). + +=== Dev mode dependencies on regular Maven artifacts + +Extensions may also declare conditional dependencies on regular Maven artifacts, that are not Quarkus extensions. Given that regular Maven artifacts do not include Quarkus metadata, the condition for their inclusion is configured by an extension depending on them. + +For example +[source,xml] +---- + + + + + quarkus-extension-a <1> + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + process-resources + + extension-descriptor <2> + + + <3> + org.acme:library-b:${b.version} <4> + + + + + + + +---- +<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> conditional dependencies that should be evaluated only in dev mode; +<4> the artifact coordinates of a conditional dependency. + +In this example `library-b` is a regular Maven artifact that will be added as a dependency of the `quarkus-extension-a` only when an application is launched in dev mode. \ No newline at end of file diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java index 637db4e7c9bd0..3096df893caa4 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; import org.jboss.logging.Logger; @@ -22,8 +21,7 @@ public class BuildToolHelper { private final static String[] DEVMODE_REQUIRED_TASKS = new String[] { "classes" }; private final static String[] TEST_REQUIRED_TASKS = new String[] { "classes", "testClasses", "integrationTestClasses" }; - private final static List ENABLE_JAR_PACKAGING = Collections - .singletonList("-Dorg.gradle.java.compile-classpath-packaging=true"); + private final static List ENABLE_JAR_PACKAGING = List.of("-Dorg.gradle.java.compile-classpath-packaging=true"); public enum BuildTool { MAVEN("pom.xml"), @@ -61,7 +59,7 @@ public static Path getProjectDir(Path p) { } currentPath = currentPath.getParent(); } - log.warnv("Unable to find a project directory for {0}.", p.toString()); + log.warnv("Unable to find a project directory for {0}.", p); return null; } @@ -76,7 +74,7 @@ public static BuildTool findBuildTool(Path project) { } currentPath = currentPath.getParent(); } - log.warnv("Unable to find a build tool in {0} or in any parent.", project.toString()); + log.warnv("Unable to find a build tool in {0} or in any parent.", project); return null; } @@ -105,6 +103,11 @@ public static ApplicationModel enableGradleAppModelForTest(Path projectRoot) return enableGradleAppModel(projectRoot, "TEST", ENABLE_JAR_PACKAGING, TEST_REQUIRED_TASKS); } + public static ApplicationModel enableGradleAppModelForProdMode(Path projectRoot) + throws IOException, AppModelResolverException { + return enableGradleAppModel(projectRoot, "NORMAL", List.of()); + } + public static ApplicationModel enableGradleAppModel(Path projectRoot, String mode, List jvmArgs, String... tasks) throws IOException, AppModelResolverException { if (isGradleProject(projectRoot)) { 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 f018ecb455821..36fb4ddb04a90 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 @@ -44,12 +44,12 @@ public TsQuarkusExt setConditionalDeps(TsQuarkusExt... exts) { return setDescriptorProp(BootstrapConstants.CONDITIONAL_DEPENDENCIES, buf.toString()); } - public TsQuarkusExt setConditionalDevDeps(TsQuarkusExt... exts) { + public TsQuarkusExt setConditionalDevDeps(TsArtifact... artifacts) { final StringBuilder buf = new StringBuilder(); int i = 0; - buf.append(exts[i++].getRuntime().toString()); - while (i < exts.length) { - buf.append(' ').append(exts[i++].getRuntime().toString()); + buf.append(artifacts[i++]); + while (i < artifacts.length) { + buf.append(' ').append(artifacts[i++]); } return setDescriptorProp(BootstrapConstants.CONDITIONAL_DEV_DEPENDENCIES, buf.toString()); } 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 3450c3b3f0113..4f5a56898aa64 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(true); return resolver; } @@ -76,8 +76,20 @@ protected void setupDependencies() { | DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); addCollectedDeploymentDep(extF.getDeployment()); + final TsQuarkusExt extH = new TsQuarkusExt("ext-h"); + install(extH, false); + addCollectedDep(extH.getRuntime(), + DependencyFlags.RUNTIME_CP + | DependencyFlags.DEPLOYMENT_CP + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(extH.getDeployment()); + final TsArtifact devOnlyLib = TsArtifact.jar("dev-only-lib"); + devOnlyLib.addDependency(extH); + install(devOnlyLib, false); + addCollectedDep(devOnlyLib, DependencyFlags.RUNTIME_CP | DependencyFlags.DEPLOYMENT_CP); + final TsQuarkusExt extG = new TsQuarkusExt("ext-g"); - extG.setConditionalDevDeps(extB); + extG.setConditionalDevDeps(extB.getRuntime(), devOnlyLib); install(extG, false); installAsDep(extG.getRuntime(), DependencyFlags.DIRECT 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 31a2161630566..85a9ba68327b3 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 @@ -67,7 +67,7 @@ protected void setupDependencies() { addCollectedDeploymentDep(extF.getDeployment()); final TsQuarkusExt extG = new TsQuarkusExt("ext-g"); - extG.setConditionalDevDeps(extB); + extG.setConditionalDevDeps(extB.getRuntime()); install(extG, false); installAsDep(extG.getRuntime(), DependencyFlags.DIRECT 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 18312969db978..f36b18a5e1d00 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 @@ -52,7 +52,7 @@ protected void setupDependencies() { install(extE, false); final TsQuarkusExt extG = new TsQuarkusExt("ext-g"); - extG.setConditionalDevDeps(extB, extC, extE); + extG.setConditionalDevDeps(extB.getRuntime(), extC.getRuntime(), extE.getRuntime()); install(extG, false); installAsDep(extG.getRuntime(), DependencyFlags.DIRECT diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RequiredConditionalDependencyTest.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RequiredConditionalDependencyTest.java new file mode 100644 index 0000000000000..93fbcb337e63b --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RequiredConditionalDependencyTest.java @@ -0,0 +1,59 @@ +package io.quarkus.bootstrap.resolver.test; + +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.maven.dependency.DependencyFlags; + +public class RequiredConditionalDependencyTest extends CollectDependenciesBase { + + @Override + protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { + var resolver = super.newAppModelResolver(currentProject); + //resolver.setIncubatingModelResolver(false); + return resolver; + } + + @Override + protected void setupDependencies() { + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + installAsDep(extA); + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA); + install(extB, false); + addCollectedDep(extB.getRuntime(), + DependencyFlags.RUNTIME_CP + | DependencyFlags.DEPLOYMENT_CP + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(extB.getDeployment()); + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB); + install(extC, false); + addCollectedDep(extC.getRuntime(), + DependencyFlags.RUNTIME_CP + | DependencyFlags.DEPLOYMENT_CP + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(extC.getDeployment()); + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + extD.addDependency(extC); + install(extD, false); + addCollectedDep(extD.getRuntime(), + DependencyFlags.RUNTIME_CP + | DependencyFlags.DEPLOYMENT_CP + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(extD.getDeployment()); + final TsQuarkusExt extE = new TsQuarkusExt("ext-e"); + extE.addDependency(extD); + extE.setDependencyCondition(extA); + install(extE, false); + addCollectedDep(extE.getRuntime(), + DependencyFlags.RUNTIME_CP + | DependencyFlags.DEPLOYMENT_CP + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + addCollectedDeploymentDep(extE.getDeployment()); + final TsQuarkusExt extF = new TsQuarkusExt("ext-f"); + extF.setConditionalDeps(extE); + installAsDep(extF); + } +} 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 93cc4aa959b2e..252745b2202e6 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 @@ -71,9 +71,10 @@ public class ApplicationDependencyTreeResolver { private static final String QUARKUS_EXTENSION_DEPENDENCY = "quarkus.ext"; /* @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 */ // this is a temporary option, to enable the previous way of initializing runtime classpath dependencies @@ -87,8 +88,8 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { return (Artifact) dep.getData().get(QUARKUS_RUNTIME_ARTIFACT); } - private byte walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS; - private final List topExtensionDeps = new ArrayList<>(); + private byte walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS | COLLECT_DEPLOYMENT_INJECTION_POINTS; + private final List deploymentInjectionPoints = new ArrayList<>(); private ExtensionDependency currentTopLevelExtension; private final Map allExtensions = new HashMap<>(); private List conditionalDepsToProcess = new ArrayList<>(); @@ -190,48 +191,12 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver this.managedDeps = managedDeps.isEmpty() ? new ArrayList<>() : managedDeps; visitRuntimeDependencies(root.getChildren()); - - List activatedConditionalDeps = List.of(); - - if (!conditionalDepsToProcess.isEmpty()) { - activatedConditionalDeps = new ArrayList<>(); - List unsatisfiedConditionalDeps = new ArrayList<>(); - while (!conditionalDepsToProcess.isEmpty()) { - final List tmp = unsatisfiedConditionalDeps; - unsatisfiedConditionalDeps = conditionalDepsToProcess; - conditionalDepsToProcess = tmp; - final int totalConditionsToProcess = unsatisfiedConditionalDeps.size(); - final Iterator i = unsatisfiedConditionalDeps.iterator(); - while (i.hasNext()) { - final ConditionalDependency cd = i.next(); - final boolean satisfied = cd.isSatisfied(); - if (!satisfied) { - continue; - } - i.remove(); - - cd.activate(); - activatedConditionalDeps.add(cd); - } - if (totalConditionsToProcess == unsatisfiedConditionalDeps.size()) { - // none of the dependencies was satisfied - break; - } - conditionalDepsToProcess.addAll(unsatisfiedConditionalDeps); - unsatisfiedConditionalDeps.clear(); - } - } + enableConditionalDeps(); if (!runtimeModelOnly) { - for (ExtensionDependency extDep : topExtensionDeps) { + for (ExtensionDependency extDep : deploymentInjectionPoints) { injectDeploymentDependencies(extDep); } - - if (!activatedConditionalDeps.isEmpty()) { - for (ConditionalDependency cd : activatedConditionalDeps) { - injectDeploymentDependencies(cd.getExtensionDependency()); - } - } } root = normalize(originalSession, root); @@ -258,6 +223,34 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver } } + private void enableConditionalDeps() { + if (!conditionalDepsToProcess.isEmpty()) { + List unsatisfiedConditionalDeps = new ArrayList<>(); + while (!conditionalDepsToProcess.isEmpty()) { + final List tmp = unsatisfiedConditionalDeps; + unsatisfiedConditionalDeps = conditionalDepsToProcess; + conditionalDepsToProcess = tmp; + final int totalConditionsToProcess = unsatisfiedConditionalDeps.size(); + final Iterator i = unsatisfiedConditionalDeps.iterator(); + while (i.hasNext()) { + final ConditionalDependency cd = i.next(); + final boolean satisfied = cd.isSatisfied(); + if (!satisfied) { + continue; + } + i.remove(); + cd.activate(); + } + if (totalConditionsToProcess == unsatisfiedConditionalDeps.size()) { + // none of the dependencies was satisfied + break; + } + conditionalDepsToProcess.addAll(unsatisfiedConditionalDeps); + unsatisfiedConditionalDeps.clear(); + } + } + } + /** * Resolves and adds compile-only dependencies to the application model with the {@link DependencyFlags#COMPILE_ONLY} flag. * Compile-only dependencies are resolved as direct dependencies of the root with all the previously resolved dependencies @@ -533,13 +526,15 @@ private void visitExtensionDependency(ExtensionDependency extDep) collectConditionalDependencies(extDep); - if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { - clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); - topExtensionDeps.add(extDep); + if (clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { currentTopLevelExtension = extDep; } else if (currentTopLevelExtension != null) { currentTopLevelExtension.addExtensionDependency(extDep); } // else it'd be an unexpected situation + + if (clearWalkingFlag(COLLECT_DEPLOYMENT_INJECTION_POINTS)) { + deploymentInjectionPoints.add(extDep); + } } private void collectConditionalDependencies(ExtensionDependency dependent) @@ -555,19 +550,18 @@ private void collectConditionalDependencies(ExtensionDependency dependent) if (selector != null && !selector.selectDependency(new Dependency(conditionalArtifact, JavaScopes.RUNTIME))) { continue; } - final ExtensionInfo conditionalInfo = getExtensionInfoOrNull(conditionalArtifact, + conditionalArtifact = resolve(conditionalArtifact, dependent.runtimeNode.getRepositories()); + final ExtensionInfo condExtInfo = getExtensionInfoOrNull(conditionalArtifact, dependent.runtimeNode.getRepositories()); - if (conditionalInfo == null) { - log.warn(dependent.info.runtimeArtifact + " declares a conditional dependency on " + conditionalArtifact - + " that is not a Quarkus extension and will be ignored"); - continue; - } - if (conditionalInfo.activated) { + if (condExtInfo != null && condExtInfo.activated) { continue; } - final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalInfo, dependent); + final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalArtifact, condExtInfo, + dependent); conditionalDepsToProcess.add(conditionalDep); - collectConditionalDependencies(conditionalDep.getExtensionDependency()); + if (condExtInfo != null) { + collectConditionalDependencies(conditionalDep.getExtensionDependency()); + } } } @@ -718,13 +712,17 @@ private void setWalkingFlag(byte flag) { } private boolean isWalkingFlagOn(byte flag) { - return (walkingFlags & flag) > 0; + return (walkingFlags & flag) == flag; } - private void clearWalkingFlag(byte flag) { - if ((walkingFlags & flag) > 0) { - walkingFlags ^= flag; - } + /** + * Clears the flags and returns {@code true}, if the flags were actually on and cleared. + * + * @param flags the flags to clear + * @return true if the flags were on and cleared, false if the flags were not on + */ + private boolean clearWalkingFlag(byte flags) { + return walkingFlags != (walkingFlags &= (byte) (walkingFlags ^ flags)); } private static class ExtensionDependency { @@ -801,55 +799,61 @@ private boolean replaceRuntimeNode(OrderedDependencyVisitor depVisitor) { private class ConditionalDependency { + final Artifact artifact; final ExtensionInfo info; final ExtensionDependency dependent; private ExtensionDependency dependency; private boolean activated; - private ConditionalDependency(ExtensionInfo info, ExtensionDependency dependent) { - this.info = Objects.requireNonNull(info, "Extension info is null"); + private ConditionalDependency(Artifact artifact, ExtensionInfo info, ExtensionDependency dependent) { + this.artifact = Objects.requireNonNull(artifact, "artifact is null"); + this.info = info; this.dependent = dependent; } ExtensionDependency getExtensionDependency() { - if (dependency == null) { - final DefaultDependencyNode rtNode = new DefaultDependencyNode( - new Dependency(info.runtimeArtifact, JavaScopes.COMPILE)); - rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())); - rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( - new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); - rtNode.setRepositories(dependent.runtimeNode.getRepositories()); - dependency = new ExtensionDependency(info, rtNode, dependent.exclusions); + if (dependency == null && info != null) { + dependency = new ExtensionDependency(info, initDependencyNode(), dependent.exclusions); } return dependency; } + private DependencyNode initDependencyNode() { + final DefaultDependencyNode rtNode = new DefaultDependencyNode(new Dependency(artifact, JavaScopes.COMPILE)); + rtNode.setVersion(new BootstrapArtifactVersion(artifact.getVersion())); + rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( + new BootstrapArtifactVersion(artifact.getVersion()))); + rtNode.setRepositories(dependent.runtimeNode.getRepositories()); + return rtNode; + } + void activate() { if (activated) { return; } activated = true; + final DependencyNode originalNode = collectDependencies(artifact, dependent.exclusions, + dependent.runtimeNode.getRepositories()); + clearWalkingFlag((byte) (COLLECT_DIRECT_DEPS | COLLECT_TOP_EXTENSION_RUNTIME_NODES)); final ExtensionDependency extDep = getExtensionDependency(); - final DependencyNode originalNode = collectDependencies(info.runtimeArtifact, extDep.exclusions, - extDep.runtimeNode.getRepositories()); - final DefaultDependencyNode rtNode = (DefaultDependencyNode) extDep.runtimeNode; - rtNode.setRepositories(originalNode.getRepositories()); + final DependencyNode rtNode = extDep == null ? initDependencyNode() : extDep.runtimeNode; + ((DefaultDependencyNode) rtNode).setRepositories(originalNode.getRepositories()); // if this node has conditional dependencies on its own, they may have been activated by this time // in which case they would be included into its children - List currentChildren = rtNode.getChildren(); - if (currentChildren == null || currentChildren.isEmpty()) { + if (rtNode.getChildren().isEmpty()) { rtNode.setChildren(originalNode.getChildren()); } else { - currentChildren.addAll(originalNode.getChildren()); + rtNode.getChildren().addAll(originalNode.getChildren()); } currentTopLevelExtension = null; + setWalkingFlag(COLLECT_DEPLOYMENT_INJECTION_POINTS); visitRuntimeDependency(rtNode); dependent.runtimeNode.getChildren().add(rtNode); } boolean isSatisfied() { - if (info.dependencyCondition == null) { + if (info == null || info.dependencyCondition == null) { return true; } for (ArtifactKey key : info.dependencyCondition) { @@ -867,17 +871,4 @@ private static boolean isSameKey(Artifact a1, Artifact a2) { && a2.getClassifier().equals(a1.getClassifier()) && a2.getExtension().equals(a1.getExtension()); } - - private static String toCompactCoords(Artifact a) { - final StringBuilder b = new StringBuilder(); - b.append(a.getGroupId()).append(':').append(a.getArtifactId()).append(':'); - if (!a.getClassifier().isEmpty()) { - b.append(a.getClassifier()).append(':'); - } - if (!ArtifactCoords.TYPE_JAR.equals(a.getExtension())) { - b.append(a.getExtension()).append(':'); - } - b.append(a.getVersion()); - return b.toString(); - } } 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 eba8f11b77f67..23fac1e5f4725 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 @@ -785,7 +785,7 @@ Artifact getResolvedArtifact() { */ private void collectConditionalDependencies() throws BootstrapDependencyProcessingException { - if (ext.info.conditionalDeps.length == 0 || ext.conditionalDepsQueued) { + if (ext == null || ext.info.conditionalDeps.length == 0 || ext.conditionalDepsQueued) { return; } ext.conditionalDepsQueued = true; @@ -796,19 +796,17 @@ private void collectConditionalDependencies() if (selector != null && !selector.selectDependency(new Dependency(conditionalArtifact, JavaScopes.RUNTIME))) { continue; } - final ExtensionInfo conditionalInfo = getExtensionInfoOrNull(conditionalArtifact, + conditionalArtifact = resolve(conditionalArtifact, ext.runtimeNode.getRepositories()); + final ExtensionInfo condExtInfo = getExtensionInfoOrNull(conditionalArtifact, ext.runtimeNode.getRepositories()); - if (conditionalInfo == null) { - log.warn(ext.info.runtimeArtifact + " declares a conditional dependency on " + conditionalArtifact - + " that is not a Quarkus extension and will be ignored"); + if (condExtInfo != null && condExtInfo.activated) { continue; } - if (conditionalInfo.activated) { - continue; - } - final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalInfo, this); + final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalArtifact, condExtInfo, this); conditionalDepsToProcess.add(conditionalDep); - conditionalDep.conditionalDep.collectConditionalDependencies(); + if (condExtInfo != null) { + conditionalDep.conditionalDep.collectConditionalDependencies(); + } } } @@ -1044,19 +1042,15 @@ private class ConditionalDependency { final AppDep conditionalDep; private boolean activated; - private ConditionalDependency(ExtensionInfo info, AppDep parent) { + private ConditionalDependency(Artifact artifact, ExtensionInfo info, AppDep parent) { final DefaultDependencyNode rtNode = new DefaultDependencyNode( - new Dependency(info.runtimeArtifact, JavaScopes.COMPILE)); - rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())); + new Dependency(artifact, JavaScopes.COMPILE)); + rtNode.setVersion(new BootstrapArtifactVersion(artifact.getVersion())); rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( - new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); + new BootstrapArtifactVersion(artifact.getVersion()))); rtNode.setRepositories(parent.ext.runtimeNode.getRepositories()); conditionalDep = new AppDep(parent, rtNode); - conditionalDep.ext = new ExtensionDependency(info, rtNode, parent.ext.exclusions); - } - - ExtensionDependency getExtensionDependency() { - return conditionalDep.ext; + conditionalDep.ext = info == null ? null : new ExtensionDependency(info, rtNode, parent.ext.exclusions); } void activate() { @@ -1064,10 +1058,10 @@ void activate() { return; } activated = true; - final ExtensionDependency extDep = getExtensionDependency(); - final DependencyNode originalNode = collectDependencies(conditionalDep.ext.info.runtimeArtifact, extDep.exclusions, - extDep.runtimeNode.getRepositories()); - final DefaultDependencyNode rtNode = (DefaultDependencyNode) extDep.runtimeNode; + final DependencyNode originalNode = collectDependencies(conditionalDep.node.getArtifact(), + conditionalDep.parent.ext.exclusions, + conditionalDep.parent.node.getRepositories()); + final DefaultDependencyNode rtNode = (DefaultDependencyNode) conditionalDep.node; rtNode.setRepositories(originalNode.getRepositories()); // if this node has conditional dependencies on its own, they may have been activated by this time // in which case they would be included into its children @@ -1077,7 +1071,7 @@ void activate() { } else { currentChildren.addAll(originalNode.getChildren()); } - if (conditionalDep.ext.extDeps == null) { + if (conditionalDep.ext != null && conditionalDep.ext.extDeps == null) { conditionalDep.ext.extDeps = new ArrayList<>(); } visitRuntimeDeps(); @@ -1096,10 +1090,11 @@ private void visitRuntimeDeps() { } boolean isSatisfied() { - if (conditionalDep.ext.info.dependencyCondition == null) { + var extInfo = conditionalDep.ext == null ? null : conditionalDep.ext.info; + if (extInfo == null || extInfo.dependencyCondition == null) { return true; } - for (ArtifactKey key : conditionalDep.ext.info.dependencyCondition) { + for (ArtifactKey key : extInfo.dependencyCondition) { if (!isRuntimeArtifact(key)) { return false; } diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/build.gradle new file mode 100644 index 0000000000000..a01ada4d2c2fd --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/build.gradle @@ -0,0 +1,31 @@ +plugins { + id 'java-library' + id 'maven-publish' +} + +repositories { + mavenLocal { + content { + includeGroup 'org.acme' // for dependencies built in this test + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() +} + +dependencies { + api project(":ext-p") +} + +publishing { + publications { + mavenJava(MavenPublication) { + groupId = 'org.acme' + artifactId = 'dev-mode-only-lib' + version = '1.0-SNAPSHOT' + + from components.java + } + } +} \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/settings.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/settings.gradle new file mode 100644 index 0000000000000..cddd0a5d46d29 --- /dev/null +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + mavenLocal { + content { + includeGroupByRegex 'io.quarkus.*' + includeGroup 'org.hibernate.orm' + } + } + mavenCentral() + gradlePluginPortal() + } +} + +rootProject.name = 'dev-mode-only-dep' \ No newline at end of file diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/src/main/resources/META-INF/beans.xml b/integration-tests/gradle/src/main/resources/conditional-dependencies/dev-mode-only-lib/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-c/runtime/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-c/runtime/build.gradle index 9d58ab9392c82..5f2c2b5812137 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-c/runtime/build.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-c/runtime/build.gradle @@ -10,6 +10,7 @@ dependencies { quarkusExtension { deploymentArtifact = "org.acme:ext-c-deployment:1.0-SNAPSHOT" + conditionalDevDependencies = ["org.acme:dev-mode-only-lib::jar:1.0-SNAPSHOT"] } publishing { diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-p/build.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-p/build.gradle index ec31c1d4dc7e3..2279617ddf80d 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-p/build.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/ext-p/build.gradle @@ -1,5 +1,5 @@ allprojects { - group 'org.acme.p' + group 'org.acme' version '1.0-SNAPSHOT' apply plugin: 'java' diff --git a/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle b/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle index 258280bab2b60..a2c7265c372fa 100644 --- a/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle +++ b/integration-tests/gradle/src/main/resources/conditional-dependencies/settings.gradle @@ -35,4 +35,4 @@ include 'ext-r:runtime', 'ext-r:deployment' include 'ext-s:runtime', 'ext-s:deployment' include 'ext-t:runtime', 'ext-t:deployment' include 'ext-u:runtime', 'ext-u:deployment' -include 'simple-dependency', 'transitive-dependency' \ No newline at end of file +include 'simple-dependency', 'transitive-dependency', 'dev-mode-only-lib' \ No newline at end of file diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java index b260aca10bb01..99385879e01b6 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDependenciesTest.java @@ -68,7 +68,8 @@ public void publishTestExtensions() throws IOException, InterruptedException, UR ":ext-t:runtime:publishToMavenLocal", ":ext-t:deployment:publishToMavenLocal", ":ext-u:runtime:publishToMavenLocal", - ":ext-u:deployment:publishToMavenLocal"); + ":ext-u:deployment:publishToMavenLocal", + ":dev-mode-only-lib:publishToMavenLocal"); } @Test @@ -192,7 +193,7 @@ public void conditionalDevDependencies() throws Exception { // A -> D?(dev) // A -> N?(dev, G) // A -> S? (dev, T) -> U - // C + // C -> dev-mode-only-lib? (dev) -> P // T var appModel = BuildToolHelper @@ -206,12 +207,43 @@ public void conditionalDevDependencies() throws Exception { assertThat(acmeArtifacts).containsExactlyInAnyOrder( "ext-a", "ext-a-deployment", "ext-b", "ext-b-deployment", - "ext-e", "ext-e-deployment", + "ext-c", "ext-c-deployment", "ext-d", "ext-d-deployment", + "ext-e", "ext-e-deployment", + "ext-p", "ext-p-deployment", "ext-s", "ext-s-deployment", "ext-u", "ext-u-deployment", + "ext-t", "ext-t-deployment", + "simple-dependency", "transitive-dependency", + "dev-mode-only-lib"); + } + + @Test + @Order(7) + public void conditionalDevDependenciesInProdMode() throws Exception { + + // A -> B?(C) -> E?(C) + // A -> D?(dev) + // A -> N?(dev, G) + // A -> S? (dev, T) -> U + // C -> dev-mode-only-lib? (dev) -> P + // T + + var appModel = BuildToolHelper + .enableGradleAppModelForProdMode(getProjectDir("conditional-test-project/runner").toPath()); + final Set acmeArtifacts = new HashSet<>(); + for (var d : appModel.getDependencies()) { + if (d.getGroupId().equals("org.acme")) { + acmeArtifacts.add(d.getArtifactId()); + } + } + assertThat(acmeArtifacts).containsExactlyInAnyOrder( + "ext-a", "ext-a-deployment", + "ext-b", "ext-b-deployment", "ext-c", "ext-c-deployment", + "ext-e", "ext-e-deployment", "ext-t", "ext-t-deployment", "simple-dependency", "transitive-dependency"); } + } diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDevDependencyTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDevDependencyTest.java deleted file mode 100644 index 442aa33f8cb89..0000000000000 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/ConditionalDevDependencyTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.quarkus.gradle; - -import org.junit.jupiter.api.Test; - -import io.quarkus.bootstrap.utils.BuildToolHelper; - -public class ConditionalDevDependencyTest extends QuarkusGradleTestBase { - - @Test - public void test() throws Exception { - - var appModel = BuildToolHelper.enableGradleAppModelForDevMode(getProjectDir("basic-java-application-project").toPath()); - appModel.getDependencies().forEach(e -> System.out.println(e.toCompactCoords())); - } -}