diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/PackageAppTestBase.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/PackageAppTestBase.java index 497dbb331d6ed0..3e99369b1c29dd 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/PackageAppTestBase.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/PackageAppTestBase.java @@ -13,7 +13,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -115,7 +114,17 @@ public Path getPath(Path workDir) throws IOException { public static Collection getDeploymentOnlyDeps(ApplicationModel model) { return model.getDependencies().stream().filter(d -> d.isDeploymentCp() && !d.isRuntimeCp()) - .map(d -> new ArtifactDependency(d)).collect(Collectors.toSet()); + .map(ArtifactDependency::new).collect(Collectors.toSet()); + } + + public static Collection getDependenciesWithFlag(ApplicationModel model, int flag) { + var set = new HashSet(); + for (var d : model.getDependencies(flag)) { + if (d.isFlagSet(flag)) { + set.add(new ArtifactDependency(d)); + } + } + return set; } @Override @@ -159,7 +168,7 @@ protected void testBootstrap(QuarkusBootstrap creator) throws Exception { } } - List missingEntries = Collections.emptyList(); + List missingEntries = List.of(); for (String entry : expectedLib) { if (!actualMainLib.remove(entry)) { if (missingEntries.isEmpty()) { diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java index 4c7e59652eda58..ba2653328ed7e4 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTest.java @@ -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.TsDependency; import io.quarkus.bootstrap.resolver.TsQuarkusExt; +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 ProvidedExtensionDepsTest extends BootstrapFromOriginalJarTestBase { @@ -51,11 +53,24 @@ protected TsArtifact composeApplication() { @Override protected void assertAppModel(ApplicationModel model) throws Exception { - final Set expected = new HashSet<>(); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), "compile", + Set expected = new HashSet<>(); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), DependencyFlags.DEPLOYMENT_CP)); - expected.add(new ArtifactDependency(new GACTV("io.quarkus.bootstrap.test", "ext-a-deployment-dep", "1"), "compile", + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-deployment-dep", "1"), DependencyFlags.DEPLOYMENT_CP)); assertEquals(expected, getDeploymentOnlyDeps(model)); + + expected = new HashSet<>(); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-b", "1"), + JavaScopes.PROVIDED, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.DIRECT, + DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.COMPILE_ONLY)); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "some-provided-dep", "1"), + JavaScopes.PROVIDED, + DependencyFlags.DIRECT, + DependencyFlags.COMPILE_ONLY)); + assertEquals(expected, getDependenciesWithFlag(model, DependencyFlags.COMPILE_ONLY)); } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTestModeTest.java b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTestModeTest.java new file mode 100644 index 00000000000000..fae40e4beda5e6 --- /dev/null +++ b/core/deployment/src/test/java/io/quarkus/deployment/runnerjar/ProvidedExtensionDepsTestModeTest.java @@ -0,0 +1,92 @@ +package io.quarkus.deployment.runnerjar; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.TsDependency; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +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 ProvidedExtensionDepsTestModeTest extends BootstrapFromOriginalJarTestBase { + + @Override + protected boolean isBootstrapForTestMode() { + return true; + } + + @Override + protected TsArtifact composeApplication() { + + final TsArtifact extADep = TsArtifact.jar("ext-a-dep"); + addToExpectedLib(extADep); + + final TsArtifact extAProvidedDep = TsArtifact.jar("ext-a-provided-dep"); + + final TsArtifact extADeploymentDep = TsArtifact.jar("ext-a-deployment-dep"); + final TsArtifact extAOptionalDeploymentDep = TsArtifact.jar("ext-a-provided-deployment-dep"); + + final TsQuarkusExt extA = new TsQuarkusExt("ext-a"); + addToExpectedLib(extA.getRuntime()); + extA.getRuntime() + .addDependency(new TsDependency(extADep)) + .addDependency(new TsDependency(extAProvidedDep, "provided")); + extA.getDeployment() + .addDependency(new TsDependency(extADeploymentDep)) + .addDependency(new TsDependency(extAOptionalDeploymentDep, "provided")); + + final TsQuarkusExt extB = new TsQuarkusExt("ext-b"); + this.install(extB); + addToExpectedLib(extB.getRuntime()); // test mode enabled + + final TsArtifact someProvidedDep = TsArtifact.jar("some-provided-dep"); + addToExpectedLib(someProvidedDep); // test mode enabled + + return TsArtifact.jar("app") + .addManagedDependency(platformDescriptor()) + .addManagedDependency(platformProperties()) + .addDependency(extA) + .addDependency(extB, "provided") + .addDependency(new TsDependency(someProvidedDep, "provided")); + } + + @Override + protected void assertAppModel(ApplicationModel model) throws Exception { + + final Set compileOnly = new HashSet<>(); + compileOnly.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-b", "1"), + JavaScopes.PROVIDED, + DependencyFlags.RUNTIME_CP, + DependencyFlags.DEPLOYMENT_CP, + DependencyFlags.RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.DIRECT, + DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT, + DependencyFlags.COMPILE_ONLY)); + compileOnly.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "some-provided-dep", "1"), + JavaScopes.PROVIDED, + DependencyFlags.RUNTIME_CP, + DependencyFlags.DEPLOYMENT_CP, + DependencyFlags.DIRECT, + DependencyFlags.COMPILE_ONLY)); + assertEquals(compileOnly, getDependenciesWithFlag(model, DependencyFlags.COMPILE_ONLY)); + + Set expected = new HashSet<>(); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-deployment", "1"), + DependencyFlags.DEPLOYMENT_CP)); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-a-deployment-dep", "1"), + DependencyFlags.DEPLOYMENT_CP)); + expected.add(new ArtifactDependency(ArtifactCoords.jar("io.quarkus.bootstrap.test", "ext-b-deployment", "1"), + JavaScopes.PROVIDED, + DependencyFlags.DEPLOYMENT_CP)); + + assertEquals(expected, getDeploymentOnlyDeps(model)); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java index 2cb24beea18d09..95bd38dfc31d05 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/ApplicationModel.java @@ -26,12 +26,25 @@ public interface ApplicationModel { ResolvedDependency getAppArtifact(); /** - * All the dependencies of an application including runtime and build time dependencies. + * Returns application dependencies that are included into the runtime and augmentation (Quarkus build time) + * classpath. + *

+ * Note: in production bootstrap mode, {@link io.quarkus.maven.dependency.DependencyFlags#COMPILE_ONLY} dependencies + * will not be included in the result. However, they could still be queries by calling {@link #getDependencies(int)} + * passing in {@link io.quarkus.maven.dependency.DependencyFlags#COMPILE_ONLY} flag as an argument. * * @return application runtime and build time dependencies */ Collection getDependencies(); + /** + * Returns application dependencies with the requested flags set. + * + * @param flags dependency flags that must be set for a dependency to be included in the result + * @return application dependencies that have requested flags set + */ + Iterable getDependencies(int flags); + /** * Runtime dependencies of an application * 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 2b80f9cb4952f2..2aab254d2db020 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 @@ -1,9 +1,12 @@ package io.quarkus.bootstrap.model; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.stream.Collectors; @@ -39,7 +42,50 @@ public ResolvedDependency getAppArtifact() { @Override public Collection getDependencies() { - return dependencies; + var result = new ArrayList(dependencies.size()); + for (var d : getDependencies(DependencyFlags.DEPLOYMENT_CP)) { + result.add(d); + } + return result; + } + + @Override + public Iterable getDependencies(int flags) { + return () -> new Iterator<>() { + + final Iterator i = dependencies.iterator(); + ResolvedDependency next; + + { + moveOn(); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public ResolvedDependency next() { + if (next == null) { + throw new NoSuchElementException(); + } + var current = next; + moveOn(); + return current; + } + + private void moveOn() { + next = null; + while (i.hasNext()) { + var d = i.next(); + if ((d.getFlags() & flags) == flags) { + next = d; + break; + } + } + } + }; } @Override 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 117a6d2730eb2f..bc7475da3ae5e6 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 @@ -3,25 +3,44 @@ public interface DependencyFlags { /* @formatter:off */ - public static final int OPTIONAL = 0b000000000001; - public static final int DIRECT = 0b000000000010; - public static final int RUNTIME_CP = 0b000000000100; - public static final int DEPLOYMENT_CP = 0b000000001000; - public static final int RUNTIME_EXTENSION_ARTIFACT = 0b000000010000; - public static final int WORKSPACE_MODULE = 0b000000100000; - public static final int RELOADABLE = 0b000001000000; + public static final int OPTIONAL = 0b0000000000001; + public static final int DIRECT = 0b0000000000010; + public static final int RUNTIME_CP = 0b0000000000100; + public static final int DEPLOYMENT_CP = 0b0000000001000; + public static final int RUNTIME_EXTENSION_ARTIFACT = 0b0000000010000; + public static final int WORKSPACE_MODULE = 0b0000000100000; + public static final int RELOADABLE = 0b0000001000000; // A top-level runtime extension artifact is either a direct // dependency or a first extension dependency on the branch // navigating from the root to leaves - public static final int TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT = 0b000010000000; - public static final int CLASSLOADER_PARENT_FIRST = 0b000100000000; - public static final int CLASSLOADER_RUNNER_PARENT_FIRST = 0b001000000000; - public static final int CLASSLOADER_LESSER_PRIORITY = 0b010000000000; + public static final int TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT = 0b0000010000000; + public static final int CLASSLOADER_PARENT_FIRST = 0b0000100000000; + public static final int CLASSLOADER_RUNNER_PARENT_FIRST = 0b0001000000000; + public static final int CLASSLOADER_LESSER_PRIORITY = 0b0010000000000; // General purpose flag that could be re-used for various // kinds of processing indicating that a dependency has been // visited. This flag is meant to be cleared for all the nodes // once the processing of the whole tree has completed. - public static final int VISITED = 0b100000000000; + public static final int VISITED = 0b0100000000000; + + /** + * Compile-only dependencies are those that are configured in the project + * to be included only in the compile phase ({@code provided} dependency scope in Maven, + * {@code compileOnly} configuration in Gradle). + *

+ * These dependencies will not be present on the Quarkus application runtime or + * augmentation (deployment) classpath when the application is bootstrapped in production mode + * (io.quarkus.runtime.LaunchMode.NORMAL). + *

+ * Compile-only dependencies will be present on both the runtime and the augmentation classpath + * of a Quarkus application launched in test and dev modes. + *

+ * In any case though, these dependencies will be available during augmentation for processing + * using {@link io.quarkus.bootstrap.model.ApplicationModel#getDependencies(int)} by passing + * this flag as an argument. + */ + public static final int COMPILE_ONLY = 0b1000000000000; + /* @formatter:on */ } 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 8d486d0c7a9896..96febed3336392 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 @@ -289,13 +289,7 @@ private ApplicationModel doResolveModel(ArtifactCoords coords, } private Set getExcludedScopes() { - if (test) { - return Set.of(); - } - if (devmode) { - return Set.of(JavaScopes.TEST); - } - return Set.of(JavaScopes.PROVIDED, JavaScopes.TEST); + return test ? Set.of() : Set.of(JavaScopes.TEST); } private ApplicationModel buildAppModel(ResolvedDependency appArtifact, CollectRequest collectRtDepsRequest, @@ -318,6 +312,7 @@ private ApplicationModel buildAppModel(ResolvedDependency appArtifact, CollectRe .setApplicationModelBuilder(appBuilder) .setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty()) .setBuildTreeConsumer(buildTreeConsumer) + .setIncludeCompileOnly(test || devmode) .resolve(collectRtDepsRequest); } catch (BootstrapDependencyProcessingException e) { throw new AppModelResolverException( 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 21d10db1cec827..f2a0c010e27053 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 @@ -99,6 +99,7 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { private ApplicationModelBuilder appBuilder; private boolean collectReloadableModules; private Consumer buildTreeConsumer; + private boolean includeCompileOnly; public ApplicationDependencyTreeResolver setArtifactResolver(MavenArtifactResolver resolver) { this.resolver = resolver; @@ -130,6 +131,11 @@ public ApplicationDependencyTreeResolver setBuildTreeConsumer(Consumer b return this; } + public ApplicationDependencyTreeResolver setIncludeCompileOnly(boolean includeCompileOnly) { + this.includeCompileOnly = includeCompileOnly; + return this; + } + public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException { DependencyNode root = resolveRuntimeDeps(collectRtDepsRequest); @@ -206,7 +212,7 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver root = normalize(originalSession, root); final BuildDependencyGraphVisitor buildDepsVisitor = new BuildDependencyGraphVisitor(originalResolver, appBuilder, - buildTreeConsumer); + buildTreeConsumer, includeCompileOnly); buildDepsVisitor.visit(root); if (!CONVERGED_TREE_ONLY && collectReloadableModules) { @@ -354,11 +360,17 @@ private void visitRuntimeDependency(DependencyNode node) { artifact.getArtifactId(), artifact.getVersion()); } dep = toAppArtifact(artifact, module) - .setRuntimeCp() - .setDeploymentCp() .setOptional(node.getDependency().isOptional()) .setScope(node.getDependency().getScope()) .setDirect(isWalkingFlagOn(COLLECT_DIRECT_DEPS)); + if (JavaScopes.PROVIDED.equals(dep.getScope())) { + dep.setFlags(DependencyFlags.COMPILE_ONLY); + if (includeCompileOnly) { + dep.setRuntimeCp().setDeploymentCp(); + } + } else { + dep.setRuntimeCp().setDeploymentCp(); + } if (extDep != null) { dep.setRuntimeExtensionArtifact(); if (isWalkingFlagOn(COLLECT_TOP_EXTENSION_RUNTIME_NODES)) { diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java index 262a10365d7011..a6848bcdd049e9 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java @@ -10,6 +10,7 @@ import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.artifact.JavaScopes; import io.quarkus.bootstrap.model.ApplicationModelBuilder; import io.quarkus.bootstrap.util.DependencyUtils; @@ -24,16 +25,18 @@ public class BuildDependencyGraphVisitor { private final StringBuilder buf; private final Consumer buildTreeConsumer; private final List depth; + private final boolean includeCompileOnly; private DependencyNode currentDeployment; private DependencyNode currentRuntime; private Artifact runtimeArtifactToFind; public BuildDependencyGraphVisitor(MavenArtifactResolver resolver, ApplicationModelBuilder appBuilder, - Consumer buildTreeConsumer) { + Consumer buildTreeConsumer, boolean includeCompileOnly) { this.resolver = resolver; this.appBuilder = appBuilder; this.buildTreeConsumer = buildTreeConsumer; + this.includeCompileOnly = includeCompileOnly; if (buildTreeConsumer == null) { buf = null; depth = null; @@ -141,7 +144,9 @@ private void visitLeave(DependencyNode node) throws BootstrapMavenException { artifact = resolver.resolve(artifact, node.getRepositories()).getArtifact(); } - int flags = DependencyFlags.DEPLOYMENT_CP; + int flags = includeCompileOnly || !JavaScopes.PROVIDED.equals(node.getDependency().getScope()) + ? DependencyFlags.DEPLOYMENT_CP + : 0; if (node.getDependency().isOptional()) { flags |= DependencyFlags.OPTIONAL; }