From 9da5774331b11ab166b746d14f3717610e058422 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 11 Jan 2024 08:31:14 +0100 Subject: [PATCH] Incubating implementation of a more efficient ApplicationModel resolver for Maven projects --- .../quarkus/deployment/BootstrapConfig.java | 9 + .../CascadingConditionalDependenciesTest.java | 22 +- .../ConditionalDependencyScenarioTwoTest.java | 55 +- ...onalDependencyWithSingleConditionTest.java | 35 +- ...tionalDependencyWithTwoConditionsTest.java | 16 +- .../BootstrapFromOriginalJarTestBase.java | 76 +- .../DeploymentDependencyConvergenceTest.java | 68 + .../runnerjar/ReloadableFlagsTest.java | 71 + .../io/quarkus/maven/DependencyTreeMojo.java | 37 +- .../main/java/io/quarkus/maven/DevMojo.java | 2 + .../maven/QuarkusBootstrapProvider.java | 4 + .../maven/BasicDependencyTreeTestBase.java | 26 + .../ConditionalDependencyGraphMojoTest.java | 53 + .../ConditionalDependencyTreeMojoTest.java | 48 + .../maven/DependencyTreeMojoTestBase.java | 71 +- .../maven/DevDependencyTreeMojoTest.java | 2 +- .../maven/ProdDependencyTreeMojoTest.java | 2 +- .../maven/TestDependencyTreeMojoTest.java | 2 +- .../app-with-conditional-deps-1.jar.prod | 15 + .../app-with-conditional-graph-1.jar.prod | 30 + .../src/test/resources/test-app-1.jar.dev | 2 +- .../src/test/resources/test-app-1.jar.prod | 2 +- .../src/test/resources/test-app-1.jar.test | 2 +- .../model/ApplicationModelBuilder.java | 16 +- .../model/DefaultApplicationModel.java | 4 +- .../maven/dependency/DependencyFlags.java | 1 - .../resolver/CollectDependenciesBase.java | 3 +- .../resolver/ResolverSetupCleanup.java | 2 + .../bootstrap/resolver/TsArtifact.java | 12 +- .../resolver/BootstrapAppModelResolver.java | 57 +- .../ApplicationDependencyModelResolver.java | 1257 +++++++++++++++++ .../ApplicationDependencyTreeResolver.java | 3 +- .../maven/BootstrapModelResolver.java | 8 +- .../maven/DependencyLoggingConfig.java | 65 + .../bootstrap/util/DependencyUtils.java | 10 + 35 files changed, 1914 insertions(+), 174 deletions(-) create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/runnerjar/DeploymentDependencyConvergenceTest.java create mode 100644 core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ReloadableFlagsTest.java create mode 100644 devtools/maven/src/test/java/io/quarkus/maven/BasicDependencyTreeTestBase.java create mode 100644 devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyGraphMojoTest.java create mode 100644 devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoTest.java create mode 100644 devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod create mode 100644 devtools/maven/src/test/resources/app-with-conditional-graph-1.jar.prod create mode 100644 independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java create mode 100644 independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DependencyLoggingConfig.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java index 8f3e059be79f0..fc4e1e776034c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/BootstrapConfig.java @@ -41,6 +41,15 @@ public class BootstrapConfig { @ConfigItem(defaultValue = "false") boolean disableJarCache; + /** + * A temporary option introduced to avoid a logging warning when {@code }-Dquarkus.bootstrap.incubating-model-resolver} + * is added to the build command line. + * This option enables an incubating implementation of the Quarkus Application Model resolver. + * This option will be removed as soon as the incubating implementation becomes the default one. + */ + @ConfigItem(defaultValue = "false") + boolean incubatingModelResolver; + /** * Whether to throw an error, warn or silently ignore misaligned platform BOM imports */ 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 index cb87d130aa054..4e668d0ca9cad 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/CascadingConditionalDependenciesTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/CascadingConditionalDependenciesTest.java @@ -5,14 +5,16 @@ import java.util.HashSet; import java.util.Set; +import org.eclipse.aether.util.artifact.JavaScopes; + import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsQuarkusExt; import io.quarkus.deployment.runnerjar.BootstrapFromOriginalJarTestBase; +import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.GACTV; public class CascadingConditionalDependenciesTest extends BootstrapFromOriginalJarTestBase { @@ -70,22 +72,28 @@ protected TsArtifact composeApplication() { protected void assertAppModel(ApplicationModel model) throws Exception { final Set expected = new HashSet<>(); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-e-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-e-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-f-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-f-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); assertEquals(expected, getDeploymentOnlyDeps(model)); } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyScenarioTwoTest.java b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyScenarioTwoTest.java index 4d4bc3f0881b7..a55543f9c1710 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyScenarioTwoTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyScenarioTwoTest.java @@ -4,16 +4,17 @@ import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; + +import org.eclipse.aether.util.artifact.JavaScopes; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsQuarkusExt; import io.quarkus.deployment.runnerjar.BootstrapFromOriginalJarTestBase; +import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.GACTV; public class ConditionalDependencyScenarioTwoTest extends BootstrapFromOriginalJarTestBase { @@ -114,54 +115,68 @@ protected TsArtifact composeApplication() { @Override protected void assertAppModel(ApplicationModel appModel) throws Exception { - final Set deploymentDeps = appModel.getDependencies().stream() - .filter(d -> d.isDeploymentCp() && !d.isRuntimeCp()).map(d -> new ArtifactDependency(d)) - .collect(Collectors.toSet()); + var deploymentDeps = getDeploymentOnlyDeps(appModel); + ; final Set expected = new HashSet<>(); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-f-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-f-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-g-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-g-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-h-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-h-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-k-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-k-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-l-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-l-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-j-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-j-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-m-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-m-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-n-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-n-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-i-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-i-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-o-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-o-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-p-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-p-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-r-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-r-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-s-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-s-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-t-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-t-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-u-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-u-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); 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 index a26d42a488b7f..bcd424cd8cbf3 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithSingleConditionTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithSingleConditionTest.java @@ -2,18 +2,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; + +import org.eclipse.aether.util.artifact.JavaScopes; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsQuarkusExt; import io.quarkus.deployment.runnerjar.BootstrapFromOriginalJarTestBase; +import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; -import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.GACTV; public class ConditionalDependencyWithSingleConditionTest extends BootstrapFromOriginalJarTestBase { @@ -42,19 +41,19 @@ protected TsArtifact composeApplication() { @Override protected void assertAppModel(ApplicationModel appModel) throws Exception { - final Set deploymentDeps = appModel.getDependencies().stream() - .filter(d -> d.isDeploymentCp() && !d.isRuntimeCp()).map(d -> new ArtifactDependency(d)) - .collect(Collectors.toSet()); - final Set expected = new HashSet<>(); - expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile", - DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile", - DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime", - DependencyFlags.DEPLOYMENT_CP)); - assertEquals(expected, deploymentDeps); + var expected = Set.of( + new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency( + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP)); + assertEquals(expected, getDeploymentOnlyDeps(appModel)); } } 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 index 9f73397e60e3d..aa6a14336d556 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithTwoConditionsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/conditionaldeps/ConditionalDependencyWithTwoConditionsTest.java @@ -5,14 +5,16 @@ import java.util.HashSet; import java.util.Set; +import org.eclipse.aether.util.artifact.JavaScopes; + import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.TsArtifact; import io.quarkus.bootstrap.resolver.TsQuarkusExt; import io.quarkus.deployment.runnerjar.BootstrapFromOriginalJarTestBase; +import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactDependency; import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.DependencyFlags; -import io.quarkus.maven.dependency.GACTV; public class ConditionalDependencyWithTwoConditionsTest extends BootstrapFromOriginalJarTestBase { @@ -46,16 +48,20 @@ protected TsArtifact composeApplication() { protected void assertAppModel(ApplicationModel model) throws Exception { final Set expected = new HashSet<>(); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-c-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-a-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), "runtime", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-b-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); expected.add(new ArtifactDependency( - new GACTV(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), "compile", + ArtifactCoords.jar(TsArtifact.DEFAULT_GROUP_ID, "ext-d-deployment", TsArtifact.DEFAULT_VERSION), + JavaScopes.COMPILE, DependencyFlags.DEPLOYMENT_CP)); assertEquals(expected, getDeploymentOnlyDeps(model)); } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/BootstrapFromOriginalJarTestBase.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/BootstrapFromOriginalJarTestBase.java index 0b7a314795338..d9886d3bdd8e0 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/BootstrapFromOriginalJarTestBase.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/BootstrapFromOriginalJarTestBase.java @@ -75,24 +75,27 @@ protected QuarkusBootstrap.Builder initBootstrapBuilder() throws Exception { .setAppModelResolver(resolver) .setTest(isBootstrapForTestMode()); - if (createWorkspace()) { + if (createWorkspace() || !wsModules.isEmpty()) { System.setProperty("basedir", ws.toAbsolutePath().toString()); final Model appPom = appJar.getPomModel(); - final List bomModules = (appPom.getDependencyManagement() == null ? List. of() - : appPom.getDependencyManagement().getDependencies()).stream() - .filter(d -> "import".equals(d.getScope()) - && d.getGroupId().equals(appPom.getGroupId())) - .collect(Collectors.toList()); - - final List depModules = appPom.getDependencies().stream() - .filter(d -> d.getGroupId().equals(appPom.getGroupId()) && - (d.getType().isEmpty() || ArtifactCoords.TYPE_JAR.equals(d.getType()))) - .collect(Collectors.toList()); + List bomModules = List.of(); + List depModules = List.of(); + if (createWorkspace()) { + bomModules = (appPom.getDependencyManagement() == null ? List. of() + : appPom.getDependencyManagement().getDependencies()).stream() + .filter(d -> "import".equals(d.getScope()) + && d.getGroupId().equals(appPom.getGroupId())) + .collect(Collectors.toList()); + depModules = appPom.getDependencies().stream() + .filter(d -> d.getGroupId().equals(appPom.getGroupId()) && + (d.getType().isEmpty() || ArtifactCoords.TYPE_JAR.equals(d.getType()))) + .collect(Collectors.toList()); + } final Path appModule; final Path appPomXml; - if (depModules.isEmpty() && bomModules.isEmpty() || appPom.getParent() != null) { + if (depModules.isEmpty() && bomModules.isEmpty() && wsModules.isEmpty() || appPom.getParent() != null) { appModule = ws; appPomXml = ws.resolve("pom.xml"); ModelUtils.persistModel(appPomXml, appPom); @@ -130,31 +133,32 @@ protected QuarkusBootstrap.Builder initBootstrapBuilder() throws Exception { ModelUtils.persistModel(appPomXml, appPom); // dependency modules - final Map managedVersions = new HashMap<>(); - collectManagedDeps(appPom, managedVersions); - for (Dependency moduleDep : depModules) { - parentPom.getModules().add(moduleDep.getArtifactId()); - final String moduleVersion = moduleDep.getVersion() == null - ? managedVersions.get(ArtifactKey.of(moduleDep.getGroupId(), moduleDep.getArtifactId(), - moduleDep.getClassifier(), moduleDep.getType())) - : moduleDep.getVersion(); - Model modulePom = ModelUtils.readModel(resolver - .resolve(ArtifactCoords.pom(moduleDep.getGroupId(), moduleDep.getArtifactId(), moduleVersion)) - .getResolvedPaths().getSinglePath()); - modulePom.setParent(parent); - final Path moduleDir = IoUtils.mkdirs(ws.resolve(modulePom.getArtifactId())); - ModelUtils.persistModel(moduleDir.resolve("pom.xml"), modulePom); - final Path resolvedJar = resolver - .resolve(ArtifactCoords.of(modulePom.getGroupId(), modulePom.getArtifactId(), - moduleDep.getClassifier(), moduleDep.getType(), modulePom.getVersion())) - .getResolvedPaths() - .getSinglePath(); - final Path moduleTargetDir = moduleDir.resolve("target"); - ZipUtils.unzip(resolvedJar, moduleTargetDir.resolve("classes")); - IoUtils.copy(resolvedJar, - moduleTargetDir.resolve(modulePom.getArtifactId() + "-" + modulePom.getVersion() + ".jar")); + if (!depModules.isEmpty()) { + final Map managedVersions = new HashMap<>(); + collectManagedDeps(appPom, managedVersions); + for (Dependency moduleDep : depModules) { + parentPom.getModules().add(moduleDep.getArtifactId()); + final String moduleVersion = moduleDep.getVersion() == null + ? managedVersions.get(ArtifactKey.of(moduleDep.getGroupId(), moduleDep.getArtifactId(), + moduleDep.getClassifier(), moduleDep.getType())) + : moduleDep.getVersion(); + Model modulePom = ModelUtils.readModel(resolver + .resolve(ArtifactCoords.pom(moduleDep.getGroupId(), moduleDep.getArtifactId(), moduleVersion)) + .getResolvedPaths().getSinglePath()); + modulePom.setParent(parent); + final Path moduleDir = IoUtils.mkdirs(ws.resolve(modulePom.getArtifactId())); + ModelUtils.persistModel(moduleDir.resolve("pom.xml"), modulePom); + final Path resolvedJar = resolver + .resolve(ArtifactCoords.of(modulePom.getGroupId(), modulePom.getArtifactId(), + moduleDep.getClassifier(), moduleDep.getType(), modulePom.getVersion())) + .getResolvedPaths() + .getSinglePath(); + final Path moduleTargetDir = moduleDir.resolve("target"); + ZipUtils.unzip(resolvedJar, moduleTargetDir.resolve("classes")); + IoUtils.copy(resolvedJar, + moduleTargetDir.resolve(modulePom.getArtifactId() + "-" + modulePom.getVersion() + ".jar")); + } } - for (TsArtifact module : wsModules) { parentPom.getModules().add(module.getArtifactId()); Model modulePom = module.getPomModel(); diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/DeploymentDependencyConvergenceTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/DeploymentDependencyConvergenceTest.java new file mode 100644 index 0000000000000..77d6c52bee6c1 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/DeploymentDependencyConvergenceTest.java @@ -0,0 +1,68 @@ +package io.quarkus.deployment.runnerjar; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Set; + +import org.eclipse.aether.util.artifact.JavaScopes; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.maven.dependency.*; + +public class DeploymentDependencyConvergenceTest extends BootstrapFromOriginalJarTestBase { + + @Override + protected TsArtifact composeApplication() { + + var libE10 = install(TsArtifact.jar("lib-e", "1.0")); + var libE20 = install(TsArtifact.jar("lib-e", "2.0")); + var libE30 = install(TsArtifact.jar("lib-e", "3.0")); + + var libD10 = install(TsArtifact.jar("lib-d", "1.0")); + var libD20 = install(TsArtifact.jar("lib-d", "2.0")); + + var libC10 = install(TsArtifact.jar("lib-c", "1.0") + .addDependency(libD10) + .addDependency(libE10)); + + var libB10 = install(TsArtifact.jar("lib-b", "1.0")); + var libB20 = install(TsArtifact.jar("lib-b", "2.0") + .addDependency(libC10)); + + var extA = new TsQuarkusExt("ext-a"); + addToExpectedLib(extA.getRuntime()); + extA.getDeployment() + .addManagedDependency(libD20) + .addManagedDependency(libE20) + .addDependency(libB10); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addManagedDependency(libB20) + .addManagedDependency(libE30) + .addDependency(extA); + } + + @Override + protected void assertAppModel(ApplicationModel model) throws Exception { + final Set expected = Set.of( + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a", "1"), JavaScopes.COMPILE, + DependencyFlags.RUNTIME_CP, DependencyFlags.DEPLOYMENT_CP, DependencyFlags.DIRECT, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT), + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), + JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "lib-b", "2.0"), JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "lib-c", "1.0"), JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "lib-d", "2.0"), JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP), + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "lib-e", "3.0"), JavaScopes.COMPILE, + DependencyFlags.DEPLOYMENT_CP)); + assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.DEPLOYMENT_CP)); + } +} diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ReloadableFlagsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ReloadableFlagsTest.java new file mode 100644 index 0000000000000..5614d62716483 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ReloadableFlagsTest.java @@ -0,0 +1,71 @@ +package io.quarkus.deployment.runnerjar; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.aether.util.artifact.JavaScopes; + +import io.quarkus.bootstrap.model.ApplicationModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactDependency; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.maven.dependency.DependencyFlags; + +public class ReloadableFlagsTest extends BootstrapFromOriginalJarTestBase { + + @Override + protected TsArtifact composeApplication() { + + var transitive = TsArtifact.jar("acme-transitive"); + addWorkspaceModule(transitive); + addToExpectedLib(transitive); + + var common = TsArtifact.jar("acme-common"); + common.addDependency(transitive); + addWorkspaceModule(common); + addToExpectedLib(common); + + var lib = TsArtifact.jar("acme-lib"); + lib.addDependency(common); + addWorkspaceModule(lib); + addToExpectedLib(lib); + + var externalLib = TsArtifact.jar("external-lib"); + externalLib.addDependency(common); + addToExpectedLib(externalLib); + + var myExt = new TsQuarkusExt("my-ext"); + addToExpectedLib(myExt.getRuntime()); + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(common) + .addDependency(lib) + .addDependency(externalLib) + .addDependency(myExt); + } + + @Override + protected void assertAppModel(ApplicationModel model) { + assertThat(model.getWorkspaceModules().stream().map(WorkspaceModule::getId).collect(Collectors.toSet())) + .isEqualTo(Set.of( + WorkspaceModuleId.of(TsArtifact.DEFAULT_GROUP_ID, "acme-transitive", TsArtifact.DEFAULT_VERSION), + WorkspaceModuleId.of(TsArtifact.DEFAULT_GROUP_ID, "acme-common", TsArtifact.DEFAULT_VERSION), + WorkspaceModuleId.of(TsArtifact.DEFAULT_GROUP_ID, "acme-lib", TsArtifact.DEFAULT_VERSION), + WorkspaceModuleId.of(TsArtifact.DEFAULT_GROUP_ID, "app", TsArtifact.DEFAULT_VERSION))); + + final Set expected = Set.of( + new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "acme-lib", "1"), JavaScopes.COMPILE, + DependencyFlags.RUNTIME_CP, DependencyFlags.DEPLOYMENT_CP, DependencyFlags.DIRECT, + DependencyFlags.RELOADABLE, DependencyFlags.WORKSPACE_MODULE)); + + assertThat(getDependenciesWithFlag(model, DependencyFlags.RELOADABLE)).isEqualTo(expected); + } +} diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java index 22891e8f44de4..6e027daff769d 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.OpenOption; import java.nio.file.StandardOpenOption; import java.util.List; import java.util.function.Consumer; @@ -21,7 +22,9 @@ import org.eclipse.aether.repository.RemoteRepository; import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyModelResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; +import io.quarkus.bootstrap.resolver.maven.DependencyLoggingConfig; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.maven.components.QuarkusWorkspaceProvider; import io.quarkus.maven.dependency.ArtifactCoords; @@ -48,9 +51,27 @@ public class DependencyTreeMojo extends AbstractMojo { * Target launch mode corresponding to {@link io.quarkus.runtime.LaunchMode} for which the dependency tree should be built. * {@code io.quarkus.runtime.LaunchMode.NORMAL} is the default. */ - @Parameter(property = "mode", required = false, defaultValue = "prod") + @Parameter(property = "mode", defaultValue = "prod") String mode; + /** + * INCUBATING option, enabled with @{code -Dquarkus.bootstrap.incubating-model-resolver} system or project property. + *

+ * Whether to log dependency properties, such as on which classpath they belong, whether they are hot-reloadable in dev + * mode, etc. + */ + @Parameter(property = "verbose") + boolean verbose; + + /** + * INCUBATING option, enabled with @{code -Dquarkus.bootstrap.incubating-model-resolver} system or project property. + *

+ * Whether to log all dependencies of each dependency node in a tree, adding {@code [+]} suffix + * to those whose dependencies are not expanded. + */ + @Parameter(property = "graph") + boolean graph; + /** * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of writing to * the console. @@ -77,8 +98,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { final BufferedWriter bw; try { Files.createDirectories(outputFile.toPath().getParent()); - bw = writer = Files.newBufferedWriter(outputFile.toPath(), - appendOutput && outputFile.exists() ? StandardOpenOption.APPEND : StandardOpenOption.CREATE); + final OpenOption[] openOptions = appendOutput && outputFile.exists() + ? new OpenOption[] { StandardOpenOption.APPEND } + : new OpenOption[0]; + bw = writer = Files.newBufferedWriter(outputFile.toPath(), openOptions); } catch (IOException e) { throw new MojoExecutionException("Failed to initialize file output writer", e); } @@ -124,7 +147,13 @@ private void logTree(final Consumer log) throws MojoExecutionException { "Parameter 'mode' was set to '" + mode + "' while expected one of 'dev', 'test' or 'prod'"); } } - modelResolver.setBuildTreeLogger(log); + modelResolver.setIncubatingModelResolver( + ApplicationDependencyModelResolver.isIncubatingEnabled(project.getProperties())); + modelResolver.setDepLogConfig(DependencyLoggingConfig.builder() + .setMessageConsumer(log) + .setVerbose(verbose) + .setGraph(graph) + .build()); modelResolver.resolveModel(appArtifact); } catch (Exception e) { throw new MojoExecutionException("Failed to resolve application model " + appArtifact + " dependencies", e); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 9dc4a552aad8c..05bd7d3b81622 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -96,6 +96,7 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyModelResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContextConfig; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; @@ -1360,6 +1361,7 @@ private QuarkusDevModeLauncher newLauncher(Boolean debugPortOk, String bootstrap .setDevMode(true) .setTest(LaunchMode.TEST.equals(getLaunchModeClasspath())) .setCollectReloadableDependencies(!noDeps) + .setIncubatingModelResolver(ApplicationDependencyModelResolver.isIncubatingEnabled(project.getProperties())) .resolveModel(mvnCtx.getCurrentProject().getAppArtifact()); } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java index 1d8f7559b2fb2..d83cb4fa9f46b 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusBootstrapProvider.java @@ -37,6 +37,7 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyModelResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; @@ -204,7 +205,10 @@ private MavenArtifactResolver artifactResolver(QuarkusBootstrapMojo mojo, Launch private CuratedApplication doBootstrap(QuarkusBootstrapMojo mojo, LaunchMode mode) throws MojoExecutionException { + final BootstrapAppModelResolver modelResolver = new BootstrapAppModelResolver(artifactResolver(mojo, mode)) + .setIncubatingModelResolver( + ApplicationDependencyModelResolver.isIncubatingEnabled(mojo.mavenProject().getProperties())) .setDevMode(mode == LaunchMode.DEVELOPMENT) .setTest(mode == LaunchMode.TEST) .setCollectReloadableDependencies(mode == LaunchMode.DEVELOPMENT || mode == LaunchMode.TEST); diff --git a/devtools/maven/src/test/java/io/quarkus/maven/BasicDependencyTreeTestBase.java b/devtools/maven/src/test/java/io/quarkus/maven/BasicDependencyTreeTestBase.java new file mode 100644 index 0000000000000..ef929a5567a5e --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/BasicDependencyTreeTestBase.java @@ -0,0 +1,26 @@ +package io.quarkus.maven; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsDependency; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +abstract class BasicDependencyTreeTestBase extends DependencyTreeMojoTestBase { + + @Override + protected void initRepo() { + + 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); + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyGraphMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyGraphMojoTest.java new file mode 100644 index 0000000000000..26509fd19828f --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyGraphMojoTest.java @@ -0,0 +1,53 @@ +package io.quarkus.maven; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class ConditionalDependencyGraphMojoTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "prod"; + } + + @Override + protected boolean isGraph() { + return true; + } + + @Override + protected boolean isIncubatingModelResolver() { + return true; + } + + @Override + protected void initRepo() { + + final TsQuarkusExt coreExt = new TsQuarkusExt("test-core-ext"); + + var tomatoExt = new TsQuarkusExt("quarkus-tomato").addDependency(coreExt); + var mozzarellaExt = new TsQuarkusExt("quarkus-mozzarella").addDependency(coreExt); + var basilExt = new TsQuarkusExt("quarkus-basil").addDependency(coreExt); + + var oilJar = TsArtifact.jar("quarkus-oil"); + + var capreseExt = new TsQuarkusExt("quarkus-caprese") + .setDependencyCondition(tomatoExt, mozzarellaExt, basilExt) + .addDependency(coreExt); + capreseExt.getDeployment().addDependency(oilJar); + capreseExt.install(repoBuilder); + + var saladExt = new TsQuarkusExt("quarkus-salad") + .setConditionalDeps(capreseExt) + .addDependency(coreExt); + + app = TsArtifact.jar("app-with-conditional-graph") + .addDependency(tomatoExt) + .addDependency(mozzarellaExt) + .addDependency(basilExt) + .addDependency(saladExt) + .addDependency(oilJar); + + appModel = app.getPomModel(); + app.install(repoBuilder); + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoTest.java new file mode 100644 index 0000000000000..45d10a0417b62 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoTest.java @@ -0,0 +1,48 @@ +package io.quarkus.maven; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class ConditionalDependencyTreeMojoTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "prod"; + } + + @Override + protected boolean isIncubatingModelResolver() { + return true; + } + + @Override + protected void initRepo() { + + final TsQuarkusExt coreExt = new TsQuarkusExt("test-core-ext"); + + var tomatoExt = new TsQuarkusExt("quarkus-tomato").addDependency(coreExt); + var mozzarellaExt = new TsQuarkusExt("quarkus-mozzarella").addDependency(coreExt); + var basilExt = new TsQuarkusExt("quarkus-basil").addDependency(coreExt); + + var oilJar = TsArtifact.jar("quarkus-oil"); + + var capreseExt = new TsQuarkusExt("quarkus-caprese") + .setDependencyCondition(tomatoExt, mozzarellaExt, basilExt) + .addDependency(coreExt); + capreseExt.getDeployment().addDependency(oilJar); + capreseExt.install(repoBuilder); + + var saladExt = new TsQuarkusExt("quarkus-salad") + .setConditionalDeps(capreseExt) + .addDependency(coreExt); + + app = TsArtifact.jar("app-with-conditional-deps") + .addDependency(tomatoExt) + .addDependency(mozzarellaExt) + .addDependency(basilExt) + .addDependency(saladExt) + .addDependency(oilJar); + + appModel = app.getPomModel(); + app.install(repoBuilder); + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java b/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java index bd99f28420ec7..db3a359c1d6d3 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java @@ -1,32 +1,28 @@ package io.quarkus.maven; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; -import java.io.BufferedReader; -import java.io.IOException; import java.io.PrintStream; -import java.nio.file.Files; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; -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.eclipse.aether.util.artifact.JavaScopes; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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; +import io.quarkus.maven.dependency.ArtifactCoords; -public abstract class DependencyTreeMojoTestBase { +abstract class DependencyTreeMojoTestBase { protected Path workDir; protected Path repoHome; @@ -50,22 +46,6 @@ public void setup() throws Exception { 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) { @@ -73,44 +53,47 @@ public void cleanup() { } } + protected abstract void initRepo(); + protected abstract String mode(); + protected boolean isGraph() { + return false; + } + + protected boolean isIncubatingModelResolver() { + return false; + } + @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.setArtifact(new DefaultArtifact(app.getGroupId(), app.getArtifactId(), app.getVersion(), + JavaScopes.COMPILE, app.getType(), app.getClassifier(), + new DefaultArtifactHandler(ArtifactCoords.TYPE_JAR))); mojo.project.setModel(appModel); mojo.project.setOriginalModel(appModel); + if (isIncubatingModelResolver()) { + mojo.project.getProperties().setProperty("quarkus.bootstrap.incubating-model-resolver", "true"); + } mojo.resolver = mvnResolver; mojo.mode = mode(); + mojo.graph = isGraph(); - final Path mojoLog = workDir.resolve("mojo.log"); + final Path mojoLog = workDir.resolve(getClass().getName() + ".log"); final PrintStream defaultOut = System.out; - try (PrintStream logOut = new PrintStream(mojoLog.toFile(), "UTF-8")) { + try (PrintStream logOut = new PrintStream(mojoLog.toFile(), StandardCharsets.UTF_8)) { System.setOut(logOut); mojo.execute(); } finally { System.setOut(defaultOut); } - assertEquals(readInLowCase(Path.of("").normalize().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; + assertThat(mojoLog).hasSameTextualContentAs( + Path.of("").normalize().toAbsolutePath() + .resolve("target").resolve("test-classes").resolve(app.getArtifactFileName() + "." + mode())); } } diff --git a/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java index bef26188da25e..5dd5c52df7412 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/DevDependencyTreeMojoTest.java @@ -1,6 +1,6 @@ package io.quarkus.maven; -public class DevDependencyTreeMojoTest extends DependencyTreeMojoTestBase { +public class DevDependencyTreeMojoTest extends BasicDependencyTreeTestBase { @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 index 81aa3c190b258..7e93473d3dc1b 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/ProdDependencyTreeMojoTest.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/ProdDependencyTreeMojoTest.java @@ -1,6 +1,6 @@ package io.quarkus.maven; -public class ProdDependencyTreeMojoTest extends DependencyTreeMojoTestBase { +public class ProdDependencyTreeMojoTest extends BasicDependencyTreeTestBase { @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 index fab83b72936e5..4ee403ebf5cf5 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/TestDependencyTreeMojoTest.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/TestDependencyTreeMojoTest.java @@ -1,6 +1,6 @@ package io.quarkus.maven; -public class TestDependencyTreeMojoTest extends DependencyTreeMojoTestBase { +public class TestDependencyTreeMojoTest extends BasicDependencyTreeTestBase { @Override protected String mode() { return "test"; diff --git a/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod b/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod new file mode 100644 index 0000000000000..5780caf8fafca --- /dev/null +++ b/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod @@ -0,0 +1,15 @@ +[info] Quarkus application PROD mode build dependency tree: +[info] io.quarkus.bootstrap.test:app-with-conditional-deps:pom:1 +[info] ├─ io.quarkus.bootstrap.test:quarkus-tomato-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-tomato: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:quarkus-mozzarella-deployment:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:quarkus-mozzarella:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:quarkus-basil-deployment:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:quarkus-basil:jar:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:quarkus-salad-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-salad:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:quarkus-caprese:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:quarkus-caprese-deployment:jar:1 (compile) +[info] └─ io.quarkus.bootstrap.test:quarkus-oil:jar:1 (compile) \ No newline at end of file diff --git a/devtools/maven/src/test/resources/app-with-conditional-graph-1.jar.prod b/devtools/maven/src/test/resources/app-with-conditional-graph-1.jar.prod new file mode 100644 index 0000000000000..77508e6965d59 --- /dev/null +++ b/devtools/maven/src/test/resources/app-with-conditional-graph-1.jar.prod @@ -0,0 +1,30 @@ +[info] Quarkus application PROD mode build dependency tree: +[info] io.quarkus.bootstrap.test:app-with-conditional-graph:pom:1 +[info] ├─ io.quarkus.bootstrap.test:quarkus-basil::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-mozzarella::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-salad::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-tomato::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-tomato-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-tomato: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:test-core-ext::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-mozzarella-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-core-ext-deployment::jar:1 (compile) [+] +[info] │ └─ io.quarkus.bootstrap.test:quarkus-mozzarella:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-basil-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-core-ext-deployment::jar:1 (compile) [+] +[info] │ └─ io.quarkus.bootstrap.test:quarkus-basil:jar:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext::jar:1 (compile) [+] +[info] ├─ io.quarkus.bootstrap.test:quarkus-salad-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:test-core-ext-deployment::jar:1 (compile) [+] +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-salad:jar:1 (compile) +[info] │ │ ├─ io.quarkus.bootstrap.test:test-core-ext::jar:1 (compile) [+] +[info] │ │ └─ io.quarkus.bootstrap.test:quarkus-caprese:jar:1 (compile) +[info] │ │ └─ io.quarkus.bootstrap.test:test-core-ext::jar:1 (compile) [+] +[info] │ └─ io.quarkus.bootstrap.test:quarkus-caprese-deployment:jar:1 (compile) +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-caprese::jar:1 (compile) [+] +[info] │ ├─ io.quarkus.bootstrap.test:quarkus-oil::jar:1 (compile) [+] +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext-deployment::jar:1 (compile) [+] +[info] └─ io.quarkus.bootstrap.test:quarkus-oil:jar:1 (compile) \ No newline at end of file diff --git a/devtools/maven/src/test/resources/test-app-1.jar.dev b/devtools/maven/src/test/resources/test-app-1.jar.dev index 1fb959fdae99b..d43162a11db94 100644 --- a/devtools/maven/src/test/resources/test-app-1.jar.dev +++ b/devtools/maven/src/test/resources/test-app-1.jar.dev @@ -1,4 +1,4 @@ -[info] quarkus application dev mode build dependency tree: +[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) diff --git a/devtools/maven/src/test/resources/test-app-1.jar.prod b/devtools/maven/src/test/resources/test-app-1.jar.prod index 5c460135a1273..bf7d0a9836aae 100644 --- a/devtools/maven/src/test/resources/test-app-1.jar.prod +++ b/devtools/maven/src/test/resources/test-app-1.jar.prod @@ -1,4 +1,4 @@ -[info] quarkus application prod mode build dependency tree: +[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) diff --git a/devtools/maven/src/test/resources/test-app-1.jar.test b/devtools/maven/src/test/resources/test-app-1.jar.test index 40bc76f40c28c..832397770b902 100644 --- a/devtools/maven/src/test/resources/test-app-1.jar.test +++ b/devtools/maven/src/test/resources/test-app-1.jar.test @@ -1,4 +1,4 @@ -[info] quarkus application test mode build dependency tree: +[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) diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java index 0dcc5c24da325..95784e1755be8 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModelBuilder.java @@ -10,6 +10,8 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; import org.jboss.logging.Logger; @@ -36,13 +38,13 @@ public class ApplicationModelBuilder { ResolvedDependency appArtifact; final Map dependencies = new LinkedHashMap<>(); - final Set parentFirstArtifacts = new HashSet<>(); - final Set runnerParentFirstArtifacts = new HashSet<>(); - final List excludedArtifacts = new ArrayList<>(); - final Map> excludedResources = new HashMap<>(0); - final Set lesserPriorityArtifacts = new HashSet<>(); - final Set reloadableWorkspaceModules = new HashSet<>(); - final List extensionCapabilities = new ArrayList<>(); + final Collection parentFirstArtifacts = new ConcurrentLinkedDeque<>(); + final Collection runnerParentFirstArtifacts = new ConcurrentLinkedDeque<>(); + final Collection excludedArtifacts = new ConcurrentLinkedDeque<>(); + final Map> excludedResources = new ConcurrentHashMap<>(); + final Collection lesserPriorityArtifacts = new ConcurrentLinkedDeque<>(); + final Collection reloadableWorkspaceModules = new ConcurrentLinkedDeque<>(); + final Collection extensionCapabilities = new ConcurrentLinkedDeque<>(); PlatformImports platformImports; final Map projectModules = new HashMap<>(); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/DefaultApplicationModel.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/DefaultApplicationModel.java index d245c1cad796c..b03ebc134dbcb 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/DefaultApplicationModel.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/DefaultApplicationModel.java @@ -29,8 +29,8 @@ public DefaultApplicationModel(ApplicationModelBuilder builder) { this.appArtifact = builder.appArtifact; this.dependencies = builder.buildDependencies(); this.platformImports = builder.platformImports; - this.capabilityContracts = builder.extensionCapabilities; - this.localProjectArtifacts = builder.reloadableWorkspaceModules; + this.capabilityContracts = List.copyOf(builder.extensionCapabilities); + this.localProjectArtifacts = Set.copyOf(builder.reloadableWorkspaceModules); this.excludedResources = builder.excludedResources; } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java index 641c677f562dd..cfc4b0539fbe9 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/DependencyFlags.java @@ -45,5 +45,4 @@ public interface DependencyFlags { */ int COMPILE_ONLY = 0b01000000000000; /* @formatter:on */ - } 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 ade5086b19147..dc86cd8b16d82 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 @@ -49,8 +49,7 @@ public void testCollectedDependencies() throws Exception { } // stripping the resolved paths final List resolvedDeps = getTestResolver().resolveModel(root.toArtifact()).getDependencies() - .stream() - .map(d -> new ArtifactDependency(d)).collect(Collectors.toList()); + .stream().map(ArtifactDependency::new).collect(Collectors.toList()); assertEquals(new HashSet<>(expected), new HashSet<>(resolvedDeps)); } 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 880ef8079b0bb..5a5d2666f48e5 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 @@ -18,6 +18,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyModelResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; @@ -148,6 +149,7 @@ protected boolean isBootstrapForTestMode() { protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { final BootstrapAppModelResolver appModelResolver = new BootstrapAppModelResolver(newArtifactResolver(currentProject)); + appModelResolver.setIncubatingModelResolver(ApplicationDependencyModelResolver.isIncubatingEnabled(null)); if (isBootstrapForTestMode()) { appModelResolver.setTest(true); } 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 21004a9983db8..c13b3c844e6de 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 @@ -15,7 +15,6 @@ import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.GACT; -import io.quarkus.maven.dependency.GACTV; /** * @@ -180,6 +179,10 @@ public TsArtifact addDependency(TsDependency dep) { return this; } + public TsArtifact addManagedDependency(TsArtifact a) { + return addManagedDependency(new TsDependency(a)); + } + public TsArtifact addManagedDependency(TsDependency dep) { if (managedDeps.isEmpty()) { managedDeps = new ArrayList<>(); @@ -239,9 +242,10 @@ public Model getPomModel() { } if (!managedDeps.isEmpty()) { - model.setDependencyManagement(new DependencyManagement()); + var dm = new DependencyManagement(); + model.setDependencyManagement(dm); for (TsDependency dep : managedDeps) { - model.getDependencyManagement().addDependency(dep.toPomDependency()); + dm.addDependency(dep.toPomDependency()); } } @@ -252,7 +256,7 @@ public Model getPomModel() { } public ArtifactCoords toArtifact() { - return new GACTV(groupId, artifactId, classifier, type, version); + return ArtifactCoords.of(groupId, artifactId, classifier, type, version); } /** 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 e7109757aa759..037ad003fea3f 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 @@ -34,8 +34,10 @@ import io.quarkus.bootstrap.BootstrapDependencyProcessingException; import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.model.ApplicationModelBuilder; +import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyModelResolver; import io.quarkus.bootstrap.resolver.maven.ApplicationDependencyTreeResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; +import io.quarkus.bootstrap.resolver.maven.DependencyLoggingConfig; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.util.DependencyUtils; import io.quarkus.bootstrap.workspace.ArtifactSources; @@ -55,17 +57,34 @@ public class BootstrapAppModelResolver implements AppModelResolver { protected final MavenArtifactResolver mvn; - protected Consumer buildTreeConsumer; + private DependencyLoggingConfig depLogConfig; protected boolean devmode; protected boolean test; private boolean collectReloadableDeps = true; + private boolean incubatingModelResolver; public BootstrapAppModelResolver(MavenArtifactResolver mvn) { this.mvn = mvn; } + /** + * Temporary method that will be removed once the incubating implementation becomes the default. + * + * @return this application model resolver + */ + public BootstrapAppModelResolver setIncubatingModelResolver(boolean incubatingModelResolver) { + this.incubatingModelResolver = incubatingModelResolver; + return this; + } + public void setBuildTreeLogger(Consumer buildTreeConsumer) { - this.buildTreeConsumer = buildTreeConsumer; + if (buildTreeConsumer != null) { + depLogConfig = DependencyLoggingConfig.builder().setMessageConsumer(buildTreeConsumer).build(); + } + } + + public void setDepLogConfig(DependencyLoggingConfig depLogConfig) { + this.depLogConfig = depLogConfig; } /** @@ -328,13 +347,33 @@ private ApplicationModel buildAppModel(ResolvedDependency appArtifact, } var collectRtDepsRequest = MavenArtifactResolver.newCollectRequest(artifact, directDeps, managedDeps, List.of(), repos); try { - ApplicationDependencyTreeResolver.newInstance() - .setArtifactResolver(mvn) - .setApplicationModelBuilder(appBuilder) - .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) - .setCollectCompileOnly(filteredProvidedDeps) - .setBuildTreeConsumer(buildTreeConsumer) - .resolve(collectRtDepsRequest); + long start = 0; + boolean logTime = false; + if (logTime) { + start = System.currentTimeMillis(); + } + if (incubatingModelResolver) { + ApplicationDependencyModelResolver.newInstance() + .setArtifactResolver(mvn) + .setApplicationModelBuilder(appBuilder) + .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) + .setCollectCompileOnly(filteredProvidedDeps) + .setDependencyLogging(depLogConfig) + .resolve(collectRtDepsRequest); + } else { + ApplicationDependencyTreeResolver.newInstance() + .setArtifactResolver(mvn) + .setApplicationModelBuilder(appBuilder) + .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) + .setCollectCompileOnly(filteredProvidedDeps) + .setBuildTreeConsumer(depLogConfig == null ? null : depLogConfig.getMessageConsumer()) + .resolve(collectRtDepsRequest); + } + if (logTime) { + System.err.println( + "Application model resolved in " + (System.currentTimeMillis() - start) + "ms, incubating=" + + incubatingModelResolver); + } } catch (BootstrapDependencyProcessingException e) { throw new AppModelResolverException( "Failed to inject extension deployment dependencies for " + appArtifact.toCompactCoords(), e); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java new file mode 100644 index 0000000000000..9a3b9e1855b4e --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyModelResolver.java @@ -0,0 +1,1257 @@ +package io.quarkus.bootstrap.resolver.maven; + +import static io.quarkus.bootstrap.util.DependencyUtils.getCoords; +import static io.quarkus.bootstrap.util.DependencyUtils.getKey; +import static io.quarkus.bootstrap.util.DependencyUtils.getWinner; +import static io.quarkus.bootstrap.util.DependencyUtils.hasWinner; +import static io.quarkus.bootstrap.util.DependencyUtils.newDependencyBuilder; +import static io.quarkus.bootstrap.util.DependencyUtils.toArtifact; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.BiConsumer; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositoryException; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.collection.DependencyGraphTransformationContext; +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.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.util.artifact.JavaScopes; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; +import org.eclipse.aether.util.graph.transformer.ConflictIdSorter; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; +import org.jboss.logging.Logger; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.BootstrapDependencyProcessingException; +import io.quarkus.bootstrap.model.ApplicationModelBuilder; +import io.quarkus.bootstrap.model.CapabilityContract; +import io.quarkus.bootstrap.model.PlatformImportsImpl; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.util.BootstrapUtils; +import io.quarkus.bootstrap.util.DependencyUtils; +import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.maven.dependency.DependencyFlags; +import io.quarkus.maven.dependency.ResolvedDependencyBuilder; +import io.quarkus.paths.PathTree; + +public class ApplicationDependencyModelResolver { + + private static final Logger log = Logger.getLogger(ApplicationDependencyModelResolver.class); + + private static final String QUARKUS_RUNTIME_ARTIFACT = "quarkus.runtime"; + private static final String QUARKUS_EXTENSION_DEPENDENCY = "quarkus.ext"; + + private static final String INCUBATING_MODEL_RESOLVER = "quarkus.bootstrap.incubating-model-resolver"; + + /* @formatter:off */ + private static final byte COLLECT_TOP_EXTENSION_RUNTIME_NODES = 0b001; + private static final byte COLLECT_DIRECT_DEPS = 0b010; + private static final byte COLLECT_RELOADABLE_MODULES = 0b100; + /* @formatter:on */ + + private static final Artifact[] NO_ARTIFACTS = new Artifact[0]; + + /** + * Temporary method that will be removed once this implementation becomes the default. + * + * @return true if this implementation is enabled + */ + public static boolean isIncubatingEnabled(Properties projectProperties) { + var value = System.getProperty(INCUBATING_MODEL_RESOLVER); + if (value == null && projectProperties != null) { + value = String.valueOf(projectProperties.get(INCUBATING_MODEL_RESOLVER)); + } + return Boolean.parseBoolean(value); + } + + public static ApplicationDependencyModelResolver newInstance() { + return new ApplicationDependencyModelResolver(); + } + + private final ExtensionInfo EXT_INFO_NONE = new ExtensionInfo(); + + private final List topExtensionDeps = new ArrayList<>(); + private final Map allExtensions = new ConcurrentHashMap<>(); + private List conditionalDepsToProcess = new ArrayList<>(); + + private final Map> artifactDeps = new HashMap<>(); + + private final Collection errors = new ConcurrentLinkedDeque<>(); + + private MavenArtifactResolver resolver; + private List managedDeps; + private ApplicationModelBuilder appBuilder; + private boolean collectReloadableModules; + private DependencyLoggingConfig depLogging; + private List collectCompileOnly; + + public ApplicationDependencyModelResolver setArtifactResolver(MavenArtifactResolver resolver) { + this.resolver = resolver; + return this; + } + + public ApplicationDependencyModelResolver setApplicationModelBuilder(ApplicationModelBuilder appBuilder) { + this.appBuilder = appBuilder; + return this; + } + + public ApplicationDependencyModelResolver setCollectReloadableModules(boolean collectReloadableModules) { + this.collectReloadableModules = collectReloadableModules; + return this; + } + + public ApplicationDependencyModelResolver setDependencyLogging(DependencyLoggingConfig depLogging) { + this.depLogging = depLogging; + return this; + } + + /** + * In addition to resolving dependencies for the build classpath, also resolve these compile-only dependencies + * and add them to the application model as {@link DependencyFlags#COMPILE_ONLY}. + * + * @param collectCompileOnly compile-only dependencies to add to the model + * @return self + */ + public ApplicationDependencyModelResolver setCollectCompileOnly(List collectCompileOnly) { + this.collectCompileOnly = collectCompileOnly; + return this; + } + + public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException { + this.managedDeps = collectRtDepsRequest.getManagedDependencies(); + // managed dependencies will be a bit augmented with every added extension, so let's load the properties early + collectPlatformProperties(); + this.managedDeps = managedDeps.isEmpty() ? new ArrayList<>() : managedDeps; + + DependencyNode root = resolveRuntimeDeps(collectRtDepsRequest); + processRuntimeDeps(root); + final List activatedConditionalDeps = activateConditionalDeps(); + + // resolve and inject deployment dependency branches for the top (first met) runtime extension nodes + injectDeployment(activatedConditionalDeps); + root = normalize(resolver.getSession(), root); + processDeploymentDeps(root); + + for (var d : appBuilder.getDependencies()) { + if (!d.isFlagSet(DependencyFlags.RELOADABLE) && !d.isFlagSet(DependencyFlags.VISITED)) { + clearReloadableFlag(d); + } + } + + for (var d : appBuilder.getDependencies()) { + d.clearFlag(DependencyFlags.VISITED); + if (d.isFlagSet(DependencyFlags.RELOADABLE)) { + appBuilder.addReloadableWorkspaceModule(d.getKey()); + } + appBuilder.addDependency(d); + } + + collectCompileOnly(collectRtDepsRequest, root); + } + + private List activateConditionalDeps() { + if (conditionalDepsToProcess.isEmpty()) { + return List.of(); + } + var activatedConditionalDeps = new ArrayList(); + boolean checkDependencyConditions = true; + while (!conditionalDepsToProcess.isEmpty() && checkDependencyConditions) { + checkDependencyConditions = false; + var unsatisfiedConditionalDeps = conditionalDepsToProcess; + conditionalDepsToProcess = new ArrayList<>(); + for (ConditionalDependency cd : unsatisfiedConditionalDeps) { + if (cd.isSatisfied()) { + cd.activate(); + activatedConditionalDeps.add(cd); + // if a dependency was activated, the remaining not satisfied conditions should be checked again + checkDependencyConditions = true; + } else { + conditionalDepsToProcess.add(cd); + } + } + } + return activatedConditionalDeps; + } + + private void processDeploymentDeps(DependencyNode root) { + var app = new AppDep(root); + var futures = new ArrayList>(); + app.scheduleChildVisits(futures, AppDep::scheduleDeploymentVisit); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + if (logErrors()) { + throw new RuntimeException( + "Failed to process Quarkus application deployment dependencies, please see the errors logged above for more details."); + } + for (var d : app.children) { + d.addToModel(); + } + + if (depLogging != null) { + new AppDepLogger().log(app); + } + } + + private boolean logErrors() { + if (!errors.isEmpty()) { + log.error("The following errors were encountered while processing Quarkus application dependencies:"); + var i = 1; + for (var error : errors) { + log.error(i++ + ")", error); + } + return true; + } + return false; + } + + private void injectDeployment(List activatedConditionalDeps) { + final List> futures = new ArrayList<>(topExtensionDeps.size() + + activatedConditionalDeps.size()); + for (ExtensionDependency extDep : topExtensionDeps) { + futures.add(CompletableFuture.supplyAsync(() -> { + var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact)); + if (resolvedDep == null) { + try { + extDep.collectDeploymentDeps(); + return () -> extDep.injectDeploymentNode(null); + } catch (BootstrapDependencyProcessingException e) { + errors.add(e); + } + } else { + // if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath + // in which case we also clear the reloadable flag on it, in case it's coming from the workspace + resolvedDep.clearFlag(DependencyFlags.RELOADABLE); + } + return null; + })); + } + // non-conditional deployment branches should be added before the activated conditional ones to have consistent + // dependency graph structures + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + if (errors.isEmpty() && !activatedConditionalDeps.isEmpty()) { + for (ConditionalDependency cd : activatedConditionalDeps) { + futures.add(CompletableFuture.supplyAsync(() -> { + var resolvedDep = appBuilder.getDependency(getKey(cd.appDep.ext.info.deploymentArtifact)); + if (resolvedDep == null) { + var extDep = cd.getExtensionDependency(); + try { + extDep.collectDeploymentDeps(); + return () -> extDep.injectDeploymentNode(cd.appDep.ext.getParentDeploymentNode()); + } catch (BootstrapDependencyProcessingException e) { + errors.add(e); + } + } else { + // if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath + // in which case we also clear the reloadable flag on it, in case it's coming from the workspace + resolvedDep.clearFlag(DependencyFlags.RELOADABLE); + } + return null; + })); + } + } + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + if (logErrors()) { + throw new RuntimeException( + "Failed to process Quarkus application deployment dependencies, please see the errors logged above for more details."); + } + + for (var future : futures) { + var ext = future.getNow(null); + if (ext != null) { + ext.run(); + } + } + } + + /** + * Resolves and adds compile-only dependencies to the application model with the {@link DependencyFlags#COMPILE_ONLY} flag. + * Compile-only dependencies are resolved as direct dependencies of the root with all the previously resolved dependencies + * enforced as version constraints to make sure compile-only dependencies do not override runtime dependencies of the final + * application. + * + * @param collectRtDepsRequest original runtime dependencies collection request + * @param root the root node of the Quarkus build time dependency tree + * @throws BootstrapMavenException in case of a failure + */ + private void collectCompileOnly(CollectRequest collectRtDepsRequest, DependencyNode root) throws BootstrapMavenException { + if (collectCompileOnly.isEmpty()) { + return; + } + // add all the build time dependencies as version constraints + var depStack = new ArrayDeque>(); + var children = root.getChildren(); + while (children != null) { + for (DependencyNode node : children) { + managedDeps.add(node.getDependency()); + if (!node.getChildren().isEmpty()) { + depStack.add(node.getChildren()); + } + } + children = depStack.poll(); + } + final CollectRequest request = new CollectRequest() + .setDependencies(collectCompileOnly) + .setManagedDependencies(managedDeps) + .setRepositories(collectRtDepsRequest.getRepositories()); + if (collectRtDepsRequest.getRoot() != null) { + request.setRoot(collectRtDepsRequest.getRoot()); + } else { + request.setRootArtifact(collectRtDepsRequest.getRootArtifact()); + } + + try { + root = resolver.getSystem().collectDependencies(resolver.getSession(), request).getRoot(); + } catch (DependencyCollectionException e) { + throw new BootstrapDependencyProcessingException( + "Failed to collect compile-only dependencies of " + root.getArtifact(), e); + } + children = root.getChildren(); + int flags = DependencyFlags.DIRECT | DependencyFlags.COMPILE_ONLY; + while (children != null) { + for (DependencyNode node : children) { + if (hasWinner(node)) { + continue; + } + var extInfo = getExtensionInfoOrNull(node.getArtifact(), node.getRepositories()); + var dep = appBuilder.getDependency(getKey(node.getArtifact())); + if (dep == null) { + dep = newDependencyBuilder(node, resolver).setFlags(flags); + if (extInfo != null) { + dep.setFlags(DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + if (dep.isFlagSet(DependencyFlags.DIRECT)) { + dep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + } + } + appBuilder.addDependency(dep); + } else { + dep.setFlags(DependencyFlags.COMPILE_ONLY); + } + if (!node.getChildren().isEmpty()) { + depStack.add(node.getChildren()); + } + } + flags = DependencyFlags.COMPILE_ONLY; + children = depStack.poll(); + } + } + + private void collectPlatformProperties() throws AppModelResolverException { + final PlatformImportsImpl platformReleases = new PlatformImportsImpl(); + for (Dependency d : managedDeps) { + final Artifact artifact = d.getArtifact(); + final String extension = artifact.getExtension(); + final String artifactId = artifact.getArtifactId(); + if ("json".equals(extension) + && artifactId.endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX)) { + platformReleases.addPlatformDescriptor(artifact.getGroupId(), artifactId, artifact.getClassifier(), extension, + artifact.getVersion()); + } else if ("properties".equals(extension) + && artifactId.endsWith(BootstrapConstants.PLATFORM_PROPERTIES_ARTIFACT_ID_SUFFIX)) { + platformReleases.addPlatformProperties(artifact.getGroupId(), artifactId, artifact.getClassifier(), extension, + artifact.getVersion(), resolver.resolve(artifact).getArtifact().getFile().toPath()); + } + } + appBuilder.setPlatformImports(platformReleases); + } + + private void clearReloadableFlag(ResolvedDependencyBuilder dep) { + final Set deps = artifactDeps.get(dep.getArtifactCoords()); + if (deps == null || deps.isEmpty()) { + return; + } + for (ArtifactKey key : deps) { + final ResolvedDependencyBuilder child = appBuilder.getDependency(key); + if (child == null || child.isFlagSet(DependencyFlags.VISITED)) { + continue; + } + child.setFlags(DependencyFlags.VISITED); + child.clearFlag(DependencyFlags.RELOADABLE); + clearReloadableFlag(child); + } + } + + private DependencyNode normalize(RepositorySystemSession session, DependencyNode root) throws AppModelResolverException { + final DependencyGraphTransformationContext context = new SimpleDependencyGraphTransformationContext(session); + try { + // resolves version conflicts + root = new ConflictIdSorter().transformGraph(root, context); + return session.getDependencyGraphTransformer().transformGraph(root, context); + } catch (RepositoryException e) { + throw new AppModelResolverException("Failed to resolve dependency graph conflicts", e); + } + } + + private DependencyNode resolveRuntimeDeps(CollectRequest request) + throws AppModelResolverException { + boolean verbose = true; //Boolean.getBoolean("quarkus.bootstrap.verbose-model-resolver"); + if (verbose) { + var session = resolver.getSession(); + final DefaultRepositorySystemSession mutableSession = new DefaultRepositorySystemSession(resolver.getSession()); + mutableSession.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true); + mutableSession.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true); + session = mutableSession; + + var ctx = new BootstrapMavenContext(BootstrapMavenContext.config() + .setRepositorySystem(resolver.getSystem()) + .setRepositorySystemSession(session) + .setRemoteRepositories(resolver.getRepositories()) + .setRemoteRepositoryManager(resolver.getRemoteRepositoryManager()) + .setCurrentProject(resolver.getMavenContext().getCurrentProject()) + .setWorkspaceDiscovery(collectReloadableModules)); + resolver = new MavenArtifactResolver(ctx); + } + try { + return resolver.getSystem().collectDependencies(resolver.getSession(), request).getRoot(); + } catch (DependencyCollectionException e) { + final Artifact a = request.getRoot() == null ? request.getRootArtifact() : request.getRoot().getArtifact(); + throw new BootstrapMavenException("Failed to resolve dependencies for " + a, e); + } + } + + private boolean isRuntimeArtifact(ArtifactKey key) { + final ResolvedDependencyBuilder dep = appBuilder.getDependency(key); + return dep != null && dep.isFlagSet(DependencyFlags.RUNTIME_CP); + } + + private void processRuntimeDeps(DependencyNode root) { + final AppDep app = new AppDep(root); + app.walkingFlags = COLLECT_TOP_EXTENSION_RUNTIME_NODES | COLLECT_DIRECT_DEPS; + if (collectReloadableModules) { + app.walkingFlags |= COLLECT_RELOADABLE_MODULES; + } + + var futures = new ArrayList>(); + app.scheduleChildVisits(futures, AppDep::scheduleRuntimeVisit); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + if (logErrors()) { + throw new RuntimeException( + "Failed to process Quarkus application runtime dependencies, please see the errors logged above for more details."); + } + app.setChildFlags(); + } + + private class AppDep { + final AppDep parent; + final DependencyNode node; + ExtensionDependency ext; + byte walkingFlags; + ResolvedDependencyBuilder resolvedDep; + final List children; + + AppDep(DependencyNode node) { + this.parent = null; + this.node = node; + this.children = new ArrayList<>(node.getChildren().size()); + } + + AppDep(AppDep parent, DependencyNode node) { + this.parent = parent; + this.node = node; + this.children = new ArrayList<>(node.getChildren().size()); + } + + void addToModel() { + for (var child : children) { + child.addToModel(); + } + // this node is added after its children to stay compatible with the legacy impl + if (resolvedDep != null) { + appBuilder.addDependency(resolvedDep); + } + } + + void scheduleDeploymentVisit(List> futures) { + futures.add(CompletableFuture.runAsync(() -> { + try { + visitDeploymentDependency(); + } catch (Throwable e) { + errors.add(e); + } + })); + scheduleChildVisits(futures, AppDep::scheduleDeploymentVisit); + } + + void visitDeploymentDependency() { + var dep = appBuilder.getDependency(getKey(node.getArtifact())); + if (dep == null) { + try { + resolvedDep = newDependencyBuilder(node, resolver).setFlags(DependencyFlags.DEPLOYMENT_CP); + } catch (BootstrapMavenException e) { + throw new RuntimeException(e); + } + } + } + + void scheduleRuntimeVisit(List> futures) { + futures.add(CompletableFuture.runAsync(() -> { + try { + visitRuntimeDependency(); + } catch (Throwable t) { + errors.add(t); + } + })); + scheduleChildVisits(futures, AppDep::scheduleRuntimeVisit); + } + + void visitRuntimeDependency() { + Artifact artifact = node.getArtifact(); + final ArtifactKey key = getKey(artifact); + if (resolvedDep == null) { + resolvedDep = appBuilder.getDependency(key); + } + + try { + var ext = getExtensionDependencyOrNull(); + if (resolvedDep == null) { + WorkspaceModule module = null; + if (resolver.getProjectModuleResolver() != null) { + module = resolver.getProjectModuleResolver().getProjectModule(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getVersion()); + } + resolvedDep = DependencyUtils.toAppArtifact(getResolvedArtifact(), module) + .setOptional(node.getDependency().isOptional()) + .setScope(node.getDependency().getScope()) + .setRuntimeCp() + .setDeploymentCp(); + if (JavaScopes.PROVIDED.equals(resolvedDep.getScope())) { + resolvedDep.setFlags(DependencyFlags.COMPILE_ONLY); + } + if (ext != null) { + resolvedDep.setRuntimeExtensionArtifact(); + collectConditionalDependencies(); + } + } + } catch (DeploymentInjectionException e) { + throw e; + } catch (Exception t) { + throw new DeploymentInjectionException("Failed to inject extension deployment dependencies", t); + } + } + + void scheduleChildVisits(List> futures, + BiConsumer>> childVisitor) { + var childNodes = node.getChildren(); + List filtered = null; + var depKeys = artifactDeps.computeIfAbsent(getCoords(node.getArtifact()), key -> new HashSet<>(childNodes.size())); + for (int i = 0; i < childNodes.size(); ++i) { + var childNode = childNodes.get(i); + var winner = getWinner(childNode); + if (winner == null) { + depKeys.add(getKey(childNode.getArtifact())); + var child = new AppDep(this, childNode); + children.add(child); + if (filtered != null) { + filtered.add(childNode); + } + } else { + depKeys.add(getKey(winner.getArtifact())); + if (filtered == null) { + filtered = new ArrayList<>(childNodes.size()); + for (int j = 0; j < i; ++j) { + filtered.add(childNodes.get(j)); + } + } + } + } + if (filtered != null) { + node.setChildren(filtered); + } + for (var child : children) { + childVisitor.accept(child, futures); + } + } + + void setChildFlags() { + for (var c : children) { + c.setFlags(walkingFlags); + } + } + + void setFlags(byte walkingFlags) { + + if (ext != null) { + var parentExtDep = parent; + while (parentExtDep != null) { + if (parentExtDep.ext != null) { + parentExtDep.ext.addExtensionDependency(ext); + break; + } + parentExtDep = parentExtDep.parent; + } + ext.info.ensureActivated(); + } + + if (appBuilder.getDependency(resolvedDep.getKey()) == null) { + appBuilder.addDependency(resolvedDep); + if (ext != null) { + managedDeps.add(new Dependency(ext.info.deploymentArtifact, JavaScopes.COMPILE)); + } + } + this.walkingFlags = walkingFlags; + + resolvedDep.setDirect(isWalkingFlagOn(COLLECT_DIRECT_DEPS)); + if (ext != null && isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { + resolvedDep.setFlags(DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); + topExtensionDeps.add(ext); + } + if (isWalkingFlagOn(COLLECT_RELOADABLE_MODULES)) { + if (resolvedDep.getWorkspaceModule() != null + && !resolvedDep.isFlagSet(DependencyFlags.RUNTIME_EXTENSION_ARTIFACT)) { + resolvedDep.setReloadable(); + } else { + clearWalkingFlag(COLLECT_RELOADABLE_MODULES); + } + } + + clearWalkingFlag(COLLECT_DIRECT_DEPS); + + setChildFlags(); + } + + private ExtensionDependency getExtensionDependencyOrNull() + throws BootstrapDependencyProcessingException { + if (ext != null) { + return ext; + } + ext = ExtensionDependency.get(node); + if (ext == null) { + final ExtensionInfo extInfo = getExtensionInfoOrNull(node.getArtifact(), node.getRepositories()); + if (extInfo != null) { + ext = new ExtensionDependency(extInfo, node, collectExclusions()); + } + } + return ext; + } + + private Collection collectExclusions() { + if (parent == null) { + return List.of(); + } + Collection exclusions = null; + var next = this; + while (next != null) { + if (next.ext != null) { + if (exclusions == null) { + return next.ext.exclusions; + } + exclusions.addAll(next.ext.exclusions); + return exclusions; + } + var nextExcl = next.node.getDependency() == null ? null : next.node.getDependency().getExclusions(); + if (nextExcl != null && !nextExcl.isEmpty()) { + if (exclusions == null) { + exclusions = new ArrayList<>(nextExcl); + } + } + next = next.parent; + } + return exclusions == null ? List.of() : exclusions; + } + + Artifact getResolvedArtifact() { + var result = node.getArtifact(); + if (result.getFile() == null) { + result = resolve(result, node.getRepositories()); + node.setArtifact(result); + } + return result; + } + + private boolean isWalkingFlagOn(byte flag) { + return (walkingFlags & flag) > 0; + } + + private void clearWalkingFlag(byte flag) { + if ((walkingFlags & flag) > 0) { + walkingFlags ^= flag; + } + } + + private void collectConditionalDependencies() + throws BootstrapDependencyProcessingException { + if (ext.info.conditionalDeps.length == 0 || ext.conditionalDepsQueued) { + return; + } + ext.conditionalDepsQueued = true; + + final DependencySelector selector = ext.exclusions == null ? null + : new ExclusionDependencySelector(ext.exclusions); + for (Artifact conditionalArtifact : ext.info.conditionalDeps) { + if (selector != null && !selector.selectDependency(new Dependency(conditionalArtifact, JavaScopes.RUNTIME))) { + continue; + } + final ExtensionInfo conditionalInfo = getExtensionInfoOrNull(conditionalArtifact, + ext.runtimeNode.getRepositories()); + if (conditionalInfo == null) { + log.warn(ext.info.runtimeArtifact + " declares a conditional dependency on " + conditionalArtifact + + " that is not a Quarkus extension and will be ignored"); + continue; + } + if (conditionalInfo.activated) { + continue; + } + final ConditionalDependency conditionalDep = new ConditionalDependency(conditionalInfo, this); + conditionalDepsToProcess.add(conditionalDep); + conditionalDep.appDep.collectConditionalDependencies(); + } + } + } + + private ExtensionInfo getExtensionInfoOrNull(Artifact artifact, List repos) + throws BootstrapDependencyProcessingException { + if (!artifact.getExtension().equals(ArtifactCoords.TYPE_JAR)) { + return null; + } + final ArtifactKey extKey = getKey(artifact); + ExtensionInfo ext = allExtensions.get(extKey); + if (ext != null) { + return ext == EXT_INFO_NONE ? null : ext; + } + artifact = resolve(artifact, repos); + final Path path = artifact.getFile().toPath(); + final Properties descriptor = PathTree.ofDirectoryOrArchive(path).apply(BootstrapConstants.DESCRIPTOR_PATH, visit -> { + if (visit == null) { + return null; + } + try { + return readDescriptor(visit.getPath()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + if (descriptor == null) { + allExtensions.put(extKey, EXT_INFO_NONE); + return null; + } + ext = new ExtensionInfo(artifact, descriptor); + allExtensions.put(extKey, ext); + return ext; + } + + private DependencyNode collectDependencies(Artifact artifact, Collection exclusions, + List repos) { + DependencyNode root; + try { + root = resolver.getSystem() + .collectDependencies(resolver.getSession(), getCollectRequest(artifact, exclusions, repos)) + .getRoot(); + } catch (DependencyCollectionException e) { + throw new DeploymentInjectionException("Failed to collect dependencies for " + artifact, e); + } + if (root.getChildren().size() != 1) { + throw new DeploymentInjectionException("Only one child expected but got " + root.getChildren()); + } + return root.getChildren().get(0); + } + + private CollectRequest getCollectRequest(Artifact artifact, Collection exclusions, + List repos) { + final ArtifactDescriptorResult descr; + try { + descr = resolver.resolveDescriptor(artifact, repos); + } catch (BootstrapMavenException e) { + throw new DeploymentInjectionException("Failed to resolve descriptor for " + artifact, e); + } + final List allConstraints = new ArrayList<>( + managedDeps.size() + descr.getManagedDependencies().size()); + allConstraints.addAll(managedDeps); + allConstraints.addAll(descr.getManagedDependencies()); + return new CollectRequest() + .setManagedDependencies(allConstraints) + .setRepositories(repos) + .setRootArtifact(artifact) + .setDependencies(List.of(new Dependency(artifact, JavaScopes.COMPILE, false, exclusions))); + } + + private Artifact resolve(Artifact artifact, List repos) { + if (artifact.getFile() != null) { + return artifact; + } + try { + return resolver.getSystem().resolveArtifact(resolver.getSession(), + new ArtifactRequest() + .setArtifact(artifact) + .setRepositories(repos)) + .getArtifact(); + } catch (ArtifactResolutionException e) { + throw new DeploymentInjectionException("Failed to resolve artifact " + artifact, e); + } + } + + private static Properties readDescriptor(Path path) throws IOException { + final Properties rtProps = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(path)) { + rtProps.load(reader); + } + return rtProps; + } + + private class ExtensionInfo { + + final Artifact runtimeArtifact; + final Properties props; + final Artifact deploymentArtifact; + final Artifact[] conditionalDeps; + final ArtifactKey[] dependencyCondition; + boolean activated; + + private ExtensionInfo() { + runtimeArtifact = null; + props = null; + deploymentArtifact = null; + conditionalDeps = null; + dependencyCondition = null; + } + + ExtensionInfo(Artifact runtimeArtifact, Properties props) throws BootstrapDependencyProcessingException { + this.runtimeArtifact = runtimeArtifact; + this.props = props; + + String value = props.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + if (value == null) { + throw new BootstrapDependencyProcessingException("Extension descriptor from " + runtimeArtifact + + " does not include " + BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + } + Artifact deploymentArtifact = toArtifact(value); + if (deploymentArtifact.getVersion() == null || deploymentArtifact.getVersion().isEmpty()) { + deploymentArtifact = deploymentArtifact.setVersion(runtimeArtifact.getVersion()); + } + this.deploymentArtifact = deploymentArtifact; + + value = props.getProperty(BootstrapConstants.CONDITIONAL_DEPENDENCIES); + if (value != null) { + final String[] deps = BootstrapUtils.splitByWhitespace(value); + conditionalDeps = new Artifact[deps.length]; + for (int i = 0; i < deps.length; ++i) { + try { + conditionalDeps[i] = toArtifact(deps[i]); + } catch (Exception e) { + throw new BootstrapDependencyProcessingException( + "Failed to parse conditional dependencies configuration of " + runtimeArtifact, e); + } + } + } else { + conditionalDeps = NO_ARTIFACTS; + } + + dependencyCondition = BootstrapUtils + .parseDependencyCondition(props.getProperty(BootstrapConstants.DEPENDENCY_CONDITION)); + } + + void ensureActivated() { + if (activated) { + return; + } + activated = true; + appBuilder.handleExtensionProperties(props, runtimeArtifact.toString()); + + final String providesCapabilities = props.getProperty(BootstrapConstants.PROP_PROVIDES_CAPABILITIES); + final String requiresCapabilities = props.getProperty(BootstrapConstants.PROP_REQUIRES_CAPABILITIES); + if (providesCapabilities != null || requiresCapabilities != null) { + appBuilder.addExtensionCapabilities( + CapabilityContract.of(toCompactCoords(runtimeArtifact), providesCapabilities, requiresCapabilities)); + } + } + } + + private class ExtensionDependency { + + static ExtensionDependency get(DependencyNode node) { + return (ExtensionDependency) node.getData().get(QUARKUS_EXTENSION_DEPENDENCY); + } + + final ExtensionInfo info; + final DependencyNode runtimeNode; + final Collection exclusions; + boolean conditionalDepsQueued; + private List extDeps; + private DependencyNode deploymentNode; + private DependencyNode parentNode; + + 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"); + } + } + + DependencyNode getParentDeploymentNode() { + if (parentNode == null) { + return null; + } + var ext = ExtensionDependency.get(parentNode); + if (ext == null) { + return null; + } + return ext.deploymentNode == null ? ext.parentNode : ext.deploymentNode; + } + + void addExtensionDependency(ExtensionDependency dep) { + if (extDeps == null) { + extDeps = new ArrayList<>(); + } + extDeps.add(dep); + } + + private void collectDeploymentDeps() + throws BootstrapDependencyProcessingException { + log.debugf("Collecting dependencies of %s", info.deploymentArtifact); + deploymentNode = collectDependencies(info.deploymentArtifact, exclusions, runtimeNode.getRepositories()); + if (deploymentNode.getChildren().isEmpty()) { + throw new BootstrapDependencyProcessingException( + "Failed to collect dependencies of " + deploymentNode.getArtifact() + + ": either its POM could not be resolved from the available Maven repositories " + + "or the artifact does not have any dependencies while at least a dependency on the runtime artifact " + + info.runtimeArtifact + " is expected"); + } + if (!replaceDirectDepBranch(deploymentNode, true)) { + throw new BootstrapDependencyProcessingException( + "Quarkus extension deployment artifact " + deploymentNode.getArtifact() + + " does not appear to depend on the corresponding runtime artifact " + + info.runtimeArtifact); + } + } + + private void injectDeploymentNode(DependencyNode parentDeploymentNode) { + if (parentDeploymentNode == null) { + runtimeNode.setData(QUARKUS_RUNTIME_ARTIFACT, runtimeNode.getArtifact()); + runtimeNode.setArtifact(deploymentNode.getArtifact()); + runtimeNode.setChildren(deploymentNode.getChildren()); + } else { + parentDeploymentNode.getChildren().add(deploymentNode); + } + } + + private boolean replaceDirectDepBranch(DependencyNode parentNode, boolean replaceRuntimeNode) { + int i = 0; + DependencyNode inserted = null; + var childNodes = parentNode.getChildren(); + while (i < childNodes.size()) { + var node = childNodes.get(i); + final Artifact a = node.getArtifact(); + if (a != null && !hasWinner(node) && isSameKey(info.runtimeArtifact, a)) { + // we are not comparing the version in the above condition because the runtime version + // may appear to be different from the deployment one and that's ok + // e.g. the version of the runtime artifact could be managed by a BOM + // but overridden by the user in the project config. The way the deployment deps + // are resolved here, the deployment version of the runtime artifact will be the one from the BOM. + if (replaceRuntimeNode) { + inserted = new DefaultDependencyNode(runtimeNode); + inserted.setChildren(runtimeNode.getChildren()); + childNodes.set(i, inserted); + } else { + inserted = runtimeNode; + } + if (this.deploymentNode == null && this.parentNode == null) { + this.parentNode = parentNode; + } + break; + } + ++i; + } + if (inserted == null) { + return false; + } + + if (extDeps != null) { + var depQueue = new ArrayList<>(childNodes); + var exts = new ArrayList<>(extDeps); + for (int j = 0; j < depQueue.size(); ++j) { + var depNode = depQueue.get(j); + if (hasWinner(depNode)) { + continue; + } + for (int k = 0; k < exts.size(); ++k) { + if (exts.get(k).replaceDirectDepBranch(depNode, replaceRuntimeNode && depNode != inserted)) { + exts.remove(k); + break; + } + } + if (exts.isEmpty()) { + break; + } + depQueue.addAll(depNode.getChildren()); + } + } + + return true; + } + } + + private class ConditionalDependency { + + final AppDep appDep; + private boolean activated; + + private ConditionalDependency(ExtensionInfo info, AppDep parent) { + final DefaultDependencyNode rtNode = new DefaultDependencyNode( + new Dependency(info.runtimeArtifact, JavaScopes.COMPILE)); + rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())); + rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( + new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); + rtNode.setRepositories(parent.ext.runtimeNode.getRepositories()); + + appDep = new AppDep(parent, rtNode); + appDep.ext = new ExtensionDependency(info, rtNode, parent.ext.exclusions); + } + + ExtensionDependency getExtensionDependency() { + return appDep.ext; + } + + void activate() { + if (activated) { + return; + } + activated = true; + final ExtensionDependency extDep = getExtensionDependency(); + final DependencyNode originalNode = collectDependencies(appDep.ext.info.runtimeArtifact, extDep.exclusions, + extDep.runtimeNode.getRepositories()); + final DefaultDependencyNode rtNode = (DefaultDependencyNode) extDep.runtimeNode; + rtNode.setRepositories(originalNode.getRepositories()); + // if this node has conditional dependencies on its own, they may have been activated by this time + // in which case they would be included into its children + List currentChildren = rtNode.getChildren(); + if (currentChildren == null || currentChildren.isEmpty()) { + rtNode.setChildren(originalNode.getChildren()); + } else { + currentChildren.addAll(originalNode.getChildren()); + } + + appDep.walkingFlags = COLLECT_DIRECT_DEPS; + if (collectReloadableModules) { + appDep.walkingFlags |= COLLECT_RELOADABLE_MODULES; + } + var futures = new ArrayList>(); + appDep.scheduleRuntimeVisit(futures); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + if (logErrors()) { + throw new RuntimeException( + "Failed to process Quarkus application conditional dependencies, please see the errors logged above for more details."); + } + + appDep.setFlags(appDep.walkingFlags); + + var parentExtDep = appDep.parent; + parentExtDep.children.add(appDep); + while (parentExtDep != null) { + if (parentExtDep.ext != null) { + parentExtDep.ext.addExtensionDependency(appDep.ext); + break; + } + parentExtDep = parentExtDep.parent; + } + appDep.ext.info.ensureActivated(); + + appDep.parent.ext.runtimeNode.getChildren().add(rtNode); + } + + boolean isSatisfied() { + if (appDep.ext.info.dependencyCondition == null) { + return true; + } + for (ArtifactKey key : appDep.ext.info.dependencyCondition) { + if (!isRuntimeArtifact(key)) { + return false; + } + } + return true; + } + } + + 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 String toCompactCoords(Artifact a) { + final StringBuilder b = new StringBuilder(); + b.append(a.getGroupId()).append(':').append(a.getArtifactId()).append(':'); + if (!a.getClassifier().isEmpty()) { + b.append(a.getClassifier()).append(':'); + } + if (!ArtifactCoords.TYPE_JAR.equals(a.getExtension())) { + b.append(a.getExtension()).append(':'); + } + b.append(a.getVersion()); + return b.toString(); + } + + private class AppDepLogger { + + final List depth = new ArrayList<>(); + + private AppDepLogger() { + } + + void log(AppDep root) { + logInternal(root); + + final int childrenTotal = root.children.size(); + if (childrenTotal > 0) { + if (childrenTotal == 1) { + depth.add(false); + log(root.children.get(0)); + } else { + depth.add(true); + int i = 0; + while (i < childrenTotal) { + log(root.children.get(i++)); + if (i == childrenTotal - 1) { + depth.set(depth.size() - 1, false); + } + } + } + depth.remove(depth.size() - 1); + } + } + + private void logInternal(AppDep dep) { + var buf = new StringBuilder(); + 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(dep.node.getArtifact()); + if (!depth.isEmpty()) { + appendFlags(buf, getResolvedDependency(getKey(dep.node.getArtifact()))); + } + depLogging.getMessageConsumer().accept(buf.toString()); + + if (depLogging.isGraph()) { + var depKeys = artifactDeps.get(getCoords(dep.node.getArtifact())); + if (depKeys != null && !depKeys.isEmpty() && depKeys.size() != dep.children.size()) { + final Map versions = new HashMap<>(dep.children.size()); + for (var c : dep.children) { + versions.put(getKey(c.node.getArtifact()), c.node.getArtifact().getVersion()); + } + var list = new ArrayList(depKeys.size() - dep.children.size()); + for (var key : depKeys) { + if (!versions.containsKey(key)) { + var d = getResolvedDependency(key); + var sb = new StringBuilder().append(d.toGACTVString()); + appendFlags(sb, d); + list.add(sb.append(" [+]").toString()); + } + } + Collections.sort(list); + for (int j = 0; j < list.size(); ++j) { + buf = new StringBuilder(); + 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('\u2502').append(" "); + } else { + buf.append(" "); + } + } + + if (j < list.size() - 1) { + //buf.append("|- "); + buf.append('\u251c').append('\u2500').append(' '); + } else if (dep.children.isEmpty()) { + //buf.append("\\- "); + buf.append('\u2514').append('\u2500').append(' '); + } else { + //buf.append("|- "); + buf.append('\u251c').append('\u2500').append(' '); + } + buf.append(list.get(j)); + depLogging.getMessageConsumer().accept(buf.toString()); + } + } + } + } + + private void appendFlags(StringBuilder sb, ResolvedDependencyBuilder d) { + sb.append(" (").append(d.getScope()); + if (d.isFlagSet(DependencyFlags.OPTIONAL)) { + sb.append(" optional"); + } + if (depLogging.isVerbose()) { + if (d.isFlagSet(DependencyFlags.RUNTIME_CP)) { + sb.append(", runtime classpath"); + } else { + sb.append(", build-time classpath"); + } + if (d.isFlagSet(DependencyFlags.RUNTIME_EXTENSION_ARTIFACT)) { + sb.append(", extension"); + } + if (d.isFlagSet(DependencyFlags.RELOADABLE)) { + sb.append(", reloadable"); + } + } + sb.append(')'); + } + + private ResolvedDependencyBuilder getResolvedDependency(ArtifactKey key) { + var d = appBuilder.getDependency(key); + if (d == null) { + throw new IllegalArgumentException(key + " is not found among application dependencies"); + } + return d; + } + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java index 7a3fd7574fb5d..006cd4923d840 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/ApplicationDependencyTreeResolver.java @@ -202,7 +202,6 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver } } - // resolve and inject deployment dependency branches for the top (first met) runtime extension nodes for (ExtensionDependency extDep : topExtensionDeps) { injectDeploymentDependencies(extDep); } @@ -868,7 +867,7 @@ private ConditionalDependency(ExtensionInfo info, ExtensionDependency dependent) ExtensionDependency getExtensionDependency() { if (dependency == null) { final DefaultDependencyNode rtNode = new DefaultDependencyNode( - new Dependency(info.runtimeArtifact, JavaScopes.RUNTIME)); + new Dependency(info.runtimeArtifact, JavaScopes.COMPILE)); rtNode.setVersion(new BootstrapArtifactVersion(info.runtimeArtifact.getVersion())); rtNode.setVersionConstraint(new BootstrapArtifactVersionConstraint( new BootstrapArtifactVersion(info.runtimeArtifact.getVersion()))); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelResolver.java index 14d4f5ef80f2b..c489b9c958b6a 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BootstrapModelResolver.java @@ -57,13 +57,7 @@ public List resolveArtifacts(RepositorySystemSession session, Collection requests) throws ArtifactResolutionException { return repoSystem.resolveArtifacts(session, requests); } - }, new VersionRangeResolver() { - @Override - public VersionRangeResult resolveVersionRange(RepositorySystemSession session, - VersionRangeRequest request) throws VersionRangeResolutionException { - return repoSystem.resolveVersionRange(session, request); - } - }, ctx.getRemoteRepositoryManager(), ctx.getRemoteRepositories()); + }, repoSystem::resolveVersionRange, ctx.getRemoteRepositoryManager(), ctx.getRemoteRepositories()); } private final RepositorySystemSession session; diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DependencyLoggingConfig.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DependencyLoggingConfig.java new file mode 100644 index 0000000000000..d9cb55946daac --- /dev/null +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DependencyLoggingConfig.java @@ -0,0 +1,65 @@ +package io.quarkus.bootstrap.resolver.maven; + +import java.util.function.Consumer; + +public class DependencyLoggingConfig { + + public static Builder builder() { + return new DependencyLoggingConfig().new Builder(); + } + + public class Builder { + + private boolean built; + + private Builder() { + } + + public Builder setGraph(boolean graph) { + if (!built) { + DependencyLoggingConfig.this.graph = graph; + } + return this; + } + + public Builder setVerbose(boolean verbose) { + if (!built) { + DependencyLoggingConfig.this.verbose = verbose; + } + return this; + } + + public Builder setMessageConsumer(Consumer msgConsumer) { + if (!built) { + DependencyLoggingConfig.this.msgConsumer = msgConsumer; + } + return this; + } + + public DependencyLoggingConfig build() { + if (!built) { + built = true; + if (msgConsumer == null) { + throw new IllegalArgumentException("msgConsumer has not been initialized"); + } + } + return DependencyLoggingConfig.this; + } + } + + private boolean verbose; + private boolean graph; + private Consumer msgConsumer; + + public boolean isGraph() { + return graph; + } + + public boolean isVerbose() { + return verbose; + } + + public Consumer getMessageConsumer() { + return msgConsumer; + } +} diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java index 66998179e9e7c..cc5f42ddec40a 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/util/DependencyUtils.java @@ -10,6 +10,7 @@ import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.graph.transformer.ConflictResolver; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; @@ -152,4 +153,13 @@ public static ResolvedDependencyBuilder toAppArtifact(Artifact artifact, Workspa .setVersion(artifact.getVersion()) .setResolvedPaths(artifact.getFile() == null ? PathList.empty() : PathList.of(artifact.getFile().toPath())); } + + public static boolean hasWinner(DependencyNode node) { + return node.getData().containsKey(ConflictResolver.NODE_DATA_WINNER) && node.getChildren().isEmpty(); + } + + public static DependencyNode getWinner(DependencyNode node) { + final DependencyNode winner = (DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER); + return winner == null || !node.getChildren().isEmpty() ? null : winner; + } }