From 227776a2a5662f848c8c4c0691ee686c66437f44 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Thu, 24 Oct 2024 18:25:36 +0200 Subject: [PATCH] An option to resolve only the runtime part of the ApplicationModel --- .../io/quarkus/maven/DependencySbomMojo.java | 9 +- .../io/quarkus/maven/DependencyTreeMojo.java | 9 +- ...onalDependencyTreeMojoRuntimeOnlyTest.java | 53 ++++++++++++ .../maven/DependencyTreeMojoTestBase.java | 11 ++- .../app-with-conditional-deps-1.jar.prod.rt | 9 ++ .../RuntimeOnlyApplicationModelTestCase.java | 82 ++++++++++++++++++ .../resolver/BootstrapAppModelResolver.java | 8 ++ .../ApplicationDependencyTreeResolver.java | 26 ++++-- .../IncubatingApplicationModelResolver.java | 84 ++++++++++--------- 9 files changed, 242 insertions(+), 49 deletions(-) create mode 100644 devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoRuntimeOnlyTest.java create mode 100644 devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod.rt create mode 100644 independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RuntimeOnlyApplicationModelTestCase.java diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DependencySbomMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DependencySbomMojo.java index 008d372d81ce5..eec9de1fabb90 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DependencySbomMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DependencySbomMojo.java @@ -83,6 +83,12 @@ public class DependencySbomMojo extends AbstractMojo { @Parameter(property = "quarkus.dependency.sbom.schema-version") String schemaVersion; + /** + * Whether to limit application dependencies to only those that are included in the runtime + */ + @Parameter(property = "quarkus.dependency.sbom.runtime-only") + boolean runtimeOnly; + protected MavenArtifactResolver resolver; @Override @@ -111,7 +117,8 @@ private ApplicationModel resolveApplicationModel() project.getVersion()); final BootstrapAppModelResolver modelResolver; try { - modelResolver = new BootstrapAppModelResolver(getResolver()); + modelResolver = new BootstrapAppModelResolver(getResolver()) + .setRuntimeModelOnly(runtimeOnly); if (mode != null) { if (mode.equalsIgnoreCase("test")) { modelResolver.setTest(true); 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 461e619c65fd4..7de487b87c0de 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DependencyTreeMojo.java @@ -85,6 +85,12 @@ public class DependencyTreeMojo extends AbstractMojo { @Parameter(property = "appendOutput", required = false, defaultValue = "false") boolean appendOutput; + /** + * Whether to log only the runtime dependencies of the Quarkus application + */ + @Parameter(property = "runtimeOnly") + boolean runtimeOnly; + protected MavenArtifactResolver resolver; @Override @@ -134,7 +140,8 @@ private void logTree(final Consumer log) throws MojoExecutionException { project.getVersion()); final BootstrapAppModelResolver modelResolver; try { - modelResolver = new BootstrapAppModelResolver(resolver()); + modelResolver = new BootstrapAppModelResolver(resolver()) + .setRuntimeModelOnly(runtimeOnly); if (mode != null) { if (mode.equalsIgnoreCase("test")) { modelResolver.setTest(true); diff --git a/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoRuntimeOnlyTest.java b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoRuntimeOnlyTest.java new file mode 100644 index 0000000000000..196e12c015826 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/ConditionalDependencyTreeMojoRuntimeOnlyTest.java @@ -0,0 +1,53 @@ +package io.quarkus.maven; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class ConditionalDependencyTreeMojoRuntimeOnlyTest extends DependencyTreeMojoTestBase { + @Override + protected String mode() { + return "prod"; + } + + @Override + protected boolean isIncubatingModelResolver() { + return true; + } + + @Override + protected boolean isRuntimeOnly() { + 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 db3a359c1d6d3..40acadbe762e2 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/DependencyTreeMojoTestBase.java @@ -65,6 +65,10 @@ protected boolean isIncubatingModelResolver() { return false; } + protected boolean isRuntimeOnly() { + return false; + } + @Test public void test() throws Exception { @@ -81,6 +85,7 @@ public void test() throws Exception { mojo.resolver = mvnResolver; mojo.mode = mode(); mojo.graph = isGraph(); + mojo.runtimeOnly = isRuntimeOnly(); final Path mojoLog = workDir.resolve(getClass().getName() + ".log"); final PrintStream defaultOut = System.out; @@ -92,8 +97,12 @@ public void test() throws Exception { System.setOut(defaultOut); } + String expectedFileName = app.getArtifactFileName() + "." + mode(); + if (isRuntimeOnly()) { + expectedFileName += ".rt"; + } assertThat(mojoLog).hasSameTextualContentAs( Path.of("").normalize().toAbsolutePath() - .resolve("target").resolve("test-classes").resolve(app.getArtifactFileName() + "." + mode())); + .resolve("target").resolve("test-classes").resolve(expectedFileName)); } } diff --git a/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod.rt b/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod.rt new file mode 100644 index 0000000000000..1805c3d2d7876 --- /dev/null +++ b/devtools/maven/src/test/resources/app-with-conditional-deps-1.jar.prod.rt @@ -0,0 +1,9 @@ +[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:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:test-core-ext:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:quarkus-mozzarella:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:quarkus-basil:1 (compile) +[info] ├─ io.quarkus.bootstrap.test:quarkus-salad:1 (compile) +[info] │ └─ io.quarkus.bootstrap.test:quarkus-caprese:1 (compile) +[info] └─ io.quarkus.bootstrap.test:quarkus-oil:1 (compile) \ No newline at end of file diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RuntimeOnlyApplicationModelTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RuntimeOnlyApplicationModelTestCase.java new file mode 100644 index 0000000000000..f0ddf15c9ede3 --- /dev/null +++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/RuntimeOnlyApplicationModelTestCase.java @@ -0,0 +1,82 @@ +package io.quarkus.bootstrap.resolver.test; + +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.CollectDependenciesBase; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.maven.dependency.DependencyFlags; + +public class RuntimeOnlyApplicationModelTestCase extends CollectDependenciesBase { + + private static final boolean runtimeOnly = true; + + @Override + protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception { + var resolver = super.newAppModelResolver(currentProject); + resolver.setIncubatingModelResolver(false); + resolver.setRuntimeModelOnly(runtimeOnly); + return resolver; + } + + @Override + protected void setupDependencies() { + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + install(extA, false); + if (!runtimeOnly) { + addCollectedDeploymentDep(extA.getDeployment()); + } + + installAsDep(extA.getRuntime(), + DependencyFlags.DIRECT + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT + | DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + install(extB, false); + + final TsQuarkusExt extC = new TsQuarkusExt("ext-c"); + extC.setDependencyCondition(extB); + install(extC, false); + + final TsQuarkusExt extD = new TsQuarkusExt("ext-d"); + install(extD, false); + installAsDep(extD.getRuntime(), + DependencyFlags.DIRECT + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT + | DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + if (!runtimeOnly) { + addCollectedDeploymentDep(extD.getDeployment()); + } + + final TsArtifact libE = TsArtifact.jar("lib-e"); + install(libE, true); + final TsArtifact libEBuildTIme = TsArtifact.jar("lib-e-build-time"); + install(libEBuildTIme); + if (!runtimeOnly) { + addCollectedDeploymentDep(libEBuildTIme); + } + + final TsQuarkusExt extE = new TsQuarkusExt("ext-e"); + extE.setDependencyCondition(extD); + extE.getRuntime().addDependency(libE); + extE.getDeployment().addDependency(libEBuildTIme); + install(extE, false); + addCollectedDep(extE.getRuntime(), DependencyFlags.RUNTIME_EXTENSION_ARTIFACT); + if (!runtimeOnly) { + addCollectedDeploymentDep(extE.getDeployment()); + } + + final TsQuarkusExt extF = new TsQuarkusExt("ext-f"); + extF.setConditionalDeps(extC, extE); + install(extF, false); + installAsDep(extF.getRuntime(), + DependencyFlags.DIRECT + | DependencyFlags.RUNTIME_EXTENSION_ARTIFACT + | DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT); + if (!runtimeOnly) { + addCollectedDeploymentDep(extF.getDeployment()); + } + } +} 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 6893d1602abe5..d0f1b3a3139f9 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 @@ -62,6 +62,7 @@ public class BootstrapAppModelResolver implements AppModelResolver { protected boolean test; private boolean collectReloadableDeps = true; private boolean incubatingModelResolver; + private boolean runtimeModelOnly; public BootstrapAppModelResolver(MavenArtifactResolver mvn) { this.mvn = mvn; @@ -110,6 +111,11 @@ public BootstrapAppModelResolver setCollectReloadableDependencies(boolean collec return this; } + public BootstrapAppModelResolver setRuntimeModelOnly(boolean runtimeModelOnly) { + this.runtimeModelOnly = runtimeModelOnly; + return this; + } + public void addRemoteRepositories(List repos) { mvn.addRemoteRepositories(repos); } @@ -371,6 +377,7 @@ private ApplicationModel buildAppModel(ResolvedDependencyBuilder appArtifact, .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) .setCollectCompileOnly(filteredProvidedDeps) .setDependencyLogging(depLogConfig) + .setRuntimeModelOnly(runtimeModelOnly) .resolve(collectRtDepsRequest); } else { ApplicationDependencyTreeResolver.newInstance() @@ -379,6 +386,7 @@ private ApplicationModel buildAppModel(ResolvedDependencyBuilder appArtifact, .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) .setCollectCompileOnly(filteredProvidedDeps) .setBuildTreeConsumer(depLogConfig == null ? null : depLogConfig.getMessageConsumer()) + .setRuntimeModelOnly(runtimeModelOnly) .resolve(collectRtDepsRequest); } if (logTime) { 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 3ffc9033746a7..a5d2ce58cde78 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 @@ -106,6 +106,7 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { private boolean collectReloadableModules; private Consumer buildTreeConsumer; private List collectCompileOnly; + private boolean runtimeModelOnly; public ApplicationDependencyTreeResolver setArtifactResolver(MavenArtifactResolver resolver) { this.resolver = resolver; @@ -139,6 +140,11 @@ public ApplicationDependencyTreeResolver setCollectCompileOnly(List return this; } + public ApplicationDependencyTreeResolver setRuntimeModelOnly(boolean runtimeModelOnly) { + this.runtimeModelOnly = runtimeModelOnly; + return this; + } + public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException { this.managedDeps = collectRtDepsRequest.getManagedDependencies(); @@ -202,13 +208,15 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver } } - for (ExtensionDependency extDep : topExtensionDeps) { - injectDeploymentDependencies(extDep); - } + if (!runtimeModelOnly) { + for (ExtensionDependency extDep : topExtensionDeps) { + injectDeploymentDependencies(extDep); + } - if (!activatedConditionalDeps.isEmpty()) { - for (ConditionalDependency cd : activatedConditionalDeps) { - injectDeploymentDependencies(cd.getExtensionDependency()); + if (!activatedConditionalDeps.isEmpty()) { + for (ConditionalDependency cd : activatedConditionalDeps) { + injectDeploymentDependencies(cd.getExtensionDependency()); + } } } @@ -231,7 +239,9 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver } collectPlatformProperties(); - collectCompileOnly(collectRtDepsRequest, root); + if (!runtimeModelOnly) { + collectCompileOnly(collectRtDepsRequest, root); + } } /** @@ -886,7 +896,7 @@ void activate() { return; } activated = true; - clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES); + clearWalkingFlag((byte) (COLLECT_DIRECT_DEPS | COLLECT_TOP_EXTENSION_RUNTIME_NODES)); final ExtensionDependency extDep = getExtensionDependency(); final DependencyNode originalNode = collectDependencies(info.runtimeArtifact, extDep.exclusions, extDep.runtimeNode.getRepositories()); diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java index b50939b6727b7..b569ce9c5d500 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/IncubatingApplicationModelResolver.java @@ -131,7 +131,7 @@ public static IncubatingApplicationModelResolver newInstance() { private final List topExtensionDeps = new ArrayList<>(); private final Map allExtensions = new ConcurrentHashMap<>(); - private List conditionalDepsToProcess = new ArrayList<>(); + private Collection conditionalDepsToProcess = new ConcurrentLinkedDeque<>(); private MavenArtifactResolver resolver; private List managedDeps; @@ -139,6 +139,7 @@ public static IncubatingApplicationModelResolver newInstance() { private boolean collectReloadableModules; private DependencyLoggingConfig depLogging; private List collectCompileOnly; + private boolean runtimeModelOnly; public IncubatingApplicationModelResolver setArtifactResolver(MavenArtifactResolver resolver) { this.resolver = resolver; @@ -172,6 +173,11 @@ public IncubatingApplicationModelResolver setCollectCompileOnly(List return this; } + public IncubatingApplicationModelResolver setRuntimeModelOnly(boolean runtimeModelOnly) { + this.runtimeModelOnly = runtimeModelOnly; + 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 @@ -183,10 +189,13 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver final List activatedConditionalDeps = activateConditionalDeps(); // resolve and inject deployment dependency branches for the top (first met) runtime extension nodes - injectDeployment(activatedConditionalDeps); + if (!runtimeModelOnly) { + injectDeployment(activatedConditionalDeps); + } root = normalize(resolver.getSession(), root); - processDeploymentDeps(root); + populateModelBuilder(root); + // clear the reloadable flags for (var d : appBuilder.getDependencies()) { if (!d.isFlagSet(DependencyFlags.RELOADABLE) && !d.isFlagSet(DependencyFlags.VISITED)) { clearReloadableFlag(d); @@ -198,10 +207,14 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver if (d.isFlagSet(DependencyFlags.RELOADABLE)) { appBuilder.addReloadableWorkspaceModule(d.getKey()); } - appBuilder.addDependency(d); + if (!runtimeModelOnly) { + d.setFlags(DependencyFlags.DEPLOYMENT_CP); + } } - collectCompileOnly(collectRtDepsRequest, root); + if (!runtimeModelOnly) { + collectCompileOnly(collectRtDepsRequest, root); + } } private List activateConditionalDeps() { @@ -213,7 +226,7 @@ private List activateConditionalDeps() { while (!conditionalDepsToProcess.isEmpty() && checkDependencyConditions) { checkDependencyConditions = false; var unsatisfiedConditionalDeps = conditionalDepsToProcess; - conditionalDepsToProcess = new ArrayList<>(); + conditionalDepsToProcess = new ConcurrentLinkedDeque<>(); for (ConditionalDependency cd : unsatisfiedConditionalDeps) { if (cd.isSatisfied()) { cd.activate(); @@ -228,7 +241,7 @@ private List activateConditionalDeps() { return activatedConditionalDeps; } - private void processDeploymentDeps(DependencyNode root) { + private void populateModelBuilder(DependencyNode root) { var app = new AppDep(root); final ModelResolutionTaskRunner taskRunner = new ModelResolutionTaskRunner(); app.scheduleChildVisits(taskRunner, AppDep::scheduleDeploymentVisit); @@ -237,7 +250,6 @@ private void processDeploymentDeps(DependencyNode root) { for (var d : app.children) { d.addToModel(); } - if (depLogging != null) { new AppDepLogger().log(app); } @@ -245,6 +257,8 @@ private void processDeploymentDeps(DependencyNode root) { private void injectDeployment(List activatedConditionalDeps) { final ConcurrentLinkedDeque injectQueue = new ConcurrentLinkedDeque<>(); + // non-conditional deployment branches should be added before the activated conditional ones to have consistent + // dependency graph structures collectDeploymentDeps(injectQueue); if (!activatedConditionalDeps.isEmpty()) { collectConditionalDeploymentDeps(activatedConditionalDeps, injectQueue); @@ -258,18 +272,7 @@ private void collectConditionalDeploymentDeps(List activa ConcurrentLinkedDeque injectQueue) { var taskRunner = new ModelResolutionTaskRunner(); for (ConditionalDependency cd : activatedConditionalDeps) { - taskRunner.run(() -> { - var resolvedDep = appBuilder.getDependency(getKey(cd.conditionalDep.ext.info.deploymentArtifact)); - if (resolvedDep == null) { - var extDep = cd.getExtensionDependency(); - extDep.collectDeploymentDeps(); - injectQueue.add(() -> extDep.injectDeploymentNode(cd.conditionalDep.ext.getParentDeploymentNode())); - } 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); - } - }); + injectDeploymentDep(taskRunner, cd.getExtensionDependency(), injectQueue, true); } taskRunner.waitForCompletion(); } @@ -277,23 +280,26 @@ private void collectConditionalDeploymentDeps(List activa private void collectDeploymentDeps(ConcurrentLinkedDeque injectQueue) { var taskRunner = new ModelResolutionTaskRunner(); for (ExtensionDependency extDep : topExtensionDeps) { - taskRunner.run(() -> { - var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact)); - if (resolvedDep == null) { - extDep.collectDeploymentDeps(); - injectQueue.add(() -> extDep.injectDeploymentNode(null)); - } 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); - } - }); + injectDeploymentDep(taskRunner, extDep, injectQueue, false); } - // non-conditional deployment branches should be added before the activated conditional ones to have consistent - // dependency graph structures taskRunner.waitForCompletion(); } + private void injectDeploymentDep(ModelResolutionTaskRunner taskRunner, ExtensionDependency extDep, + ConcurrentLinkedDeque injectQueue, boolean conditionalDep) { + taskRunner.run(() -> { + var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact)); + if (resolvedDep == null) { + extDep.collectDeploymentDeps(); + injectQueue.add(() -> extDep.injectDeploymentNode(conditionalDep ? extDep.getParentDeploymentNode() : null)); + } 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); + } + }); + } + /** * 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 @@ -452,11 +458,15 @@ private void processRuntimeDeps(DependencyNode root) { appRoot.walkingFlags |= COLLECT_RELOADABLE_MODULES; } + visitRuntimeDeps(appRoot); + appBuilder.getApplicationArtifact().addDependencies(appRoot.allDeps); + appRoot.setChildFlags(); + } + + private void visitRuntimeDeps(AppDep appRoot) { final ModelResolutionTaskRunner taskRunner = new ModelResolutionTaskRunner(); appRoot.scheduleChildVisits(taskRunner, AppDep::scheduleRuntimeVisit); taskRunner.waitForCompletion(); - appBuilder.getApplicationArtifact().addDependencies(appRoot.allDeps); - appRoot.setChildFlags(); } private class AppDep { @@ -501,8 +511,7 @@ void scheduleDeploymentVisit(ModelResolutionTaskRunner taskRunner) { void visitDeploymentDependency() { if (!appBuilder.hasDependency(getKey(node.getArtifact()))) { try { - resolvedDep = newDependencyBuilder(node, resolver) - .setFlags(DependencyFlags.DEPLOYMENT_CP); + resolvedDep = newDependencyBuilder(node, resolver); } catch (BootstrapMavenException e) { throw new RuntimeException(e); } @@ -1053,7 +1062,6 @@ void activate() { } else { currentChildren.addAll(originalNode.getChildren()); } - conditionalDep.walkingFlags = COLLECT_DIRECT_DEPS; if (collectReloadableModules) { conditionalDep.walkingFlags |= COLLECT_RELOADABLE_MODULES; }