diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/CascadingConditionalDependenciesTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/CascadingConditionalDependenciesTest.java new file mode 100644 index 0000000000000..2ce5655986f9e --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/CascadingConditionalDependenciesTest.java @@ -0,0 +1,84 @@ +package io.quarkus.deployment.conditionaldeps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class CascadingConditionalDependenciesTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + final TsQuarkusExt extE = new TsQuarkusExt("ext-e"); + final TsQuarkusExt extF = new TsQuarkusExt("ext-f"); + final TsQuarkusExt extG = new TsQuarkusExt("ext-g"); + final TsQuarkusExt extH = new TsQuarkusExt("ext-h"); + + extA.setConditionalDeps(extB, extF); + + extB.setDependencyCondition(extC); + extB.setConditionalDeps(extD); + + extD.setDependencyCondition(extE); + + extE.addDependency(extC); + extE.setDependencyCondition(extA); + extE.setDependencyCondition(extG); + + extF.setDependencyCondition(extD); + + extG.setDependencyCondition(extH); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extB.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + addToExpectedLib(extE.getRuntime()); + addToExpectedLib(extF.getRuntime()); + + install(extA); + install(extB); + install(extC); + install(extD); + install(extE); + install(extF); + install(extG); + install(extH); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extA) + .addDependency(extE); + } + + @Override + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { + final Set expected = new HashSet<>(); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), "runtime")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-e-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-f-deployment", TsArtifact.DEFAULT_VERSION), "runtime")); + assertEquals(expected, new HashSet<>(deploymentDeps)); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithSingleConditionTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithSingleConditionTest.java new file mode 100644 index 0000000000000..6c38fb3c0231b --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithSingleConditionTest.java @@ -0,0 +1,53 @@ +package io.quarkus.deployment.conditionaldeps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class ConditionalDependencyWithSingleConditionTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA); + install(extB); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extB.getRuntime()); + addToExpectedLib(extC.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC) + .addDependency(extA); + } + + @Override + protected void assertAppModel(AppModel appModel) throws Exception { + final List deploymentDeps = appModel.getDeploymentDependencies(); + final Set expected = new HashSet<>(); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime")); + assertEquals(expected, new HashSet<>(deploymentDeps)); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithTwoConditionsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithTwoConditionsTest.java new file mode 100644 index 0000000000000..5f346bb2f7e0f --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithTwoConditionsTest.java @@ -0,0 +1,56 @@ +package io.quarkus.deployment.conditionaldeps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class ConditionalDependencyWithTwoConditionsTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA, extD); + install(extB); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extB.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC) + .addDependency(extA) + .addDependency(extD); + } + + @Override + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { + final Set expected = new HashSet<>(); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + assertEquals(expected, new HashSet<>(deploymentDeps)); + } +} 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 new file mode 100644 index 0000000000000..0795f851ea6c0 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/DependencyConditionMatchesConditionalDependencyTest.java @@ -0,0 +1,50 @@ +package io.quarkus.deployment.conditionaldeps; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class DependencyConditionMatchesConditionalDependencyTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA); + + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + extD.setDependencyCondition(extB); + extD.setConditionalDeps(extB); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extD); + + install(extA); + install(extB); + install(extC); + install(extD); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extB.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC) + .addDependency(extA); + } + + @Override + protected String[] expectedExtensionDependencies() { + return new String[] { + "ext-a", + "ext-b", + "ext-c", + "ext-d" + }; + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedDirectConditionalDependencyTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedDirectConditionalDependencyTest.java new file mode 100644 index 0000000000000..13ab687487d3e --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedDirectConditionalDependencyTest.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.conditionaldeps; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class ExcludedDirectConditionalDependencyTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA); + install(extB); + + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + + final TsQuarkusExt extE = new TsQuarkusExt("ext-e"); + extE.setDependencyCondition(extD); + install(extE); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB, extE); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + addToExpectedLib(extE.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC, extB.getRuntime()) + .addDependency(extA) + .addDependency(extD); + } + + @Override + protected String[] expectedExtensionDependencies() { + return new String[] { + "ext-a", + "ext-c", + "ext-d", + "ext-e" + }; + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedTransitiveConditionalDependencyTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedTransitiveConditionalDependencyTest.java new file mode 100644 index 0000000000000..3a225286fa6b4 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ExcludedTransitiveConditionalDependencyTest.java @@ -0,0 +1,43 @@ +package io.quarkus.deployment.conditionaldeps; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class ExcludedTransitiveConditionalDependencyTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA); + install(extB); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB); + + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + extD.addDependency(extC); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extD, extB.getRuntime()) + .addDependency(extA); + } + + @Override + protected String[] expectedExtensionDependencies() { + return new String[] { + "ext-a", + "ext-c", + "ext-d" + }; + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/TransitiveConditionalDepTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/TransitiveConditionalDepTest.java new file mode 100644 index 0000000000000..233f47a4b70aa --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/TransitiveConditionalDepTest.java @@ -0,0 +1,71 @@ +package io.quarkus.deployment.conditionaldeps; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class TransitiveConditionalDepTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + // these H and I are not going to be satisfied + final TsQuarkusExt extH = new TsQuarkusExt("ext-h"); + install(extH); + final TsQuarkusExt extIConditional = new TsQuarkusExt("ext-i-conditional"); + extIConditional.setDependencyCondition(extH); + install(extIConditional); + + final TsQuarkusExt extGConditional = new TsQuarkusExt("ext-g-conditional"); + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + extA.setConditionalDeps(extGConditional); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.addDependency(extA); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.addDependency(extB); + + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + extD.addDependency(extB); + + final TsQuarkusExt extEConditional = new TsQuarkusExt("ext-e-conditional"); + extEConditional.setDependencyCondition(extB); + install(extEConditional); + + final TsQuarkusExt extF = new TsQuarkusExt("ext-f"); + extF.setConditionalDeps(extEConditional, extIConditional); + + extGConditional.setDependencyCondition(extC); + extGConditional.addDependency(extF); + install(extGConditional); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extB.getRuntime()); + addToExpectedLib(extC.getRuntime()); + addToExpectedLib(extD.getRuntime()); + addToExpectedLib(extEConditional.getRuntime()); + addToExpectedLib(extF.getRuntime()); + addToExpectedLib(extGConditional.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC) + .addDependency(extD); + } + + @Override + protected String[] expectedExtensionDependencies() { + return new String[] { + "ext-a", + "ext-b", + "ext-c", + "ext-d", + "ext-e-conditional", + "ext-f", + "ext-g-conditional" + }; + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/UnsatisfiedConditionalDependencyWithTwoConditionsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/UnsatisfiedConditionalDependencyWithTwoConditionsTest.java new file mode 100644 index 0000000000000..264845f459be3 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/UnsatisfiedConditionalDependencyWithTwoConditionsTest.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.conditionaldeps; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.deployment.runnerjar.ExecutableOutputOutcomeTestBase; + +public class UnsatisfiedConditionalDependencyWithTwoConditionsTest extends ExecutableOutputOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + extB.setDependencyCondition(extA, extD); + install(extB); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setConditionalDeps(extB); + + addToExpectedLib(extA.getRuntime()); + addToExpectedLib(extC.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extC) + .addDependency(extA); + } + + @Override + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { + final Set expected = new HashSet<>(); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + expected.add(new AppDependency( + new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile")); + assertEquals(expected, new HashSet<>(deploymentDeps)); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExecutableOutputOutcomeTestBase.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExecutableOutputOutcomeTestBase.java index c6cf3c650c7b3..00013e201311c 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExecutableOutputOutcomeTestBase.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ExecutableOutputOutcomeTestBase.java @@ -27,6 +27,8 @@ import io.quarkus.bootstrap.app.AugmentResult; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsArtifact.ContentProvider; @@ -46,9 +48,16 @@ protected void addToExpectedLib(TsArtifact entry) { expectedLib.add(entry.getGroupId() + '.' + entry.getArtifactId() + '-' + entry.getVersion() + '.' + entry.getType()); } + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { + } + protected void assertAppModel(AppModel appModel) throws Exception { } + protected String[] expectedExtensionDependencies() { + return null; + } + protected TsDependency platformDescriptor() { if (platformDescriptor == null) { TsArtifact platformDescr = new TsArtifact("org.acme", @@ -107,6 +116,11 @@ protected void testCreator(QuarkusBootstrap creator) throws Exception { try { CuratedApplication curated = creator.bootstrap(); assertAppModel(curated.getAppModel()); + final String[] expectedExtensions = expectedExtensionDependencies(); + if (expectedExtensions != null) { + assertExtensionDependencies(curated.getAppModel(), expectedExtensions); + } + assertDeploymentDeps(curated.getAppModel().getDeploymentDependencies()); AugmentAction action = curated.createAugmentor(); AugmentResult outcome = action.createProductionApplication(); @@ -178,4 +192,24 @@ protected void testCreator(QuarkusBootstrap creator) throws Exception { System.clearProperty("quarkus.package.type"); } } + + private static void assertExtensionDependencies(AppModel appModel, String[] expectedExtensions) { + final Set expectedRuntime = new HashSet<>(expectedExtensions.length); + final Set expectedDeployment = new HashSet<>(expectedExtensions.length); + for (String rtId : expectedExtensions) { + expectedRuntime.add(new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, rtId, TsArtifact.DEFAULT_VERSION)); + expectedDeployment + .add(new AppArtifact(TsArtifact.DEFAULT_GROUP_ID, rtId + "-deployment", TsArtifact.DEFAULT_VERSION)); + } + + for (AppDependency dep : appModel.getUserDependencies()) { + assertTrue(expectedRuntime.contains(dep.getArtifact()), dep.getArtifact().toString()); + } + assertEquals(expectedExtensions.length, appModel.getUserDependencies().size()); + + for (AppDependency dep : appModel.getDeploymentDependencies()) { + assertTrue(expectedDeployment.contains(dep.getArtifact())); + } + assertEquals(expectedExtensions.length, appModel.getDeploymentDependencies().size()); + } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java index 091a0118a0e17..764e145dd906d 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/OptionalDepsTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashSet; +import java.util.List; import java.util.Set; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppDependency; -import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsDependency; import io.quarkus.bootstrap.resolver.TsQuarkusExt; @@ -66,7 +66,7 @@ protected TsArtifact modelApp() { } @Override - protected void assertAppModel(AppModel appModel) throws Exception { + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { final Set expected = new HashSet<>(); expected.add(new AppDependency(new AppArtifact("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), "compile", true)); expected.add( @@ -74,6 +74,6 @@ protected void assertAppModel(AppModel appModel) throws Exception { expected.add(new AppDependency(new AppArtifact("io.quarkus.bootstrap.test", "ext-b-deployment", "1"), "compile", true)); expected.add( new AppDependency(new AppArtifact("io.quarkus.bootstrap.test", "ext-d-deployment", "1"), "compile", false)); - assertEquals(expected, new HashSet<>(appModel.getDeploymentDependencies())); + assertEquals(expected, new HashSet<>(deploymentDeps)); } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java index fd05600da9303..fa09f84adb0de 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.HashSet; +import java.util.List; import java.util.Set; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppDependency; -import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsDependency; import io.quarkus.bootstrap.resolver.TsQuarkusExt; @@ -41,10 +41,10 @@ protected TsArtifact modelApp() { } @Override - protected void assertAppModel(AppModel appModel) throws Exception { + protected void assertDeploymentDeps(List deploymentDeps) throws Exception { final Set expected = new HashSet<>(); expected.add(new AppDependency(new AppArtifact("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), "compile")); expected.add(new AppDependency(new AppArtifact("io.quarkus.bootstrap.test", "ext-a-deployment-dep", "1"), "compile")); - assertEquals(expected, new HashSet<>(appModel.getDeploymentDependencies())); + assertEquals(expected, new HashSet<>(deploymentDeps)); } } diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml index cc5d536ed0996..a455d3839fdf3 100644 --- a/devtools/maven/pom.xml +++ b/devtools/maven/pom.xml @@ -145,6 +145,12 @@ + + io.quarkus + quarkus-bootstrap-core + test-jar + test + org.assertj assertj-core diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java new file mode 100644 index 0000000000000..8f72689733ea7 --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java @@ -0,0 +1,87 @@ +package io.quarkus.maven; + +import java.util.List; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.repository.RemoteRepository; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; + +/** + * Displays Quarkus application build dependency tree including the deployment ones. + */ +@Mojo(name = "dependency-tree", defaultPhase = LifecyclePhase.NONE, requiresDependencyResolution = ResolutionScope.NONE) +public class DependencyTreeMojo extends AbstractMojo { + + @Component + protected QuarkusBootstrapProvider bootstrapProvider; + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + protected MavenProject project; + + @Parameter(defaultValue = "${project.remoteRepositories}", readonly = true, required = true) + private List repos; + + /** + * Target launch mode corresponding to {@link io.quarkus.runtime.LaunchMode} for which the dependency tree should be built. + * {@link io.quarkus.runtime.LaunchMode.PROD} is the default. + */ + @Parameter(property = "mode", required = false, defaultValue = "prod") + String mode; + + protected MavenArtifactResolver resolver; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + + final StringBuilder buf = new StringBuilder(); + buf.append("Quarkus application ").append(mode.toUpperCase()).append(" mode build dependency tree:"); + getLog().info(buf.toString()); + + final AppArtifact appArtifact = new AppArtifact(project.getGroupId(), project.getArtifactId(), null, "pom", + project.getVersion()); + final BootstrapAppModelResolver modelResolver; + try { + modelResolver = new BootstrapAppModelResolver(resolver()); + if (mode != null) { + if (mode.equalsIgnoreCase("test")) { + modelResolver.setTest(true); + } else if (mode.equalsIgnoreCase("dev") || mode.equalsIgnoreCase("development")) { + modelResolver.setDevMode(true); + } else if (mode.equalsIgnoreCase("prod") || mode.isEmpty()) { + // ignore, that's the default + } else { + throw new MojoExecutionException( + "Parameter 'mode' was set to '" + mode + "' while expected one of 'dev', 'test' or 'prod'"); + } + } + modelResolver.setBuildTreeLogger(s -> getLog().info(s)); + modelResolver.resolveModel(appArtifact); + } catch (Exception e) { + throw new MojoExecutionException("Failed to resolve application model " + appArtifact + " dependencies", e); + } + } + + protected MavenArtifactResolver resolver() throws BootstrapMavenException { + return resolver == null + ? resolver = MavenArtifactResolver.builder() + .setRepositorySystem(bootstrapProvider.repositorySystem()) + .setRemoteRepositoryManager(bootstrapProvider.remoteRepositoryManager()) + //.setRepositorySystemSession(repoSession) the session should be initialized with the loaded workspace + .setRemoteRepositories(repos) + .build() + : resolver; + } + +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java b/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java new file mode 100644 index 0000000000000..c89f2e5f57aaa --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java @@ -0,0 +1,117 @@ +package io.quarkus.maven; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; +import org.apache.maven.model.Model; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsDependency; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.bootstrap.resolver.TsRepoBuilder; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.util.IoUtils; + +public abstract class DependencyTreeMojoTestBase { + protected Path workDir; + protected Path repoHome; + + protected MavenArtifactResolver mvnResolver; + protected TsRepoBuilder repoBuilder; + protected TsArtifact app; + protected Model appModel; + + @BeforeEach + public void setup() throws Exception { + workDir = IoUtils.createRandomTmpDir(); + repoHome = IoUtils.mkdirs(workDir.resolve("repo")); + + mvnResolver = MavenArtifactResolver.builder() + .setOffline(true) + .setLocalRepository(repoHome.toString()) + .setRemoteRepositories(Collections.emptyList()) + .build(); + + repoBuilder = TsRepoBuilder.getInstance(new BootstrapAppModelResolver(mvnResolver), workDir); + initRepo(); + } + + protected void initRepo() throws Exception { + final TsQuarkusExt coreExt = new TsQuarkusExt("test-core-ext"); + app = TsArtifact.jar("test-app") + .addDependency(new TsArtifact(TsArtifact.DEFAULT_GROUP_ID, "artifact-with-classifier", "classifier", "jar", + TsArtifact.DEFAULT_VERSION)) + .addDependency(new TsQuarkusExt("test-ext2") + .addDependency(new TsQuarkusExt("test-ext1").addDependency(coreExt))) + .addDependency(new TsDependency(TsArtifact.jar("optional"), true)) + .addDependency(new TsQuarkusExt("test-ext3").addDependency(coreExt)) + .addDependency(new TsDependency(TsArtifact.jar("provided"), "provided")) + .addDependency(new TsDependency(TsArtifact.jar("runtime"), "runtime")) + .addDependency(new TsDependency(TsArtifact.jar("test"), "test")); + appModel = app.getPomModel(); + app.install(repoBuilder); + } + + @AfterEach + public void cleanup() { + if (workDir != null) { + IoUtils.recursiveDelete(workDir); + } + } + + protected abstract String mode(); + + @Test + public void test() throws Exception { + + final DependencyTreeMojo mojo = new DependencyTreeMojo(); + mojo.project = new MavenProject(); + mojo.project.setArtifact(new DefaultArtifact(app.getGroupId(), app.getArtifactId(), app.getVersion(), "compile", + app.getType(), app.getClassifier(), new DefaultArtifactHandler("jar"))); + mojo.project.setModel(appModel); + mojo.project.setOriginalModel(appModel); + mojo.resolver = mvnResolver; + mojo.mode = mode(); + + final Path mojoLog = workDir.resolve("mojo.log"); + final PrintStream defaultOut = System.out; + + try (PrintStream logOut = new PrintStream(mojoLog.toFile(), "UTF-8")) { + System.setOut(logOut); + mojo.execute(); + } finally { + System.setOut(defaultOut); + } + + assertEquals(readInLowCase(Paths.get("").toAbsolutePath().resolve("target").resolve("test-classes") + .resolve(app.getArtifactFileName() + "." + mode())), readInLowCase(mojoLog)); + } + + private static List readInLowCase(Path p) throws IOException { + final List list = new ArrayList<>(); + try (BufferedReader reader = Files.newBufferedReader(p)) { + String line = reader.readLine(); + while (line != null) { + list.add(line.toLowerCase()); + line = reader.readLine(); + } + } + return list; + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java new file mode 100644 index 0000000000000..bef26188da25e --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java @@ -0,0 +1,8 @@ +package io.quarkus.maven; + +public class DevDependencyTreeMojoTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "dev"; + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/ProdDependencyTreeMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/ProdDependencyTreeMojoTest.java new file mode 100644 index 0000000000000..81aa3c190b258 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/ProdDependencyTreeMojoTest.java @@ -0,0 +1,8 @@ +package io.quarkus.maven; + +public class ProdDependencyTreeMojoTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "prod"; + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/TestDependencyTreeMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/TestDependencyTreeMojoTest.java new file mode 100644 index 0000000000000..fab83b72936e5 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/TestDependencyTreeMojoTest.java @@ -0,0 +1,8 @@ +package io.quarkus.maven; + +public class TestDependencyTreeMojoTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "test"; + } +} diff --git a/devtools/maven/src/test/resources/test-app-1.jar.dev b/devtools/maven/src/test/resources/test-app-1.jar.dev new file mode 100644 index 0000000000000..1fb959fdae99b --- /dev/null +++ b/devtools/maven/src/test/resources/test-app-1.jar.dev @@ -0,0 +1,14 @@ +[info] quarkus application dev mode build dependency tree: +[info] io.quarkus.bootstrap.test:test-app:pom:1 +[info] ├─ io.quarkus.bootstrap.test:artifact-with-classifier:jar:classifier:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:test-ext2-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext2:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-ext1:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-ext1-deployment:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:optional:jar:1 (compile optional) +[info] ├─ io.quarkus.bootstrap.test:test-ext3-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext3:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-core-ext:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext-deployment:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:provided:jar:1 (provided) +[info] └─ io.quarkus.bootstrap.test:runtime:jar:1 (runtime) diff --git a/devtools/maven/src/test/resources/test-app-1.jar.prod b/devtools/maven/src/test/resources/test-app-1.jar.prod new file mode 100644 index 0000000000000..5c460135a1273 --- /dev/null +++ b/devtools/maven/src/test/resources/test-app-1.jar.prod @@ -0,0 +1,13 @@ +[info] quarkus application prod mode build dependency tree: +[info] io.quarkus.bootstrap.test:test-app:pom:1 +[info] ├─ io.quarkus.bootstrap.test:artifact-with-classifier:jar:classifier:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:test-ext2-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext2:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-ext1:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-ext1-deployment:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:optional:jar:1 (compile optional) +[info] ├─ io.quarkus.bootstrap.test:test-ext3-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext3:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-core-ext:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext-deployment:jar:1 (compile) +[info] └─ io.quarkus.bootstrap.test:runtime:jar:1 (runtime) diff --git a/devtools/maven/src/test/resources/test-app-1.jar.test b/devtools/maven/src/test/resources/test-app-1.jar.test new file mode 100644 index 0000000000000..3f2160b370767 --- /dev/null +++ b/devtools/maven/src/test/resources/test-app-1.jar.test @@ -0,0 +1,14 @@ +[info] quarkus application test mode build dependency tree: +[info] io.quarkus.bootstrap.test:test-app:pom:1 +[info] ├─ io.quarkus.bootstrap.test:artifact-with-classifier:jar:classifier:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:test-ext2-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext2:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-ext1:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-ext1-deployment:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:optional:jar:1 (compile optional) +[info] ├─ io.quarkus.bootstrap.test:test-ext3-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-ext3:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-core-ext:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext-deployment:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:runtime:jar:1 (runtime) +[info] └─ io.quarkus.bootstrap.test:test:jar:1 (test) diff --git a/docs/src/main/asciidoc/conditional-extension-dependencies.adoc b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc new file mode 100644 index 0000000000000..3e5cc5927a902 --- /dev/null +++ b/docs/src/main/asciidoc/conditional-extension-dependencies.adoc @@ -0,0 +1,178 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc +//// += Quarkus - Conditional Extension Dependencies + +include::./attributes.adoc[] + +Quarkus extension dependencies are usually configured in the same way as any other project dependencies in the project's build file, e.g. the Maven `pom.xml` or the Gradle build scripts. However, there are dependency types that aren't yet supported out-of-the-box by Maven and Gradle. What we refer here to as "conditional dependencies" is one example. + +== Conditional Dependencies + +The idea behind the notion of the conditional dependency is that such a dependency must be 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, i.e. they may or may not appear in the resulting set of project dependencies. + +In which cases could conditional dependencies be useful? A typical example would be a component that should be activated **only** in case all of its required dependencies are available. If one or more of the component's required dependencies aren't available, instead of failing, the component should simply not be activated. + +== Quarkus Conditional Extension Dependencies + +Quarkus supports conditional extension dependencies. I.e. one Quarkus extension may declare one or more conditional dependencies on other Quarkus extensions. Conditional dependencies on and from non-extension artifacts aren't supported. + +Let's take 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 its dependencies (direct or transitive). In other words, the presence of `quarkus-extension-c` is the condition which, if satisfied, enables `quarkus-extension-b` during the build of a Quarkus application. + +The condition which triggers activation of an extension is configured in the extension's descriptor, which is included into the runtime artifact of the extension as `META-INF/quarkus-extension.properties`. Given that extension descriptor is generated by the Quarkus plugin at extension build time, 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] +---- + + + + + quarkus-extension-b <1> + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + process-resources + + extension-descriptor <2> + + + <3> + org.acme:quarkus-extension-c <4> + + + + + + + +---- + +<1> runtime Quarkus extension artifact ID, in our example `quarkus-extension-b`; +<2> the goal that generates the extension descriptor which every Quarkus runtime extension project should be configured with; +<3> configuration of the condition which will have to be satisfied for this extension to be included into 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 `dependencyCondition`. + +Now, having a dependency activating condition in the descriptor of `quarkus-extension-b`, other extensions may declare a conditional dependency on it. + +A conditional dependency is configured in the runtime artifact of a Quarkus extension. In our example, it's the `quarkus-extension-a` that has a conditional dependency on `quarkus-extension-b`, which can be expressed in two ways. + +=== Declaring a dependency as `optional` + +If an extension was configured with a dependency condition in its descriptor, other extensions may configure a conditional dependency on it by simply adding `true` to the dependency configuration. In our example it would look like this: + +[source,xml] +---- + + + + + quarkus-extension-a <1> + + + + + + org.acme + quarkus-extension-b <2> + true + + + +---- + +<1> the runtime extension artifact `quarkus-extension-a` +<2> declares an optional Maven dependency on the runtime extension artifact `quarkus-extension-b` + +IMPORTANT: In general, for every runtime extension artifact dependency on another runtime extension artifact there must be a corresponding deployment extension artifact dependency on the other deployment extension artifact. And if the runtime dependency is declared as optional then the corresponding deployment dependency **must** also be configured as optional. + +[source,xml] +---- + + + + + quarkus-extension-a-deployment <1> + + + + + + org.acme + quarkus-extension-b-deployment <2> + true + + + +---- + +<1> the deployment extension artifact `quarkus-extension-a-deployment` +<2> declares an optional Maven dependency on the deployment extension artifact `quarkus-extension-b-deployment` + +Normally, optional Maven extension dependencies are ignored by the Quarkus dependency resolver at build time. In this case though, the optional dependency `quarkus-extension-b` includes a dependency condition in its extension descriptor, which turns this optional Maven dependency into a Quarkus conditional extension dependency. + +IMPORTANT: If `quarkus-extension-b` wasn't declared as `true` that would make `quarkus-extension-b` a required dependency of `quarkus-extension-a` and its dependency condition would be ignored. + +=== Declaring a conditional dependency in the Quarkus extension descriptor + +Conditional dependencies can also be configured in the Quarkus extension descriptor. The conditional dependency configured above could be expressed in the extension descriptor of `quarkus-extension-a` as: + +[source,xml] +---- + + + + + quarkus-extension-a <1> + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + process-resources + + extension-descriptor <2> + + + <3> + org.acme:quarkus-extension-b:${b.version} <4> + + + + + + + +---- + +<1> runtime Quarkus extension artifact ID, in our example `quarkus-extension-a` +<2> the goal that generates the extension descriptor which every Quarkus runtime extension project should be configured with +<3> conditional dependency configuration element +<4> artifact coordinates of conditional dependencies on other extensions. + +In this case, the Maven dependency is not at all required in the `pom.xml`. diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java index 7b106025ebb86..76416a48b0016 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java @@ -10,6 +10,8 @@ public interface BootstrapConstants { String SERIALIZED_APP_MODEL = "quarkus-internal.serialized-app-model.path"; String DESCRIPTOR_FILE_NAME = "quarkus-extension.properties"; + String CONDITIONAL_DEPENDENCIES = "conditional-dependencies"; + String DEPENDENCY_CONDITION = "dependency-condition"; /** * Constant for sharing the additional mappings between test-sources and the corresponding application-sources. diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java index eeb73815d29e7..ffebc06fd8145 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java @@ -6,6 +6,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +44,7 @@ public void testCollectedDependencies() throws Exception { expected.addAll(deploymentDeps); } final List resolvedDeps = getTestResolver().resolveModel(root.toAppArtifact()).getFullDeploymentDeps(); - assertEquals(expected, resolvedDeps); + assertEquals(new HashSet<>(expected), new HashSet<>(resolvedDeps)); } protected BootstrapAppModelResolver getTestResolver() throws Exception { @@ -79,8 +80,9 @@ protected TsArtifact install(TsArtifact dep, Path p, String collectedInScope, bo return dep; } - protected void install(TsQuarkusExt ext) { + protected TsQuarkusExt install(TsQuarkusExt ext) { install(ext, true); + return ext; } protected void install(TsQuarkusExt ext, boolean collected) { @@ -150,6 +152,11 @@ protected void addCollectedDeploymentDep(TsArtifact ext) { deploymentDeps.add(new AppDependency(ext.toAppArtifact(), "compile", false)); } + protected void addManagedDep(TsQuarkusExt ext) { + addManagedDep(ext.runtime); + addManagedDep(ext.deployment); + } + protected void addManagedDep(TsArtifact dep) { root.addManagedDependency(new TsDependency(dep)); } diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java index ca5ed97db1539..3a6da4b9fb35d 100644 --- a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java @@ -69,8 +69,9 @@ protected TsJar newJar() throws IOException { return new TsJar(workDir.resolve(UUID.randomUUID().toString())); } - protected void install(TsQuarkusExt extension) { + protected TsQuarkusExt install(TsQuarkusExt extension) { extension.install(repo); + return extension; } protected TsArtifact install(TsArtifact artifact) { 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 e65a602d63c7d..041d37ea024cb 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 @@ -1,6 +1,7 @@ package io.quarkus.bootstrap.resolver; import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifactKey; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -64,6 +65,8 @@ public interface ContentProvider { protected Properties pomProps; protected List pomProfiles = Collections.emptyList(); + private boolean installed; + public TsArtifact(String artifactId) { this(artifactId, DEFAULT_VERSION); } @@ -84,6 +87,10 @@ public TsArtifact(String groupId, String artifactId, String classifier, String t this.version = version; } + public AppArtifactKey getKey() { + return new AppArtifactKey(groupId, artifactId); + } + public String getGroupId() { return groupId; } @@ -226,6 +233,15 @@ public AppArtifact toAppArtifact() { * @param repoBuilder */ public void install(TsRepoBuilder repoBuilder) { + if (installed) { + return; + } + installed = true; + if (!extDeps.isEmpty()) { + for (TsQuarkusExt ext : extDeps) { + ext.install(repoBuilder); + } + } if (!deps.isEmpty()) { for (TsDependency dep : deps) { if (dep.artifact.getVersion() != null) { @@ -233,11 +249,6 @@ public void install(TsRepoBuilder repoBuilder) { } } } - if (!extDeps.isEmpty()) { - for (TsQuarkusExt ext : extDeps) { - ext.deployment.install(repoBuilder); - } - } try { repoBuilder.install(this, content == null ? null : content.getPath(repoBuilder.workDir)); } catch (IOException e) { 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 364f26efd137c..15f1a342db6bf 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 @@ -1,11 +1,17 @@ package io.quarkus.bootstrap.resolver; import io.quarkus.bootstrap.BootstrapConstants; +import java.util.ArrayList; +import java.util.List; public class TsQuarkusExt { protected final TsArtifact runtime; protected final TsArtifact deployment; + protected final List extDeps = new ArrayList<>(0); + protected final TsJar rtContent; + protected final PropsBuilder rtDescr = PropsBuilder.newInstance(); + private boolean installed; public TsQuarkusExt(String artifactId) { this(artifactId, TsArtifact.DEFAULT_VERSION); @@ -15,8 +21,34 @@ public TsQuarkusExt(String artifactId, String version) { runtime = TsArtifact.jar(artifactId, version); deployment = TsArtifact.jar(artifactId + "-deployment", version); deployment.addDependency(runtime); - runtime.setContent(new TsJar().addEntry(PropsBuilder.build(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment), - BootstrapConstants.DESCRIPTOR_PATH)); + rtContent = new TsJar(); + runtime.setContent(rtContent); + rtDescr.set(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment.toString()); + } + + public TsQuarkusExt setConditionalDeps(TsQuarkusExt... exts) { + 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()); + } + return setDescriptorProp(BootstrapConstants.CONDITIONAL_DEPENDENCIES, buf.toString()); + } + + public TsQuarkusExt setDependencyCondition(TsQuarkusExt... exts) { + final StringBuilder buf = new StringBuilder(); + int i = 0; + buf.append(exts[i++].getRuntime().getKey()); + while (i < exts.length) { + buf.append(' ').append(exts[i++].getRuntime().getKey()); + } + return setDescriptorProp(BootstrapConstants.DEPENDENCY_CONDITION, buf.toString()); + } + + public TsQuarkusExt setDescriptorProp(String name, String value) { + rtDescr.set(name, value); + return this; } public TsArtifact getRuntime() { @@ -27,14 +59,25 @@ public TsArtifact getDeployment() { return deployment; } - public TsQuarkusExt addDependency(TsQuarkusExt ext) { - runtime.addDependency(ext.runtime); + public TsQuarkusExt addDependency(TsQuarkusExt ext, TsArtifact... exclusions) { + extDeps.add(ext); + runtime.addDependency(ext.runtime, exclusions); deployment.addDependency(ext.deployment); return this; } public void install(TsRepoBuilder repo) { - repo.install(deployment); - repo.install(runtime); + if (installed) { + return; + } + installed = true; + rtContent.addEntry(rtDescr.build(), BootstrapConstants.DESCRIPTOR_PATH); + if (!extDeps.isEmpty()) { + for (TsQuarkusExt e : extDeps) { + e.install(repo); + } + } + deployment.install(repo); + runtime.install(repo); } } diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java index 5971390b59e4f..f1281757413d8 100644 --- a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java +++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java @@ -2,20 +2,33 @@ import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; -import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import java.util.List; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.repository.RemoteRepository; public class AbstractTreeMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true, required = true) protected MavenProject project; + @Component + RepositorySystem repoSystem; + + @Component + RemoteRepositoryManager remoteRepoManager; + + @Parameter(defaultValue = "${project.remoteRepositories}", readonly = true, required = true) + private List repos; + protected MavenArtifactResolver resolver; @Override @@ -34,7 +47,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { } protected MavenArtifactResolver resolver() throws BootstrapMavenException { - return resolver == null ? resolver = new MavenArtifactResolver(new BootstrapMavenContext()) : resolver; + return resolver == null + ? resolver = MavenArtifactResolver.builder() + .setRepositorySystem(repoSystem) + .setRemoteRepositoryManager(remoteRepoManager) + //.setRepositorySystemSession(repoSession) the session should be initialized with the loaded workspace + .setRemoteRepositories(repos) + .build() + : resolver; } protected void setupResolver(BootstrapAppModelResolver modelResolver) { diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java index af95e9110cec1..19934d53b7143 100644 --- a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java +++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java @@ -9,7 +9,10 @@ /** * Displays Quarkus application build dependency tree including the deployment ones. + * + * @deprecated this mojo has moved to the quarkus-maven-plugin */ @Mojo(name = "build-tree", defaultPhase = LifecyclePhase.NONE, requiresDependencyResolution = ResolutionScope.NONE) +@Deprecated public class BuildTreeMojo extends AbstractTreeMojo { } diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java index 4a3024af6bac7..0fdc2cea19831 100644 --- a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java +++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java @@ -17,6 +17,7 @@ import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.util.DependencyNodeUtils; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -45,6 +46,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; @@ -60,6 +62,7 @@ import org.eclipse.aether.resolution.ArtifactDescriptorResult; import org.eclipse.aether.resolution.DependencyRequest; import org.eclipse.aether.resolution.DependencyResult; +import org.eclipse.aether.util.artifact.JavaScopes; /** * Generates Quarkus extension descriptor for the runtime artifact. @@ -90,7 +93,7 @@ public class ExtensionDescriptorMojo extends AbstractMojo { RemoteRepositoryManager remoteRepoManager; @Component - BootstrapWorkspaceProvider workpaceProvider; + BootstrapWorkspaceProvider workspaceProvider; /** * The current repository/network configuration of Maven. @@ -167,6 +170,12 @@ public class ExtensionDescriptorMojo extends AbstractMojo { @Parameter(required = false, defaultValue = "${ignoreNotDetectedQuarkusCoreVersion") boolean ignoreNotDetectedQuarkusCoreVersion; + @Parameter + private List conditionalDependencies = new ArrayList<>(0); + + @Parameter + private List dependencyCondition = new ArrayList<>(0); + AppArtifactCoords deploymentCoords; CollectResult collectedDeploymentDeps; @@ -179,8 +188,61 @@ public void execute() throws MojoExecutionException { validateExtensionDeps(); } + if (conditionalDependencies.isEmpty()) { + // if conditional dependencies haven't been configured + // we check whether there are direct optional dependencies on extensions + // that are configured with a dependency condition + // such dependencies will be registered as conditional + StringBuilder buf = null; + for (org.apache.maven.model.Dependency d : project.getDependencies()) { + if (!d.isOptional()) { + continue; + } + if (!d.getScope().isEmpty() + && !(d.getScope().equals(JavaScopes.COMPILE) || d.getScope().equals(JavaScopes.RUNTIME))) { + continue; + } + final Properties props = getExtensionDescriptor( + new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion()), + false); + if (props == null || !props.containsKey(BootstrapConstants.DEPENDENCY_CONDITION)) { + continue; + } + if (buf == null) { + buf = new StringBuilder(); + } else { + buf.setLength(0); + } + buf.append(d.getGroupId()).append(':').append(d.getArtifactId()).append(':'); + if (d.getClassifier() != null) { + buf.append(d.getClassifier()); + } + buf.append(':').append(d.getType()).append(':').append(d.getVersion()); + conditionalDependencies.add(buf.toString()); + } + } + final Properties props = new Properties(); props.setProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment); + if (!conditionalDependencies.isEmpty()) { + final StringBuilder buf = new StringBuilder(); + int i = 0; + buf.append(AppArtifactCoords.fromString(conditionalDependencies.get(i++)).toString()); + while (i < conditionalDependencies.size()) { + buf.append(' ').append(AppArtifactCoords.fromString(conditionalDependencies.get(i++)).toString()); + } + props.setProperty(BootstrapConstants.CONDITIONAL_DEPENDENCIES, buf.toString()); + } + if (!dependencyCondition.isEmpty()) { + final StringBuilder buf = new StringBuilder(); + int i = 0; + buf.append(AppArtifactKey.fromString(dependencyCondition.get(i++)).toString()); + while (i < dependencyCondition.size()) { + buf.append(' ').append(AppArtifactKey.fromString(dependencyCondition.get(i++)).toString()); + } + props.setProperty(BootstrapConstants.DEPENDENCY_CONDITION, buf.toString()); + + } final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF); if (parentFirstArtifacts != null && !parentFirstArtifacts.isEmpty()) { @@ -539,6 +601,26 @@ private void visitRuntimeDeps(RootNode root, Node currentNode, int currentId, De } private AppArtifactKey getDeploymentKey(org.eclipse.aether.artifact.Artifact a) throws MojoExecutionException { + final org.eclipse.aether.artifact.Artifact deployment = getDeploymentArtifact(a); + return deployment == null ? null : toKey(deployment); + } + + private org.eclipse.aether.artifact.Artifact getDeploymentArtifact(org.eclipse.aether.artifact.Artifact a) + throws MojoExecutionException { + final Properties props = getExtensionDescriptor(a, true); + if (props == null) { + return null; + } + final String deploymentStr = props.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + if (deploymentStr == null) { + throw new IllegalStateException("Quarkus extension runtime artifact " + a + " is missing " + + BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT + " property in its " + + BootstrapConstants.DESCRIPTOR_PATH); + } + return DependencyNodeUtils.toArtifact(deploymentStr); + } + + private Properties getExtensionDescriptor(org.eclipse.aether.artifact.Artifact a, boolean packaged) { final File f; try { f = resolve(a); @@ -547,31 +629,35 @@ private AppArtifactKey getDeploymentKey(org.eclipse.aether.artifact.Artifact a) return null; } // if it hasn't been packaged yet, we skip it, we are not packaging yet - if (isAnalyzable(f)) { - try (FileSystem fs = FileSystems.newFileSystem(f.toPath(), (ClassLoader) null)) { - final Path extDescr = fs.getPath(BootstrapConstants.DESCRIPTOR_PATH); - if (Files.exists(extDescr)) { - final Properties props = new Properties(); - try (BufferedReader reader = Files.newBufferedReader(extDescr)) { - props.load(reader); - } - final String deploymentStr = props.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); - if (deploymentStr == null) { - throw new IllegalStateException("Quarkus extension runtime artifact " + a + " is missing " - + BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT + " property in its " - + BootstrapConstants.DESCRIPTOR_PATH); - } - return AppArtifactCoords.fromString(deploymentStr).getKey(); + if (packaged && !isJarFile(f)) { + return null; + } + try { + if (f.isDirectory()) { + return readExtensionDescriptor(f.toPath().resolve(BootstrapConstants.DESCRIPTOR_PATH)); + } else { + try (FileSystem fs = FileSystems.newFileSystem(f.toPath(), (ClassLoader) null)) { + return readExtensionDescriptor(fs.getPath(BootstrapConstants.DESCRIPTOR_PATH)); } - } catch (Throwable e) { - throw new IllegalStateException("Failed to read " + f, e); } + } catch (Throwable e) { + throw new IllegalStateException("Failed to read " + f, e); + } + } + + private Properties readExtensionDescriptor(final Path extDescr) throws IOException { + if (!Files.exists(extDescr)) { + return null; + } + final Properties props = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(extDescr)) { + props.load(reader); } - return null; + return props; } private static AppArtifactKey toKey(org.eclipse.aether.artifact.Artifact a) { - return new AppArtifactKey(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()); + return DependencyNodeUtils.toKey(a); } private CollectResult collectDeploymentDeps() throws MojoExecutionException { @@ -618,7 +704,7 @@ private CollectRequest newCollectRequest(DefaultArtifact projectArtifact) throws return request; } - private boolean isAnalyzable(final File f) { + private boolean isJarFile(final File f) { return f != null && f.getName().endsWith(".jar") && f.exists() && !f.isDirectory(); } @@ -820,13 +906,15 @@ private static interface NodeHandler { private MavenArtifactResolver resolver() throws MojoExecutionException { if (resolver == null) { + final DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(repoSession); + session.setWorkspaceReader(workspaceProvider.workspace()); try { final BootstrapMavenContext ctx = new BootstrapMavenContext(BootstrapMavenContext.config() .setRepositorySystem(repoSystem) .setRemoteRepositoryManager(remoteRepoManager) - .setRepositorySystemSession(repoSession) + .setRepositorySystemSession(session) .setRemoteRepositories(repos) - .setCurrentProject(workpaceProvider.origin())); + .setCurrentProject(workspaceProvider.origin())); resolver = new MavenArtifactResolver(ctx); } catch (BootstrapMavenException e) { throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java index 6e3d864336a41..28193461c84ac 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java @@ -181,32 +181,9 @@ private AppModel doResolveModel(AppArtifact appArtifact, List direct appArtifact.setPaths(PathsCollection.of(resolveResult.getArtifact().getFile().toPath())); } - final Set appDeps = new HashSet<>(); - final List userDeps = new ArrayList<>(); DependencyNode resolvedDeps = mvn.resolveManagedDependencies(mvnArtifact, directMvnDeps, managedDeps, managedRepos, excludedScopes.toArray(new String[0])).getRoot(); - final TreeDependencyVisitor visitor = new TreeDependencyVisitor(new DependencyVisitor() { - @Override - public boolean visitEnter(DependencyNode node) { - return true; - } - - @Override - public boolean visitLeave(DependencyNode node) { - final Dependency dep = node.getDependency(); - if (dep != null) { - final AppArtifact appArtifact = toAppArtifact(dep.getArtifact()); - appDeps.add(appArtifact.getKey()); - userDeps.add(new AppDependency(appArtifact, dep.getScope(), dep.isOptional())); - } - return true; - } - }); - for (DependencyNode child : resolvedDeps.getChildren()) { - child.accept(visitor); - } - ArtifactDescriptorResult appArtifactDescr = mvn.resolveDescriptor(toAetherArtifact(appArtifact)); if (managingProject == null) { managedDeps = appArtifactDescr.getManagedDependencies(); @@ -252,7 +229,9 @@ public boolean visitLeave(DependencyNode node) { } catch (RepositoryException e) { throw new AppModelResolverException("Failed to normalize the dependency graph", e); } - final BuildDependencyGraphVisitor buildDepsVisitor = new BuildDependencyGraphVisitor(appDeps, buildTreeConsumer); + final BuildDependencyGraphVisitor buildDepsVisitor = new BuildDependencyGraphVisitor( + deploymentInjector.allRuntimeDeps, + buildTreeConsumer); buildDepsVisitor.visit(resolvedDeps); final List requests = buildDepsVisitor.getArtifactRequests(); if (!requests.isEmpty()) { @@ -275,9 +254,6 @@ public boolean visitLeave(DependencyNode node) { collectPlatformProperties(appBuilder, managedDeps); - List fullDeploymentDeps = new ArrayList<>(userDeps.size() + deploymentDeps.size()); - fullDeploymentDeps.addAll(userDeps); - fullDeploymentDeps.addAll(deploymentDeps); //we need these to have a type of 'jar' //type is blank when loaded for (AppArtifactKey i : localProjects) { @@ -286,8 +262,7 @@ public boolean visitLeave(DependencyNode node) { return appBuilder .addDeploymentDeps(deploymentDeps) .setAppArtifact(appArtifact) - .addFullDeploymentDeps(fullDeploymentDeps) - .addRuntimeDeps(userDeps) + .addFullDeploymentDeps(deploymentDeps) .build(); } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersion.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersion.java new file mode 100644 index 0000000000000..01243b1b11457 --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersion.java @@ -0,0 +1,359 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import org.eclipse.aether.version.Version; + +public class BootstrapArtifactVersion implements Version { + + private final String version; + + private final Item[] items; + + private final int hash; + + /** + * Creates a generic version from the specified string. + * + * @param version The version string, must not be {@code null}. + */ + BootstrapArtifactVersion(String version) { + this.version = version; + items = parse(version); + hash = Arrays.hashCode(items); + } + + private static Item[] parse(String version) { + List items = new ArrayList<>(); + + for (Tokenizer tokenizer = new Tokenizer(version); tokenizer.next();) { + Item item = tokenizer.toItem(); + items.add(item); + } + + trimPadding(items); + + return items.toArray(new Item[items.size()]); + } + + private static void trimPadding(List items) { + Boolean number = null; + int end = items.size() - 1; + for (int i = end; i > 0; i--) { + Item item = items.get(i); + if (!Boolean.valueOf(item.isNumber()).equals(number)) { + end = i; + number = item.isNumber(); + } + if (end == i && (i == items.size() - 1 || items.get(i - 1).isNumber() == item.isNumber()) + && item.compareTo(null) == 0) { + items.remove(i); + end--; + } + } + } + + public int compareTo(Version obj) { + final Item[] these = items; + final Item[] those = ((BootstrapArtifactVersion) obj).items; + + boolean number = true; + + for (int index = 0;; index++) { + if (index >= these.length && index >= those.length) { + return 0; + } else if (index >= these.length) { + return -comparePadding(those, index, null); + } else if (index >= those.length) { + return comparePadding(these, index, null); + } + + Item thisItem = these[index]; + Item thatItem = those[index]; + + if (thisItem.isNumber() != thatItem.isNumber()) { + if (number == thisItem.isNumber()) { + return comparePadding(these, index, number); + } else { + return -comparePadding(those, index, number); + } + } else { + int rel = thisItem.compareTo(thatItem); + if (rel != 0) { + return rel; + } + number = thisItem.isNumber(); + } + } + } + + private static int comparePadding(Item[] items, int index, Boolean number) { + int rel = 0; + for (int i = index; i < items.length; i++) { + Item item = items[i]; + if (number != null && number != item.isNumber()) { + break; + } + rel = item.compareTo(null); + if (rel != 0) { + break; + } + } + return rel; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof BootstrapArtifactVersion) && compareTo((BootstrapArtifactVersion) obj) == 0; + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public String toString() { + return version; + } + + static final class Tokenizer { + + private static final Integer QUALIFIER_ALPHA = -5; + + private static final Integer QUALIFIER_BETA = -4; + + private static final Integer QUALIFIER_MILESTONE = -3; + + private static final Map QUALIFIERS; + + static { + QUALIFIERS = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + QUALIFIERS.put("alpha", QUALIFIER_ALPHA); + QUALIFIERS.put("beta", QUALIFIER_BETA); + QUALIFIERS.put("milestone", QUALIFIER_MILESTONE); + QUALIFIERS.put("cr", -2); + QUALIFIERS.put("rc", -2); + QUALIFIERS.put("snapshot", -1); + QUALIFIERS.put("ga", 0); + QUALIFIERS.put("final", 0); + QUALIFIERS.put("release", 0); + QUALIFIERS.put("", 0); + QUALIFIERS.put("sp", 1); + } + + private final String version; + + private int index; + + private String token; + + private boolean number; + + private boolean terminatedByNumber; + + Tokenizer(String version) { + this.version = (version.length() > 0) ? version : "0"; + } + + public boolean next() { + final int n = version.length(); + if (index >= n) { + return false; + } + + int state = -2; + + int start = index; + int end = n; + terminatedByNumber = false; + + for (; index < n; index++) { + char c = version.charAt(index); + + if (c == '.' || c == '-' || c == '_') { + end = index; + index++; + break; + } else { + int digit = Character.digit(c, 10); + if (digit >= 0) { + if (state == -1) { + end = index; + terminatedByNumber = true; + break; + } + if (state == 0) { + // normalize numbers and strip leading zeros (prereq for Integer/BigInteger handling) + start++; + } + state = (state > 0 || digit > 0) ? 1 : 0; + } else { + if (state >= 0) { + end = index; + break; + } + state = -1; + } + } + + } + + if (end - start > 0) { + token = version.substring(start, end); + number = state >= 0; + } else { + token = "0"; + number = true; + } + + return true; + } + + @Override + public String toString() { + return String.valueOf(token); + } + + public Item toItem() { + if (number) { + try { + if (token.length() < 10) { + return new Item(Item.KIND_INT, Integer.parseInt(token)); + } else { + return new Item(Item.KIND_BIGINT, new BigInteger(token)); + } + } catch (NumberFormatException e) { + throw new IllegalStateException(e); + } + } else { + if (index >= version.length()) { + if ("min".equalsIgnoreCase(token)) { + return Item.MIN; + } else if ("max".equalsIgnoreCase(token)) { + return Item.MAX; + } + } + if (terminatedByNumber && token.length() == 1) { + switch (token.charAt(0)) { + case 'a': + case 'A': + return new Item(Item.KIND_QUALIFIER, QUALIFIER_ALPHA); + case 'b': + case 'B': + return new Item(Item.KIND_QUALIFIER, QUALIFIER_BETA); + case 'm': + case 'M': + return new Item(Item.KIND_QUALIFIER, QUALIFIER_MILESTONE); + default: + } + } + Integer qualifier = QUALIFIERS.get(token); + if (qualifier != null) { + return new Item(Item.KIND_QUALIFIER, qualifier); + } else { + return new Item(Item.KIND_STRING, token.toLowerCase(Locale.ENGLISH)); + } + } + } + + } + + static final class Item { + + static final int KIND_MAX = 8; + + static final int KIND_BIGINT = 5; + + static final int KIND_INT = 4; + + static final int KIND_STRING = 3; + + static final int KIND_QUALIFIER = 2; + + static final int KIND_MIN = 0; + + static final Item MAX = new Item(KIND_MAX, "max"); + + static final Item MIN = new Item(KIND_MIN, "min"); + + private final int kind; + + private final Object value; + + Item(int kind, Object value) { + this.kind = kind; + this.value = value; + } + + public boolean isNumber() { + return (kind & KIND_QUALIFIER) == 0; // i.e. kind != string/qualifier + } + + public int compareTo(Item that) { + int rel; + if (that == null) { + // null in this context denotes the pad item (0 or "ga") + switch (kind) { + case KIND_MIN: + rel = -1; + break; + case KIND_MAX: + case KIND_BIGINT: + case KIND_STRING: + rel = 1; + break; + case KIND_INT: + case KIND_QUALIFIER: + rel = (Integer) value; + break; + default: + throw new IllegalStateException("unknown version item kind " + kind); + } + } else { + rel = kind - that.kind; + if (rel == 0) { + switch (kind) { + case KIND_MAX: + case KIND_MIN: + break; + case KIND_BIGINT: + rel = ((BigInteger) value).compareTo((BigInteger) that.value); + break; + case KIND_INT: + case KIND_QUALIFIER: + rel = ((Integer) value).compareTo((Integer) that.value); + break; + case KIND_STRING: + rel = ((String) value).compareToIgnoreCase((String) that.value); + break; + default: + throw new IllegalStateException("unknown version item kind " + kind); + } + } + } + return rel; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof Item) && compareTo((Item) obj) == 0; + } + + @Override + public int hashCode() { + return value.hashCode() + kind * 31; + } + + @Override + public String toString() { + return String.valueOf(value); + } + + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersionConstraint.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersionConstraint.java new file mode 100644 index 0000000000000..657033c6d3124 --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapArtifactVersionConstraint.java @@ -0,0 +1,83 @@ +package io.quarkus.bootstrap.resolver.maven; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; +import org.eclipse.aether.version.Version; +import org.eclipse.aether.version.VersionConstraint; +import org.eclipse.aether.version.VersionRange; + +public class BootstrapArtifactVersionConstraint implements VersionConstraint { + + private final VersionRange range; + + private final Version version; + + /** + * Creates a version constraint from the specified version range. + * + * @param range The version range, must not be {@code null}. + */ + BootstrapArtifactVersionConstraint(VersionRange range) { + this.range = requireNonNull(range, "version range cannot be null"); + this.version = null; + } + + /** + * Creates a version constraint from the specified version. + * + * @param version The version, must not be {@code null}. + */ + BootstrapArtifactVersionConstraint(Version version) { + this.version = requireNonNull(version, "version cannot be null"); + this.range = null; + } + + public VersionRange getRange() { + return range; + } + + public Version getVersion() { + return version; + } + + public boolean containsVersion(Version version) { + if (range == null) { + return version.equals(this.version); + } else { + return range.containsVersion(version); + } + } + + @Override + public String toString() { + return String.valueOf((range == null) ? version : range); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || !getClass().equals(obj.getClass())) { + return false; + } + + BootstrapArtifactVersionConstraint that = (BootstrapArtifactVersionConstraint) obj; + + return Objects.equals(range, that.range) && Objects.equals(version, that.getVersion()); + } + + @Override + public int hashCode() { + int hash = 17; + hash = hash * 31 + hash(getRange()); + hash = hash * 31 + hash(getVersion()); + return hash; + } + + private static int hash(Object obj) { + return obj != null ? obj.hashCode() : 0; + } + +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java index baf7038db120d..247fe294b813c 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java @@ -4,6 +4,7 @@ package io.quarkus.bootstrap.resolver.maven; import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.util.DependencyNodeUtils; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -15,14 +16,14 @@ public class BuildDependencyGraphVisitor { - private final Set appDeps; + private final Set allRuntimeDeps; private final StringBuilder buf; private final Consumer buildTreeConsumer; private final List depth; - private DependencyNode deploymentNode; - private DependencyNode runtimeNode; - private Artifact runtimeArtifact; + private DependencyNode currentDeployment; + private DependencyNode currentRuntime; + private Artifact runtimeArtifactToFind; /** * Nodes that are only present in the deployment class loader @@ -30,8 +31,8 @@ public class BuildDependencyGraphVisitor { private final List deploymentDepNodes = new ArrayList<>(); private final List requests = new ArrayList<>(); - public BuildDependencyGraphVisitor(Set appDeps, Consumer buildTreeConsumer) { - this.appDeps = appDeps; + public BuildDependencyGraphVisitor(Set allRuntimeDeps, Consumer buildTreeConsumer) { + this.allRuntimeDeps = allRuntimeDeps; this.buildTreeConsumer = buildTreeConsumer; if (buildTreeConsumer == null) { buf = null; @@ -52,35 +53,25 @@ public List getArtifactRequests() { public void visit(DependencyNode node) { if (depth != null) { - buf.setLength(0); - if (!depth.isEmpty()) { - for (int i = 0; i < depth.size() - 1; ++i) { - if (depth.get(i)) { - //buf.append("| "); - buf.append('\u2502').append(" "); - } else { - buf.append(" "); - } - } - if (depth.get(depth.size() - 1)) { - //buf.append("|- "); - buf.append('\u251c').append('\u2500').append(' '); - } else { - //buf.append("\\- "); - buf.append('\u2514').append('\u2500').append(' '); - } - } - buf.append(node.getArtifact()); - if (!depth.isEmpty()) { - buf.append(" (").append(node.getDependency().getScope()); - if (node.getDependency().isOptional()) { - buf.append(" optional"); - } - buf.append(')'); - } - buildTreeConsumer.accept(buf.toString()); + consume(node); + } + final Dependency dep = node.getDependency(); + + final DependencyNode previousDeployment = currentDeployment; + final DependencyNode previousRuntime = currentRuntime; + final Artifact previousRuntimeArtifact = runtimeArtifactToFind; + + final Artifact newRuntimeArtifact = DeploymentInjectingDependencyVisitor.getRuntimeArtifact(node); + if (newRuntimeArtifact != null) { + currentDeployment = node; + runtimeArtifactToFind = newRuntimeArtifact; + currentRuntime = null; + } else if (runtimeArtifactToFind != null && currentRuntime == null + && runtimeArtifactToFind.equals(dep.getArtifact())) { + currentRuntime = node; + runtimeArtifactToFind = null; } - visitEnter(node); + final List children = node.getChildren(); if (!children.isEmpty()) { final int childrenTotal = children.size(); @@ -94,13 +85,9 @@ public void visit(DependencyNode node) { depth.add(true); } int i = 0; - while (true) { + while (i < childrenTotal) { visit(children.get(i++)); - if (i < childrenTotal - 1) { - continue; - } else if (i == childrenTotal) { - break; - } else if (depth != null) { + if (depth != null && i == childrenTotal - 1) { depth.set(depth.size() - 1, false); } } @@ -110,18 +97,40 @@ public void visit(DependencyNode node) { } } visitLeave(node); + + currentDeployment = previousDeployment; + currentRuntime = previousRuntime; + runtimeArtifactToFind = previousRuntimeArtifact; } - private void visitEnter(DependencyNode node) { - final Dependency dep = node.getDependency(); - if (deploymentNode == null) { - runtimeArtifact = DeploymentInjectingDependencyVisitor.getRuntimeArtifact(node); - if (runtimeArtifact != null) { - deploymentNode = node; + private void consume(DependencyNode node) { + buf.setLength(0); + if (!depth.isEmpty()) { + for (int i = 0; i < depth.size() - 1; ++i) { + if (depth.get(i)) { + //buf.append("| "); + buf.append('\u2502').append(" "); + } else { + buf.append(" "); + } + } + if (depth.get(depth.size() - 1)) { + //buf.append("|- "); + buf.append('\u251c').append('\u2500').append(' '); + } else { + //buf.append("\\- "); + buf.append('\u2514').append('\u2500').append(' '); + } + } + buf.append(node.getArtifact()); + if (!depth.isEmpty()) { + buf.append(" (").append(node.getDependency().getScope()); + if (node.getDependency().isOptional()) { + buf.append(" optional"); } - } else if (runtimeArtifact != null && runtimeNode == null && runtimeArtifact.equals(dep.getArtifact())) { - runtimeNode = node; + buf.append(')'); } + buildTreeConsumer.accept(buf.toString()); } private void visitLeave(DependencyNode node) { @@ -133,16 +142,15 @@ private void visitLeave(DependencyNode node) { if (artifact.getFile() == null) { requests.add(new ArtifactRequest(node)); } - if (deploymentNode != null) { - if (runtimeNode == null && !appDeps.contains(new AppArtifactKey(artifact.getGroupId(), - artifact.getArtifactId(), artifact.getClassifier(), artifact.getExtension()))) { + if (currentDeployment != null) { + if (currentRuntime == null && !allRuntimeDeps.contains(DependencyNodeUtils.toKey(artifact))) { deploymentDepNodes.add(node); - } else if (runtimeNode == node) { - runtimeNode = null; - runtimeArtifact = null; + } else if (currentRuntime == node) { + currentRuntime = null; + runtimeArtifactToFind = null; } - if (deploymentNode == node) { - deploymentNode = null; + if (currentDeployment == node) { + currentDeployment = null; } } } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java index 500fa7823a7f0..96482533a0444 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java @@ -2,8 +2,13 @@ import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.BootstrapDependencyProcessingException; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.util.DependencyNodeUtils; import io.quarkus.bootstrap.util.ZipUtils; import java.io.BufferedReader; import java.io.File; @@ -11,19 +16,29 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Properties; +import java.util.Set; import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.graph.Exclusion; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.util.artifact.JavaScopes; +import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; import org.jboss.logging.Logger; /** @@ -34,8 +49,10 @@ public class DeploymentInjectingDependencyVisitor { private static final Logger log = Logger.getLogger(DeploymentInjectingDependencyVisitor.class); - static final String QUARKUS_RUNTIME_ARTIFACT = "quarkus.runtime"; - private static final String QUARKUS_DEPLOYMENT_ARTIFACT = "quarkus.deployment"; + private static final String QUARKUS_RUNTIME_ARTIFACT = "quarkus.runtime"; + private static final String QUARKUS_EXTENSION_DEPENDENCY = "quarkus.ext"; + + private static final Artifact[] NO_ARTIFACTS = new Artifact[0]; public static Artifact getRuntimeArtifact(DependencyNode dep) { return (Artifact) dep.getData().get(DeploymentInjectingDependencyVisitor.QUARKUS_RUNTIME_ARTIFACT); @@ -44,11 +61,16 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { private final MavenArtifactResolver resolver; private final List managedDeps; private final List mainRepos; + private final AppModel.Builder appBuilder; - private boolean collectExtensions = true; + private boolean collectingTopRuntimeNodes = true; + private final List topExtensionDeps = new ArrayList<>(); + private ExtensionDependency lastVisitedRuntimeExtNode; + private final Map allExtensions = new HashMap<>(); + private List conditionalDepsToProcess = new ArrayList<>(); + private final Deque> exclusionStack = new ArrayDeque<>(); - private List runtimeNodes = new ArrayList<>(); - private final AppModel.Builder appBuilder; + public final Set allRuntimeDeps = new HashSet<>(); public DeploymentInjectingDependencyVisitor(MavenArtifactResolver resolver, List managedDeps, List mainRepos, AppModel.Builder appBuilder) throws BootstrapDependencyProcessingException { @@ -75,101 +97,275 @@ public DeploymentInjectingDependencyVisitor(MavenArtifactResolver resolver, List } public boolean isInjectedDeps() { - return !runtimeNodes.isEmpty(); + return !topExtensionDeps.isEmpty(); } public void injectDeploymentDependencies(DependencyNode root) throws BootstrapDependencyProcessingException { - collectRuntimeExtensions(root.getChildren()); - // resolve and inject deployment dependencies - for (DependencyNode rtNode : runtimeNodes) { - replaceWith(rtNode, collectDependencies((Artifact) rtNode.getData().get(QUARKUS_DEPLOYMENT_ARTIFACT), - rtNode.getDependency().getExclusions())); + visitRuntimeDependencies(root.getChildren()); + + List activatedConditionalDeps = Collections.emptyList(); + + 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(); + } + } + + // resolve and inject deployment dependency branches for the top (first met) runtime extension nodes + for (ExtensionDependency extDep : topExtensionDeps) { + injectDeploymentDependencies(extDep); } + + if (!activatedConditionalDeps.isEmpty()) { + for (ConditionalDependency cd : activatedConditionalDeps) { + injectDeploymentDependencies(cd.getExtensionDependency()); + } + } + } + + private boolean isRuntimeArtifact(AppArtifactKey key) { + return allRuntimeDeps.contains(key); } - private void collectRuntimeExtensions(List list) { + private void visitRuntimeDependencies(List list) { int i = 0; while (i < list.size()) { - collectRuntimeExtensions(list.get(i++)); + visitRuntimeDependency(list.get(i++)); } } - private void collectRuntimeExtensions(DependencyNode node) { - final Artifact artifact = node.getArtifact(); - if (!artifact.getExtension().equals("jar")) { - return; + private void visitRuntimeDependency(DependencyNode node) { + Artifact artifact = node.getArtifact(); + + if (allRuntimeDeps.add(DependencyNodeUtils.toKey(artifact))) { + artifact = resolve(artifact); + final AppArtifact appArtifact = toAppArtifact(artifact); + final AppDependency appDep = new AppDependency(appArtifact, node.getDependency().getScope(), + node.getDependency().isOptional()); + appBuilder.addRuntimeDep(appDep); + appBuilder.addFullDeploymentDep(appDep); + } + + final boolean prevCollectingTopRtNodes = collectingTopRuntimeNodes; + final ExtensionDependency prevLastVisitedRtExtNode = lastVisitedRuntimeExtNode; + + final boolean popExclusions; + if (popExclusions = !node.getDependency().getExclusions().isEmpty()) { + exclusionStack.addLast(node.getDependency().getExclusions()); } - final Path path = resolve(artifact); - final boolean parentCollectsExtensions = collectExtensions; + try { - if (Files.isDirectory(path)) { - collectExtensions &= !processMetaInfDir(node, path.resolve(BootstrapConstants.META_INF)); - } else { - try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) { - collectExtensions &= !processMetaInfDir(node, artifactFs.getPath(BootstrapConstants.META_INF)); - } + final ExtensionDependency extDep = getExtensionDependencyOrNull(node, artifact); + if (extDep != null) { + extDep.info.ensureActivated(); + visitExtensionDependency(extDep); } - collectRuntimeExtensions(node.getChildren()); + visitRuntimeDependencies(node.getChildren()); } catch (DeploymentInjectionException e) { throw e; } catch (Exception t) { throw new DeploymentInjectionException("Failed to inject extension deployment dependencies", t); - } finally { - collectExtensions = parentCollectsExtensions; } + + if (popExclusions) { + exclusionStack.pollLast(); + } + collectingTopRuntimeNodes = prevCollectingTopRtNodes; + lastVisitedRuntimeExtNode = prevLastVisitedRtExtNode; } - /** - * @return true in case the node is a Quarkus runtime extension artifact, otherwise - false - * @throws BootstrapDependencyProcessingException in case of a failure - */ - private boolean processMetaInfDir(DependencyNode node, Path metaInfDir) throws BootstrapDependencyProcessingException { - final Path p = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); - return Files.exists(p) ? processPlatformArtifact(node, p) : false; + private ExtensionDependency getExtensionDependencyOrNull(DependencyNode node, Artifact artifact) + throws BootstrapDependencyProcessingException { + ExtensionDependency extDep = ExtensionDependency.get(node); + if (extDep != null) { + return extDep; + } + final ExtensionInfo extInfo = getExtensionInfoOrNull(artifact); + if (extInfo != null) { + Collection exclusions; + if (!exclusionStack.isEmpty()) { + if (exclusionStack.size() == 1) { + exclusions = exclusionStack.peekLast(); + } else { + exclusions = new ArrayList<>(); + for (Collection set : exclusionStack) { + exclusions.addAll(set); + } + } + } else { + exclusions = Collections.emptyList(); + } + return new ExtensionDependency(extInfo, node, exclusions); + } + return null; } - /** - * @return true in case the node is a Quarkus runtime extension artifact, otherwise - false - * @throws BootstrapDependencyProcessingException in case of a failure - */ - private boolean processPlatformArtifact(DependencyNode node, Path descriptor) + private void visitExtensionDependency(ExtensionDependency extDep) throws BootstrapDependencyProcessingException { - final Properties rtProps = resolveDescriptor(descriptor); - if (rtProps == null) { - return false; + + managedDeps.add(new Dependency(extDep.info.deploymentArtifact, JavaScopes.COMPILE)); + + collectConditionalDependencies(extDep); + + if (collectingTopRuntimeNodes) { + collectingTopRuntimeNodes = false; + topExtensionDeps.add(extDep); } - appBuilder.handleExtensionProperties(rtProps, node.getArtifact().toString()); - final String value = rtProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); - if (value == null) { - return false; + if (lastVisitedRuntimeExtNode != null) { + lastVisitedRuntimeExtNode.addExtensionDependency(extDep); } - Artifact deploymentArtifact = toArtifact(value); - if (deploymentArtifact.getVersion() == null || deploymentArtifact.getVersion().isEmpty()) { - deploymentArtifact = deploymentArtifact.setVersion(node.getArtifact().getVersion()); + lastVisitedRuntimeExtNode = extDep; + } + + private void collectConditionalDependencies(ExtensionDependency dependent) + throws BootstrapDependencyProcessingException { + if (dependent.info.conditionalDeps.length == 0 || dependent.conditionalDepsQueued) { + return; } - managedDeps.add(new Dependency(deploymentArtifact, JavaScopes.COMPILE)); - if (collectExtensions) { - node.setData(QUARKUS_DEPLOYMENT_ARTIFACT, deploymentArtifact); - runtimeNodes.add(node); + dependent.conditionalDepsQueued = true; + + final DependencySelector selector = dependent.exclusions == null ? null + : new ExclusionDependencySelector(dependent.exclusions); + for (Artifact conditionalArtifact : dependent.info.conditionalDeps) { + if (selector != null && !selector.selectDependency(new Dependency(conditionalArtifact, "runtime"))) { + continue; + } + final ExtensionInfo conditionalInfo = getExtensionInfoOrNull(conditionalArtifact); + final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalInfo, dependent); + conditionalDepsToProcess.add(conditionalDep); + collectConditionalDependencies(conditionalDep.getExtensionDependency()); } - return true; } - private void replaceWith(DependencyNode originalNode, DependencyNode newNode) + private ExtensionInfo getExtensionInfoOrNull(Artifact artifact) throws BootstrapDependencyProcessingException { + if (!artifact.getExtension().equals("jar")) { + return null; + } + final AppArtifactKey extKey = DependencyNodeUtils.toKey(artifact); + ExtensionInfo ext = allExtensions.get(extKey); + if (ext != null) { + return ext; + } + + artifact = resolve(artifact); + final Path path = artifact.getFile().toPath(); + if (Files.isDirectory(path)) { + ext = createExtensionInfoOrNull(artifact, path.resolve(BootstrapConstants.DESCRIPTOR_PATH)); + } else { + try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) { + ext = createExtensionInfoOrNull(artifact, artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH)); + } catch (IOException e) { + throw new DeploymentInjectionException("Failed to read " + path, e); + } + } + allExtensions.put(extKey, ext); + return ext; + } + + private ExtensionInfo createExtensionInfoOrNull(Artifact artifact, Path descriptor) throws BootstrapDependencyProcessingException { - List children = newNode.getChildren(); - if (children.isEmpty()) { + final Properties rtProps = readDescriptor(descriptor); + if (rtProps == null) { + return null; + } + return new ExtensionInfo(artifact, rtProps); + } + + private void injectDeploymentDependencies(ExtensionDependency extDep) + throws BootstrapDependencyProcessingException { + log.debugf("Injecting deployment dependency %s", extDep.info.deploymentArtifact); + final DependencyNode deploymentNode = collectDependencies(extDep.info.deploymentArtifact, extDep.exclusions); + final List deploymentDeps = deploymentNode.getChildren(); + if (!replaceDirectDepBranch(extDep, deploymentDeps)) { throw new BootstrapDependencyProcessingException( - "No dependencies collected for Quarkus extension deployment artifact " + newNode.getArtifact() - + " while at least the corresponding runtime artifact " + originalNode.getArtifact() - + " is expected"); + "Quarkus extension deployment artifact " + deploymentNode.getArtifact() + + " does not appear to depend on the corresponding runtime artifact " + + extDep.info.runtimeArtifact); } - log.debugf("Injecting deployment dependency %s", newNode); - originalNode.setData(QUARKUS_RUNTIME_ARTIFACT, originalNode.getArtifact()); - originalNode.setArtifact(newNode.getArtifact()); - originalNode.getDependency().setArtifact(newNode.getArtifact()); - originalNode.setChildren(children); + final DependencyNode runtimeNode = extDep.runtimeNode; + runtimeNode.setData(QUARKUS_RUNTIME_ARTIFACT, runtimeNode.getArtifact()); + runtimeNode.setArtifact(deploymentNode.getArtifact()); + runtimeNode.getDependency().setArtifact(deploymentNode.getArtifact()); + runtimeNode.setChildren(deploymentDeps); + } + + 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) @@ -187,25 +383,23 @@ private DependencyNode collectDependencies(Artifact artifact, Collection exclusions; + boolean conditionalDepsQueued; + private List runtimeExtensionDeps; + + ExtensionDependency(ExtensionInfo info, DependencyNode node, Collection exclusions) { + this.runtimeNode = node; + this.info = info; + this.exclusions = exclusions; + + @SuppressWarnings("unchecked") + final Map data = (Map) node.getData(); + if (data.isEmpty()) { + node.setData(QUARKUS_EXTENSION_DEPENDENCY, this); + } else if (data.put(QUARKUS_EXTENSION_DEPENDENCY, this) != null) { + throw new IllegalStateException( + "Dependency node " + node + " has already been associated with an extension dependency"); + } + } + + void addExtensionDependency(ExtensionDependency dep) { + if (runtimeExtensionDeps == null) { + runtimeExtensionDeps = new ArrayList<>(); + } + runtimeExtensionDeps.add(dep); + } + } + + private class ConditionalDependency { + + 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"); + this.dependent = dependent; + } + + ExtensionDependency getExtensionDependency() { + if (dependency == null) { + final DefaultDependencyNode rtNode = new DefaultDependencyNode(new Dependency(info.runtimeArtifact, "runtime")); + rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())); + rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( + new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); + dependency = new ExtensionDependency(info, rtNode, dependent.exclusions); + } + return dependency; + } + + void activate() throws BootstrapDependencyProcessingException { + if (activated) { + return; + } + activated = true; + collectingTopRuntimeNodes = false; + final ExtensionDependency extDep = getExtensionDependency(); + final DependencyNode originalNode = collectDependencies(info.runtimeArtifact, extDep.exclusions); + final DependencyNode rtNode = extDep.runtimeNode; + // 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()) { + rtNode.setChildren(originalNode.getChildren()); + } else { + currentChildren.addAll(originalNode.getChildren()); + } + visitRuntimeDependency(rtNode); + dependent.runtimeNode.getChildren().add(rtNode); + } + + boolean isSatisfied() throws BootstrapDependencyProcessingException { + if (info.dependencyCondition == null) { + return true; + } + for (AppArtifactKey key : info.dependencyCondition) { + if (!isRuntimeArtifact(key)) { + return false; } } + return true; } - return new DefaultArtifact(groupId, artifactId, classifier, type, version); } - private static void illegalDependencyFormat(String str) { - throw new IllegalArgumentException("Bad artifact coordinates " + str - + ", expected format is :[:[:]]:"); + private static boolean isSameKey(Artifact a1, Artifact a2) { + return a2.getArtifactId().equals(a1.getArtifactId()) + && a2.getGroupId().equals(a1.getGroupId()) + && a2.getClassifier().equals(a1.getClassifier()) + && a2.getExtension().equals(a1.getExtension()); + } + + private static AppArtifact toAppArtifact(Artifact artifact) { + final AppArtifact appArtifact = new AppArtifact(artifact.getGroupId(), artifact.getArtifactId(), + artifact.getClassifier(), artifact.getExtension(), artifact.getVersion()); + final File file = artifact.getFile(); + if (file != null) { + appArtifact.setPaths(PathsCollection.of(file.toPath())); + } + return appArtifact; } } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyNodeUtils.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyNodeUtils.java new file mode 100644 index 0000000000000..cc62881d2e62b --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyNodeUtils.java @@ -0,0 +1,98 @@ +package io.quarkus.bootstrap.util; + +import io.quarkus.bootstrap.model.AppArtifactKey; +import java.io.PrintWriter; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.DependencyNode; + +public class DependencyNodeUtils { + + public static AppArtifactKey toKey(Artifact artifact) { + return new AppArtifactKey(artifact.getGroupId(), artifact.getArtifactId(), + artifact.getClassifier(), artifact.getExtension()); + } + + public static Artifact toArtifact(String str) { + return toArtifact(str, 0); + } + + private static Artifact toArtifact(String str, int offset) { + String groupId = null; + String artifactId = null; + String classifier = ""; + String type = "jar"; + String version = null; + + int colon = str.indexOf(':', offset); + final int length = str.length(); + if (colon < offset + 1 || colon == length - 1) { + illegalDependencyFormat(str); + } + groupId = str.substring(offset, colon); + offset = colon + 1; + colon = str.indexOf(':', offset); + if (colon < 0) { + artifactId = str.substring(offset, length); + } else { + if (colon == length - 1) { + illegalDependencyFormat(str); + } + artifactId = str.substring(offset, colon); + offset = colon + 1; + colon = str.indexOf(':', offset); + if (colon < 0) { + version = str.substring(offset, length); + } else { + if (colon == length - 1) { + illegalDependencyFormat(str); + } + type = str.substring(offset, colon); + offset = colon + 1; + colon = str.indexOf(':', offset); + if (colon < 0) { + version = str.substring(offset, length); + } else { + if (colon == length - 1) { + illegalDependencyFormat(str); + } + classifier = type; + type = str.substring(offset, colon); + version = str.substring(colon + 1); + } + } + } + return new DefaultArtifact(groupId, artifactId, classifier, type, version); + } + + private static void illegalDependencyFormat(String str) { + throw new IllegalArgumentException("Bad artifact coordinates " + str + + ", expected format is :[:|[::]]:"); + } + + public static void printTree(DependencyNode node) { + PrintWriter out = new PrintWriter(System.out); + try { + printTree(node, out); + } finally { + out.flush(); + } + } + + public static void printTree(DependencyNode node, PrintWriter out) { + out.println("Dependency tree for " + node.getArtifact()); + printTree(node, 0, out); + } + + private static void printTree(DependencyNode node, int depth, PrintWriter out) { + if (node.getArtifact() != null) { + for (int i = 0; i < depth; ++i) { + out.append(" "); + } + out.println(node.getArtifact()); + } + for (DependencyNode c : node.getChildren()) { + printTree(c, depth + 1, out); + } + } +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java index b4e8883286f9b..df79f6afc6923 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java @@ -6,6 +6,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; @@ -28,6 +29,28 @@ class BuildIT extends MojoTestBase { private RunningInvoker running; private File testDir; + @Test + void testConditionalDependencies() + throws MavenInvocationException, IOException, InterruptedException { + testDir = initProject("projects/conditional-dependencies", "projects/conditional-dependencies-build"); + + running = new RunningInvoker(testDir, false); + MavenProcessInvocationResult result = running.execute(Collections.singletonList("package"), + Collections.emptyMap()); + assertThat(result.getProcess().waitFor()).isZero(); + + final File targetDir = new File(testDir, "runner" + File.separator + "target"); + final File runnerJar = targetDir.toPath().resolve("quarkus-app").resolve("quarkus-run.jar").toFile(); + // make sure the jar can be read by JarInputStream + ensureManifestOfJarIsReadableByJarInputStream(runnerJar); + + final Path mainLib = targetDir.toPath().resolve("quarkus-app").resolve("lib").resolve("main"); + assertThat(mainLib.resolve("org.acme.acme-quarkus-ext-a-1.0-SNAPSHOT.jar")).exists(); + assertThat(mainLib.resolve("org.acme.acme-quarkus-ext-b-1.0-SNAPSHOT.jar")).exists(); + assertThat(mainLib.resolve("org.acme.acme-quarkus-ext-c-1.0-SNAPSHOT.jar")).exists(); + assertThat(mainLib.resolve("org.acme.acme-quarkus-ext-d-1.0-SNAPSHOT.jar")).doesNotExist(); + } + @Test void testMultiModuleAppRootWithNoSources() throws MavenInvocationException, IOException, InterruptedException { diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/pom.xml new file mode 100644 index 0000000000000..d6e30b4b43e5e --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + pom + + + io.quarkus + quarkus-bom + @project.version@ + @project.version@ + @project.version@ + 1.8 + 1.8 + true + 3.8.1 + UTF-8 + UTF-8 + 3.0.0-M5 + + + quarkus-ext-a + quarkus-ext-b + quarkus-ext-c + quarkus-ext-d + runner + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + org.acme + acme-quarkus-ext-a + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-a-deployment + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-b + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-b-deployment + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-c + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-c-deployment + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-d + 1.0-SNAPSHOT + + + org.acme + acme-quarkus-ext-d-deployment + 1.0-SNAPSHOT + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/pom.xml new file mode 100644 index 0000000000000..22c851394f0c3 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-a-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-a-deployment + Acme Quarkus Ext A - Deployment + + + + io.quarkus + quarkus-core-deployment + + + org.acme + acme-quarkus-ext-a + + + org.acme + acme-quarkus-ext-b-deployment + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/src/main/java/org/acme/quarkus/a/ext/deployment/AcmeQuarkusExtProcessor.java b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/src/main/java/org/acme/quarkus/a/ext/deployment/AcmeQuarkusExtProcessor.java new file mode 100644 index 0000000000000..ed9c3404b96b9 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/deployment/src/main/java/org/acme/quarkus/a/ext/deployment/AcmeQuarkusExtProcessor.java @@ -0,0 +1,15 @@ +package org.acme.quarkus.ext.a.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class AcmeQuarkusExtProcessor { + + private static final String FEATURE = "acme-quarkus-ext-a"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + +} diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/pom.xml new file mode 100644 index 0000000000000..6af11cc0079fa --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-a-parent + Acme Quarkus Ext A - Parent + + pom + + + runtime + deployment + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/runtime/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/runtime/pom.xml new file mode 100644 index 0000000000000..91a7bbb68de5f --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-a/runtime/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-a-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-a + Acme Quarkus Ext A - Runtime + + + + io.quarkus + quarkus-core + + + org.acme + acme-quarkus-ext-b + true + + + org.acme + acme-quarkus-ext-d + true + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + org.acme:acme-quarkus-ext-a-deployment:1.0-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/pom.xml new file mode 100644 index 0000000000000..1cf10cbb8eed8 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-b-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-b-deployment + Acme Quarkus Ext B - Deployment + + + + io.quarkus + quarkus-core-deployment + + + org.acme + acme-quarkus-ext-b + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/src/main/java/org/acme/quarkus/b/ext/deployment/AcmeQuarkusExtProcessor.java b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/src/main/java/org/acme/quarkus/b/ext/deployment/AcmeQuarkusExtProcessor.java new file mode 100644 index 0000000000000..ee1f074a94bf8 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/deployment/src/main/java/org/acme/quarkus/b/ext/deployment/AcmeQuarkusExtProcessor.java @@ -0,0 +1,15 @@ +package org.acme.quarkus.ext.b.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class AcmeQuarkusExtProcessor { + + private static final String FEATURE = "acme-quarkus-ext-b"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + +} diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/pom.xml new file mode 100644 index 0000000000000..96bc4d1ce86d6 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-b-parent + Acme Quarkus Ext B - Parent + + pom + + + runtime + deployment + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/runtime/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/runtime/pom.xml new file mode 100644 index 0000000000000..dfcf02a92b2cf --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-b/runtime/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-b-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-b + Acme Quarkus Ext B - Runtime + + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + org.acme:acme-quarkus-ext-b-deployment:1.0-SNAPSHOT + + org.acme:acme-quarkus-ext-c + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/pom.xml new file mode 100644 index 0000000000000..68f78bc284dba --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-c-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-c-deployment + Acme Quarkus Ext C - Deployment + + + + io.quarkus + quarkus-core-deployment + + + org.acme + acme-quarkus-ext-c + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/src/main/java/org/acme/quarkus/c/ext/deployment/AcmeQuarkusExtProcessor.java b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/src/main/java/org/acme/quarkus/c/ext/deployment/AcmeQuarkusExtProcessor.java new file mode 100644 index 0000000000000..2cca1bcae81e3 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/deployment/src/main/java/org/acme/quarkus/c/ext/deployment/AcmeQuarkusExtProcessor.java @@ -0,0 +1,15 @@ +package org.acme.quarkus.ext.c.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class AcmeQuarkusExtProcessor { + + private static final String FEATURE = "acme-quarkus-ext-c"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + +} diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/pom.xml new file mode 100644 index 0000000000000..56b0656a25abb --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-c-parent + Acme Quarkus Ext C - Parent + + pom + + + runtime + deployment + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/runtime/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/runtime/pom.xml new file mode 100644 index 0000000000000..2e2e6da52f4e8 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-c/runtime/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-c-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-c + Acme Quarkus Ext C - Runtime + + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + org.acme:acme-quarkus-ext-c-deployment:1.0-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/pom.xml new file mode 100644 index 0000000000000..9a1a5ddb11e4c --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-d-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-d-deployment + Acme Quarkus Ext D - Deployment + + + + io.quarkus + quarkus-core-deployment + + + org.acme + acme-quarkus-ext-d + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/src/main/java/org/acme/quarkus/d/ext/deployment/AcmeQuarkusExtProcessor.java b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/src/main/java/org/acme/quarkus/d/ext/deployment/AcmeQuarkusExtProcessor.java new file mode 100644 index 0000000000000..abd3e69e8173e --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/deployment/src/main/java/org/acme/quarkus/d/ext/deployment/AcmeQuarkusExtProcessor.java @@ -0,0 +1,15 @@ +package org.acme.quarkus.ext.d.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class AcmeQuarkusExtProcessor { + + private static final String FEATURE = "acme-quarkus-ext-d"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + +} diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/pom.xml new file mode 100644 index 0000000000000..c0c7bd64e648d --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-d-parent + Acme Quarkus Ext D - Parent + + pom + + + runtime + deployment + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/runtime/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/runtime/pom.xml new file mode 100644 index 0000000000000..dfbfc5c7b468e --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/quarkus-ext-d/runtime/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + org.acme + acme-quarkus-ext-d-parent + 1.0-SNAPSHOT + ../pom.xml + + + acme-quarkus-ext-d + Acme Quarkus Ext C - Runtime + + + + io.quarkus + quarkus-core + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + org.acme:acme-quarkus-ext-d-deployment:1.0-SNAPSHOT + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/pom.xml b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/pom.xml new file mode 100644 index 0000000000000..239d47b72dab9 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + + org.acme + quarkus-quickstart-multimodule-main + 1.0-SNAPSHOT + + + + + io.quarkus + quarkus-resteasy + + + org.acme + acme-quarkus-ext-a + + + + org.acme + acme-quarkus-ext-c + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + build + + + + + + + + + + + native + + + native + + + + native + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..78696e1cf40fb --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/conditional-dependencies/runner/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,22 @@ +package org.acme; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; + +@Path("/hello") +public class HelloResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } +}