diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ConfiguredClassLoading.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ConfiguredClassLoading.java index 70074a1d91c45..498dcc6cf816e 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ConfiguredClassLoading.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/ConfiguredClassLoading.java @@ -19,6 +19,7 @@ import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.GACT; +import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.paths.PathCollection; public class ConfiguredClassLoading implements Serializable { @@ -61,6 +62,7 @@ public Builder setApplicationModel(ApplicationModel model) { public ConfiguredClassLoading build() { + final String profilePrefix = getProfilePrefix(mode); for (Path path : applicationRoot) { Path props = path.resolve("application.properties"); if (Files.exists(props)) { @@ -71,29 +73,19 @@ public ConfiguredClassLoading build() { throw new RuntimeException("Failed to load bootstrap classloading config from application.properties", e); } - collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.parent-first-artifacts", p, mode)), - parentFirstArtifacts); - collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.reloadable-artifacts", p, mode)), - reloadableArtifacts); - collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.removed-artifacts", p, mode)), - removedArtifacts); - collectRemovedResources("quarkus.class-loading.removed-resources.", p); - - if (!flatTestClassPath && mode == Mode.TEST) { - final String s = p.getProperty(selectKey("quarkus.test.flat-class-path", p, mode)); - if (s != null) { - flatTestClassPath = Boolean.parseBoolean(s); - } - } + readProperties(profilePrefix, p); } } + // this is to be able to support exclusion of artifacts from build classpath configured using system properties + readProperties(profilePrefix, System.getProperties()); + if (appModel != null) { - appModel.getDependencies().forEach(d -> { + for (ResolvedDependency d : appModel.getDependencies()) { if (d.isClassLoaderParentFirst()) { parentFirstArtifacts.add(d.getKey()); } - }); + } if (mode == Mode.TEST) { final WorkspaceModule module = appModel.getApplicationModule(); @@ -128,15 +120,31 @@ public ConfiguredClassLoading build() { return ConfiguredClassLoading.this; } - private void collectRemovedResources(String baseConfigKey, Properties properties) { + private void readProperties(String profilePrefix, Properties p) { + collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.parent-first-artifacts", p, profilePrefix)), + parentFirstArtifacts); + collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.reloadable-artifacts", p, profilePrefix)), + reloadableArtifacts); + collectArtifactKeys(p.getProperty(selectKey("quarkus.class-loading.removed-artifacts", p, profilePrefix)), + removedArtifacts); + collectRemovedResources("quarkus.class-loading.removed-resources.", p, profilePrefix); + + if (!flatTestClassPath && mode == Mode.TEST) { + final String s = p.getProperty(selectKey("quarkus.test.flat-class-path", p, profilePrefix)); + if (s != null) { + flatTestClassPath = Boolean.parseBoolean(s); + } + } + } + + private void collectRemovedResources(String baseConfigKey, Properties properties, String profilePrefix) { Properties profileProps = new Properties(); - String profile = BootstrapProfile.getActiveProfile(mode); for (Map.Entry i : properties.entrySet()) { String key = i.getKey().toString(); if (key.startsWith("%")) { continue; } - String profileKey = "%" + profile + "." + key; + final String profileKey = profilePrefix + key; if (properties.containsKey(profileKey)) { profileProps.put(key, properties.getProperty(profileKey)); } else { @@ -160,15 +168,18 @@ private void collectRemovedResources(String baseConfigKey, Properties properties } } - private String selectKey(String base, Properties p, Mode mode) { - String profile = BootstrapProfile.getActiveProfile(mode); - String profileKey = "%" + profile + "." + base; + private String selectKey(String base, Properties p, String profilePrefix) { + final String profileKey = profilePrefix + base; if (p.containsKey(profileKey)) { return profileKey; } return base; } + private String getProfilePrefix(Mode mode) { + return "%" + BootstrapProfile.getActiveProfile(mode) + "."; + } + private void collectArtifactKeys(String config, Collection keys) { if (config == null) { return; diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java index f246f83c0cdd4..7f77a82f5e6da 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -36,6 +36,7 @@ import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; @@ -344,6 +345,120 @@ public void reaugmentationWithRemovedArtifacts() throws Exception { } } + @Test + @Timeout(80) // windows might need more time + public void reaugmentationWithRemovedArtifactsUsingSystemProperties() throws Exception { + File testDir = initProject("projects/extension-removed-resources", + "projects/extension-removed-artifacts-reaugmentation"); + RunningInvoker running = new RunningInvoker(testDir, false); + + // The default build + MavenProcessInvocationResult result = running + .execute(List.of("package", "-DskipTests", "-Dquarkus.package.type=mutable-jar"), Map.of()); + await().atMost(1, TimeUnit.MINUTES).until(() -> result.getProcess() != null && !result.getProcess().isAlive()); + assertThat(running.log()).containsIgnoringCase("BUILD SUCCESS"); + running.stop(); + + testDir = testDir.toPath().resolve("runner").toFile(); + + Path runJar = testDir.toPath().toAbsolutePath().resolve(Paths.get("target/quarkus-app/quarkus-run.jar")); + assertThat(runJar).exists(); + + File output = new File(testDir, "target/output.log"); + output.createNewFile(); + + Process process = doLaunch(runJar, output).start(); + try { + AtomicReference response = new AtomicReference<>(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/runtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("subatomic,supersonic"); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/buildtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("subatomic,supersonic"); + } finally { + process.destroy(); + } + + // re-augment and exclude html-extra + process = doLaunch(runJar, output, + List.of("-Dquarkus.class-loading.removed-artifacts=org.acme:acme-subatomic-provider", + "-Dquarkus.launch.rebuild=true")) + .start(); + try { + assertThat(process.waitFor()).isEqualTo(0); + } finally { + process.destroy(); + } + + process = doLaunch(runJar, output).start(); + try { + AtomicReference response = new AtomicReference<>(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/runtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("supersonic"); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/buildtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("supersonic"); + } finally { + process.destroy(); + } + + // re-augment with the original dependencies + process = doLaunch(runJar, output, List.of("-Dquarkus.launch.rebuild=true")).start(); + try { + assertThat(process.waitFor()).isEqualTo(0); + } finally { + process.destroy(); + } + + process = doLaunch(runJar, output).start(); + try { + AtomicReference response = new AtomicReference<>(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/runtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("subatomic,supersonic"); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String ret = DevModeTestUtils.getHttpResponse("/words/buildtime", false); + response.set(ret); + return true; + }); + assertThat(response.get()).isEqualTo("subatomic,supersonic"); + } finally { + process.destroy(); + } + } + private void assertThatMutableFastJarWorks(String targetDirSuffix, String providersDir) throws Exception { File testDir = initProject("projects/classic", "projects/project-classic-console-output-mutable-fast-jar" + targetDirSuffix); diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/deployment/src/main/java/org/acme/deployment/AcmeProcessor.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/deployment/src/main/java/org/acme/deployment/AcmeProcessor.java index 363713a3935e4..5a22d07114931 100644 --- a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/deployment/src/main/java/org/acme/deployment/AcmeProcessor.java +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/deployment/src/main/java/org/acme/deployment/AcmeProcessor.java @@ -2,10 +2,17 @@ import java.util.Set; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.RemovedResourceBuildItem; -import io.quarkus.maven.dependency.ArtifactKey; + +import org.acme.AcmeRecorder; +import org.acme.RecordedWords; +import org.acme.WordProvider; class AcmeProcessor { @@ -15,4 +22,16 @@ class AcmeProcessor { FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } + + @BuildStep + void registerBeans(BuildProducer additionalBeans) { + additionalBeans.produce(AdditionalBeanBuildItem.builder() + .addBeanClasses(RecordedWords.class).build()); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void recordWords(AcmeRecorder recorder, BeanContainerBuildItem beanContainer) { + recorder.recordWords(beanContainer.getValue(), WordProvider.loadAndSortWords(), Thread.currentThread().getContextClassLoader().getName()); + } } diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/pom.xml index cb89f7886c31a..3352052195be8 100644 --- a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/pom.xml +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/pom.xml @@ -18,6 +18,16 @@ org.acme acme-resources + + org.acme + acme-supersonic-provider + \${project.version} + + + org.acme + acme-subatomic-provider + \${project.version} + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/AcmeRecorder.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/AcmeRecorder.java new file mode 100644 index 0000000000000..283d59c959361 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/AcmeRecorder.java @@ -0,0 +1,16 @@ +package org.acme; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.annotations.Recorder; + +import java.util.List; + +@Recorder +public class AcmeRecorder { + + public void recordWords(BeanContainer beanContainer, List words, String classLoaderName) { + var bean = beanContainer.beanInstance(RecordedWords.class); + bean.setWords(words); + bean.setClassLoaderName(classLoaderName); + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/RecordedWords.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/RecordedWords.java new file mode 100644 index 0000000000000..0d2e57325230d --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/extension/runtime/src/main/java/org/acme/RecordedWords.java @@ -0,0 +1,28 @@ +package org.acme; + +import jakarta.inject.Singleton; + +import java.util.List; + +@Singleton +public class RecordedWords { + + private List words; + private String classLoaderName; + + void setWords(List words) { + this.words = words; + } + + public List getWords() { + return words; + } + + void setClassLoaderName(String classLoaderName) { + this.classLoaderName = classLoaderName; + } + + public String getClassLoaderName() { + return classLoaderName; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/pom.xml index 433fef757143d..b9e8420511aa3 100644 --- a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/pom.xml +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/pom.xml @@ -43,6 +43,9 @@ + service-loader + supersonic-provider + subatomic-provider resources extension runner diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/runner/src/main/java/org/acme/WordsResource.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/runner/src/main/java/org/acme/WordsResource.java new file mode 100644 index 0000000000000..85a143c3a9fdb --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/runner/src/main/java/org/acme/WordsResource.java @@ -0,0 +1,51 @@ +package org.acme; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.inject.Inject; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; + +import java.util.List; + +import org.acme.WordProvider; + +@Path("/words") +public class WordsResource { + + @Inject + RecordedWords recordedWords; + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/runtime") + public String runtimeWords() { + return toString(WordProvider.loadAndSortWords()); + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/buildtime") + public String buildtimeWords() { + return toString(recordedWords.getWords()); + } + + private static String toString(List words) { + if(words.isEmpty()) { + return ""; + } + var sb = new StringBuilder(); + sb.append(words.get(0)); + for(int i = 1; i < words.size(); ++i) { + sb.append(",").append(words.get(i)); + } + return sb.toString(); + } +} diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/pom.xml new file mode 100644 index 0000000000000..95b25018b5ada --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.acme + code-with-quarkus + 1.0.0-SNAPSHOT + + acme-service-loader + + + + maven-compiler-plugin + \${compiler-plugin.version} + + + -parameters + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/src/main/java/org/acme/WordProvider.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/src/main/java/org/acme/WordProvider.java new file mode 100644 index 0000000000000..2c0cea2903398 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/service-loader/src/main/java/org/acme/WordProvider.java @@ -0,0 +1,36 @@ +package org.acme; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +public interface WordProvider { + + static List loadProviders() { + var i = ServiceLoader.load(WordProvider.class).iterator(); + if(!i.hasNext()) { + return List.of(); + } + final List result = new ArrayList<>(); + while(i.hasNext()) { + result.add(i.next()); + } + return result; + } + + static List loadAndSortWords() { + var providers = loadProviders(); + if(providers.isEmpty()) { + return List.of(); + } + final List result = new ArrayList<>(providers.size()); + for(var provider : providers) { + result.add(provider.getWord()); + } + Collections.sort(result); + return result; + } + + String getWord(); +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/pom.xml new file mode 100644 index 0000000000000..f826901cf5ff9 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.acme + code-with-quarkus + 1.0.0-SNAPSHOT + + acme-subatomic-provider + + + org.acme + acme-service-loader + \${project.version} + + + + + + maven-compiler-plugin + \${compiler-plugin.version} + + + -parameters + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/java/org/acme/SubatomicProvider.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/java/org/acme/SubatomicProvider.java new file mode 100644 index 0000000000000..60bef92a0dd45 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/java/org/acme/SubatomicProvider.java @@ -0,0 +1,9 @@ +package org.acme; + +public class SubatomicProvider implements WordProvider { + + @Override + public String getWord() { + return "subatomic"; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/resources/META-INF/services/org.acme.WordProvider b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/resources/META-INF/services/org.acme.WordProvider new file mode 100644 index 0000000000000..bfbdad8d6da66 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/subatomic-provider/src/main/resources/META-INF/services/org.acme.WordProvider @@ -0,0 +1 @@ +org.acme.SubatomicProvider \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/pom.xml b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/pom.xml new file mode 100644 index 0000000000000..1eb6f96c04979 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.acme + code-with-quarkus + 1.0.0-SNAPSHOT + + acme-supersonic-provider + + + org.acme + acme-service-loader + \${project.version} + + + + + + maven-compiler-plugin + \${compiler-plugin.version} + + + -parameters + + + + + + diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/java/org/acme/SupersonicProvider.java b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/java/org/acme/SupersonicProvider.java new file mode 100644 index 0000000000000..41117d467f536 --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/java/org/acme/SupersonicProvider.java @@ -0,0 +1,9 @@ +package org.acme; + +public class SupersonicProvider implements WordProvider { + + @Override + public String getWord() { + return "supersonic"; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/resources/META-INF/services/org.acme.WordProvider b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/resources/META-INF/services/org.acme.WordProvider new file mode 100644 index 0000000000000..4c84187f2c76e --- /dev/null +++ b/integration-tests/maven/src/test/resources-filtered/projects/extension-removed-resources/supersonic-provider/src/main/resources/META-INF/services/org.acme.WordProvider @@ -0,0 +1 @@ +org.acme.SupersonicProvider \ No newline at end of file