From 0d900a75f38bbbff715d2f87d02ecfa47a61c2b5 Mon Sep 17 00:00:00 2001 From: essobedo Date: Sun, 16 May 2021 08:16:34 +0200 Subject: [PATCH] Allow to define parent first artifacts in fast jar mode --- .../pkg/steps/JarResultBuildStep.java | 34 ++++- .../java/io/quarkus/maven/it/BuildIT.java | 13 ++ .../java/io/quarkus/maven/it/DevMojoIT.java | 8 ++ .../classloader-linkage-error/pom.xml | 121 ++++++++++++++++++ .../src/main/java/org/acme/HelloResource.java | 22 ++++ .../src/main/java/org/acme/Producer.java | 14 ++ .../src/main/resources/application.properties | 1 + 7 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 integration-tests/maven/src/test/resources/projects/classloader-linkage-error/pom.xml create mode 100644 integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/HelloResource.java create mode 100644 integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/Producer.java create mode 100644 integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/resources/application.properties diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index 4ca4cb54ec5f3..1928309f232ee 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -68,6 +68,7 @@ import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.builditem.QuarkusBuildCloseablesBuildItem; import io.quarkus.deployment.builditem.TransformedClassesBuildItem; +import io.quarkus.deployment.configuration.ClassLoadingConfig; import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.pkg.builditem.AppCDSRequestedBuildItem; import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; @@ -203,6 +204,7 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem ApplicationArchivesBuildItem applicationArchivesBuildItem, ApplicationInfoBuildItem applicationInfo, PackageConfig packageConfig, + ClassLoadingConfig classLoadingConfig, List generatedClasses, List generatedResources, List uberJarRequired, @@ -234,7 +236,7 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem packageConfig, applicationInfo, generatedClasses, generatedResources, mainClassBuildItem); } else { return buildThinJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem, - packageConfig, applicationInfo, generatedClasses, generatedResources, + packageConfig, classLoadingConfig, applicationInfo, generatedClasses, generatedResources, additionalApplicationArchiveBuildItems, mainClassBuildItem); } } @@ -509,6 +511,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem, TransformedClassesBuildItem transformedClasses, ApplicationArchivesBuildItem applicationArchivesBuildItem, PackageConfig packageConfig, + ClassLoadingConfig classLoadingConfig, ApplicationInfoBuildItem applicationInfo, List generatedClasses, List generatedResources, @@ -576,7 +579,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem, } List jars = new ArrayList<>(); - List bootJars = new ArrayList<>(); + List parentFirst = new ArrayList<>(); //we process in order of priority //transformed classes first if (!transformedClasses.getTransformedClassesByJar().isEmpty()) { @@ -641,7 +644,7 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem, } } } - + final Set parentFirstKeys = getParentFirstKeys(curateOutcomeBuildItem, classLoadingConfig); StringBuilder classPath = new StringBuilder(); for (AppDependency appDep : curateOutcomeBuildItem.getEffectiveModel().getUserDependencies()) { if (rebuild) { @@ -650,9 +653,8 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem, copyDependency(curateOutcomeBuildItem, outputTargetBuildItem, copiedArtifacts, mainLib, baseLib, jars, true, classPath, appDep); } - if (curateOutcomeBuildItem.getEffectiveModel().getRunnerParentFirstArtifacts() - .contains(appDep.getArtifact().getKey())) { - bootJars.addAll(appDep.getArtifact().getPaths().toList()); + if (parentFirstKeys.contains(appDep.getArtifact().getKey())) { + parentFirst.addAll(appDep.getArtifact().getPaths().toList()); } } for (AdditionalApplicationArchiveBuildItem i : additionalApplicationArchiveBuildItems) { @@ -684,7 +686,8 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem, Path appInfo = buildDir.resolve(QuarkusEntryPoint.QUARKUS_APPLICATION_DAT); try (OutputStream out = Files.newOutputStream(appInfo)) { - SerializedApplication.write(out, mainClassBuildItem.getClassName(), buildDir, jars, bootJars, nonExistentResources); + SerializedApplication.write(out, mainClassBuildItem.getClassName(), buildDir, jars, parentFirst, + nonExistentResources); } runnerJar.toFile().setReadable(true, false); @@ -774,6 +777,23 @@ public void accept(Path path) { return new JarBuildItem(initJar, null, libDir, packageConfig.type, null); } + /** + * @return a {@code Set} containing the key of the artifacts to load from the parent ClassLoader first. + */ + private Set getParentFirstKeys(CurateOutcomeBuildItem curateOutcomeBuildItem, + ClassLoadingConfig classLoadingConfig) { + final Set parentFirstKeys = new HashSet<>( + curateOutcomeBuildItem.getEffectiveModel().getRunnerParentFirstArtifacts()); + classLoadingConfig.parentFirstArtifacts.ifPresent( + parentFirstArtifacts -> { + String[] artifacts = parentFirstArtifacts.split(","); + for (String artifact : artifacts) { + parentFirstKeys.add(new AppArtifactKey(artifact.split(":"))); + } + }); + return parentFirstKeys; + } + private boolean downloadFernflowerJar(PackageConfig packageConfig, Path fernflowerJar) { String downloadURL = String.format("https://jitpack.io/com/github/fesh0r/fernflower/%s/fernflower-%s.jar", packageConfig.fernflower.hash, packageConfig.fernflower.hash); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java index 2f5319e547443..8f40e23e59504 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/BuildIT.java @@ -72,6 +72,19 @@ void testMultiModuleAppRootWithNoSources() ensureManifestOfJarIsReadableByJarInputStream(runnerJar); } + @Test + void testClassLoaderLinkageError() + throws MavenInvocationException, IOException, InterruptedException { + testDir = initProject("projects/classloader-linkage-error", "projects/classloader-linkage-error-build"); + build(); + for (TestContext context : TestContext.values()) { + if (context == TestContext.FAST_NO_PREFIX) { + continue; + } + launch(context, "", "hello"); + } + } + @Test void testModuleWithBuildProfileInProperty() throws MavenInvocationException, InterruptedException, IOException { testDir = initProject("projects/build-mode-quarkus-profile-property"); diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 82ce10529fc4d..1a6c0e56511de 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -65,6 +65,14 @@ public void testEnvironmentVariablesConfig() throws MavenInvocationException, IO assertThat(DevModeTestUtils.getHttpResponse("/hello")).isEqualTo("hello, WORLD"); } + @Test + void testClassLoaderLinkageError() + throws MavenInvocationException, IOException, InterruptedException { + testDir = initProject("projects/classloader-linkage-error", "projects/classloader-linkage-error-dev"); + run(true); + assertThat(DevModeTestUtils.getHttpResponse("/hello")).isEqualTo("hello"); + } + @Test public void testCapabilitiesConflict() throws MavenInvocationException, IOException { testDir = getTargetDir("projects/capabilities-conflict"); diff --git a/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/pom.xml b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/pom.xml new file mode 100644 index 0000000000000..ca0a9ddc0636f --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + org.acme + acme + 1.0-SNAPSHOT + + io.quarkus + quarkus-bom + @project.version@ + @project.version@ + 11 + UTF-8 + 11 + 2.3.1 + 2.3.0.1 + 1.0.1 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + io.quarkus + quarkus-resteasy + + + javax.xml.bind + jaxb-api + ${jaxb.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + com.sun.xml.bind + jaxb-core + ${jaxb-core.version} + + + stax + stax-api + ${stax-api.version} + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + fast-jar + + generate-code + generate-code-tests + build + + + + fast-quarkus-app + + + + + legacy-jar + + generate-code + generate-code-tests + build + + + + legacy + legacy-jar + legacy-quarkus-app + + + + + uber-jar + + generate-code + generate-code-tests + build + + + + uber + uber-jar + uber-quarkus-app + + + + + + + + + + native + + native + + + + diff --git a/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..8108949192ae6 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,22 @@ +package org.acme; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import javax.xml.bind.JAXBContext; + +@Path("/hello") +public class HelloResource { + + @Inject + JAXBContext context; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } +} diff --git a/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/Producer.java b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/Producer.java new file mode 100644 index 0000000000000..4237af3c413d2 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/java/org/acme/Producer.java @@ -0,0 +1,14 @@ +package org.acme; + +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; +import javax.xml.bind.JAXBContext; + +public class Producer { + + @Singleton + @Produces + protected JAXBContext createJAXBContext() throws Exception { + return JAXBContext.newInstance(); // This line causes the LinkageError + } +} diff --git a/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/resources/application.properties b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/resources/application.properties new file mode 100644 index 0000000000000..abd84162f5b9c --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/classloader-linkage-error/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.class-loading.parent-first-artifacts=stax:stax-api