From 7670c2189f6d16e972ff43de3c0bf7c1542ed8b3 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 6 Sep 2025 22:17:17 +0100 Subject: [PATCH] POC for a buildItem defining the add-opens needs --- .../deployment/JBossThreadsProcessor.java | 8 ++++ .../deployment/ResolvedJVMRequirements.java | 38 +++++++++++++++++++ .../builditem/ModuleOpenBuildItem.java | 23 +++++++++++ .../pkg/jar/AbstractJarBuilder.java | 11 ++++-- .../pkg/jar/AbstractLegacyThinJarBuilder.java | 9 +++-- .../deployment/pkg/jar/FastJarBuilder.java | 7 +++- .../pkg/jar/LegacyThinJarBuilder.java | 6 ++- .../pkg/jar/NativeImageSourceJarBuilder.java | 6 ++- .../deployment/pkg/jar/UberJarBuilder.java | 8 ++-- .../pkg/steps/JarResultBuildStep.java | 17 ++++++--- .../steps/JvmRequirementsBuildStep.java | 15 ++++++++ 11 files changed, 128 insertions(+), 20 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/ResolvedJVMRequirements.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/builditem/ModuleOpenBuildItem.java create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/steps/JvmRequirementsBuildStep.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/JBossThreadsProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/JBossThreadsProcessor.java index dc45d2f0b305b..63f9c03ad974c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/JBossThreadsProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/JBossThreadsProcessor.java @@ -1,6 +1,7 @@ package io.quarkus.deployment; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ModuleOpenBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; public class JBossThreadsProcessor { @@ -11,4 +12,11 @@ RuntimeInitializedClassBuildItem build() { // see https://github.com/jbossas/jboss-threads/pull/200 return new RuntimeInitializedClassBuildItem("org.jboss.threads.EnhancedQueueExecutor$RuntimeFields"); } + + @BuildStep + ModuleOpenBuildItem allowClearThreadLocals() { + // JDK 24+ needs --add-opens=java.base/java.lang=ALL-UNNAMED for org.jboss.JDKSpecific.ThreadAccess.clearThreadLocals() + return new ModuleOpenBuildItem("java.base/java.lang"); + } + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ResolvedJVMRequirements.java b/core/deployment/src/main/java/io/quarkus/deployment/ResolvedJVMRequirements.java new file mode 100644 index 0000000000000..728210068cd0f --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/ResolvedJVMRequirements.java @@ -0,0 +1,38 @@ +package io.quarkus.deployment; + +import java.util.List; +import java.util.jar.Attributes; + +import org.jboss.logging.Logger; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.deployment.builditem.ModuleOpenBuildItem; + +/** + * Represents requirements and restrictions on the runtime. + * Currently only used to track add-opens requirements; I'd like to eventually + * support tracking, for example, the required JVM version as we start to see + * some extensions having stronger opinions. + * Another use could be, for example, to force enabling experimental features such + * as needing jdk.incubator.vector (which would also inherently bump the minimum + * JVM version). + */ +public final class ResolvedJVMRequirements extends SimpleBuildItem { + private static final Logger LOG = Logger.getLogger(ResolvedJVMRequirements.class); + private static final Attributes.Name ADD_OPENS_JARATTRIBUTENAME = new Attributes.Name("Add-Opens"); + private List modulesToAddOpens; + + public ResolvedJVMRequirements(List addOpens) { + this.modulesToAddOpens = addOpens.stream().map(ModuleOpenBuildItem::moduleName).distinct().sorted().toList(); + } + + public void renderAddOpensElementToJarManifest(Attributes attributes) { + if (!modulesToAddOpens.isEmpty()) { + if (attributes.getValue(ADD_OPENS_JARATTRIBUTENAME) != null) { + LOG.warn( + "An 'Add-Opens' entry was already defined in your MANIFEST.MF or using the property quarkus.package.jar.manifest.attributes.\"Add-Opens\". Quarkus has overwritten this existing entry."); + } + attributes.put(ADD_OPENS_JARATTRIBUTENAME, String.join(" ", modulesToAddOpens)); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ModuleOpenBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ModuleOpenBuildItem.java new file mode 100644 index 0000000000000..6c010521121ac --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ModuleOpenBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.deployment.builditem; + +import java.util.Objects; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This will generate the equivalent of "--add-opens [module-name]=ALL-UNNAMED" for + * all runners of the generated application. + * It's currently only possible to open a module to ALL-UNNAMED; this restriction is dictated + * by the limitations of the specification of the Jar's manifest format. + */ +public final class ModuleOpenBuildItem extends MultiBuildItem { + private final String moduleName; + + public ModuleOpenBuildItem(String moduleName) { + this.moduleName = Objects.requireNonNull(moduleName); + } + + public String moduleName() { + return moduleName; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractJarBuilder.java index 99e1d993f0056..639427e5c3654 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractJarBuilder.java @@ -19,6 +19,7 @@ import io.quarkus.builder.item.BuildItem; import io.quarkus.deployment.ApplicationArchive; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -47,6 +48,7 @@ public abstract class AbstractJarBuilder implements JarBuil protected final List generatedClasses; protected final List generatedResources; protected final Set removedArtifactKeys; + protected final ResolvedJVMRequirements jvmRequirements; public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome, OutputTargetBuildItem outputTarget, @@ -57,7 +59,8 @@ public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome, TransformedClassesBuildItem transformedClasses, List generatedClasses, List generatedResources, - Set removedArtifactKeys) { + Set removedArtifactKeys, + ResolvedJVMRequirements jvmRequirements) { this.curateOutcome = curateOutcome; this.outputTarget = outputTarget; this.applicationInfo = applicationInfo; @@ -68,6 +71,7 @@ public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome, this.generatedClasses = generatedClasses; this.generatedResources = generatedResources; this.removedArtifactKeys = removedArtifactKeys; + this.jvmRequirements = jvmRequirements; } /** @@ -176,6 +180,7 @@ protected void copyCommonContent(ArchiveCreator archiveCreator, */ protected static void generateManifest(ArchiveCreator archiveCreator, final String classPath, PackageConfig config, ResolvedDependency appArtifact, + ResolvedJVMRequirements jvmRequirements, String mainClassName, ApplicationInfoBuildItem applicationInfo) throws IOException { @@ -183,8 +188,8 @@ protected static void generateManifest(ArchiveCreator archiveCreator, final Stri Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); - // JDK 24+ needs --add-opens=java.base/java.lang=ALL-UNNAMED for org.jboss.JDKSpecific.ThreadAccess.clearThreadLocals() - attributes.put(new Attributes.Name("Add-Opens"), "java.base/java.lang"); + + jvmRequirements.renderAddOpensElementToJarManifest(attributes); for (Map.Entry attribute : config.jar().manifest().attributes().entrySet()) { attributes.putValue(attribute.getKey(), attribute.getValue()); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractLegacyThinJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractLegacyThinJarBuilder.java index dd7d34f537e6d..26b970339c1bf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractLegacyThinJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/AbstractLegacyThinJarBuilder.java @@ -20,6 +20,7 @@ import org.jboss.logging.Logger; import io.quarkus.builder.item.BuildItem; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -49,9 +50,10 @@ public AbstractLegacyThinJarBuilder(CurateOutcomeBuildItem curateOutcome, List generatedClasses, List generatedResources, Set removedArtifactKeys, - ExecutorService executorService) { + ExecutorService executorService, + ResolvedJVMRequirements jvmRequirements) { super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses, - generatedClasses, generatedResources, removedArtifactKeys); + generatedClasses, generatedResources, removedArtifactKeys, jvmRequirements); this.executorService = executorService; } @@ -77,7 +79,8 @@ protected void doBuild(Path runnerJar, Path libDir) throws IOException { ResolvedDependency appArtifact = curateOutcome.getApplicationModel().getAppArtifact(); // the manifest needs to be the first entry in the jar, otherwise JarInputStream does not work properly // see https://bugs.openjdk.java.net/browse/JDK-8031748 - generateManifest(archiveCreator, classPath.toString(), packageConfig, appArtifact, mainClass.getClassName(), + generateManifest(archiveCreator, classPath.toString(), packageConfig, appArtifact, jvmRequirements, + mainClass.getClassName(), applicationInfo); copyCommonContent(archiveCreator, services, ignoredEntriesPredicate); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/FastJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/FastJarBuilder.java index da30d75d3db43..b7de9618b0d1e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/FastJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/FastJarBuilder.java @@ -41,6 +41,7 @@ import io.quarkus.bootstrap.runner.QuarkusEntryPoint; import io.quarkus.bootstrap.runner.SerializedApplication; import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveBuildItem; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; @@ -82,9 +83,10 @@ public FastJarBuilder(CurateOutcomeBuildItem curateOutcome, List generatedResources, Set parentFirstArtifactKeys, Set removedArtifactKeys, - ExecutorService executorService) { + ExecutorService executorService, + ResolvedJVMRequirements jvmRequirements) { super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses, - generatedClasses, generatedResources, removedArtifactKeys); + generatedClasses, generatedResources, removedArtifactKeys, jvmRequirements); this.additionalApplicationArchives = additionalApplicationArchives; this.parentFirstArtifactKeys = parentFirstArtifactKeys; this.executorService = executorService; @@ -313,6 +315,7 @@ public JarBuildItem build() throws IOException { outputTarget.getOutputDirectory(), executorService)) { ResolvedDependency appArtifact = curateOutcome.getApplicationModel().getAppArtifact(); generateManifest(archiveCreator, classPath.toString(), packageConfig, appArtifact, + jvmRequirements, QuarkusEntryPoint.class.getName(), applicationInfo); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/LegacyThinJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/LegacyThinJarBuilder.java index 5f71cc46e8e38..0d1e6eef88dcb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/LegacyThinJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/LegacyThinJarBuilder.java @@ -10,6 +10,7 @@ import org.jboss.logging.Logger; import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -36,9 +37,10 @@ public LegacyThinJarBuilder(CurateOutcomeBuildItem curateOutcome, List generatedClasses, List generatedResources, Set removedArtifactKeys, - ExecutorService executorService) { + ExecutorService executorService, + ResolvedJVMRequirements jvmRequirements) { super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses, - generatedClasses, generatedResources, removedArtifactKeys, executorService); + generatedClasses, generatedResources, removedArtifactKeys, executorService, jvmRequirements); } public JarBuildItem build() throws IOException { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/NativeImageSourceJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/NativeImageSourceJarBuilder.java index ce759eccb0067..77590a92d14ed 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/NativeImageSourceJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/NativeImageSourceJarBuilder.java @@ -17,6 +17,7 @@ import org.jboss.logging.Logger; import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -46,10 +47,11 @@ public NativeImageSourceJarBuilder(CurateOutcomeBuildItem curateOutcome, List generatedResources, List nativeImageResources, Set removedArtifactKeys, - ExecutorService executorService) { + ExecutorService executorService, + ResolvedJVMRequirements jvmRequirements) { super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses, augmentGeneratedClasses(generatedClasses, nativeImageResources), generatedResources, - augmentRemovedArtifactKeys(removedArtifactKeys), executorService); + augmentRemovedArtifactKeys(removedArtifactKeys), executorService, jvmRequirements); } public NativeImageSourceJarBuildItem build() throws IOException { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/UberJarBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/UberJarBuilder.java index b1fd2f6357311..1a95f49b93d7b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/UberJarBuilder.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/jar/UberJarBuilder.java @@ -23,6 +23,7 @@ import org.jboss.logging.Logger; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -73,9 +74,10 @@ public UberJarBuilder(CurateOutcomeBuildItem curateOutcome, List generatedResources, Set removedArtifactKeys, List mergedResources, - List ignoredResources) { + List ignoredResources, + ResolvedJVMRequirements jvmRequirements) { super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses, - generatedClasses, generatedResources, removedArtifactKeys); + generatedClasses, generatedResources, removedArtifactKeys, jvmRequirements); this.mergedResources = mergedResources; this.ignoredResources = ignoredResources; @@ -169,7 +171,7 @@ public boolean test(String path) { // the manifest needs to be the first entry in the jar, otherwise JarInputStream does not work properly // see https://bugs.openjdk.java.net/browse/JDK-8031748 - generateManifest(archiveCreator, "", packageConfig, appArtifact, mainClass.getClassName(), + generateManifest(archiveCreator, "", packageConfig, appArtifact, jvmRequirements, mainClass.getClassName(), applicationInfo); for (ResolvedDependency appDep : curateOutcome.getApplicationModel().getRuntimeDependencies()) { 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 7eb166e3879de..37618b9d52c5a 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 @@ -18,6 +18,7 @@ import java.util.function.BooleanSupplier; import java.util.stream.Collectors; +import io.quarkus.deployment.ResolvedJVMRequirements; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveBuildItem; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; @@ -92,6 +93,7 @@ ArtifactResultBuildItem jarOutput(JarBuildItem jarBuildItem) { @SuppressWarnings("deprecation") // JarType#LEGACY_JAR @BuildStep public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem, + ResolvedJVMRequirements jvmRequirements, OutputTargetBuildItem outputTargetBuildItem, TransformedClassesBuildItem transformedClasses, ApplicationArchivesBuildItem applicationArchivesBuildItem, @@ -128,7 +130,8 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem generatedResources, removedArtifactKeys, uberJarMergedResourceBuildItems, - uberJarIgnoredResourceBuildItems).build(); + uberJarIgnoredResourceBuildItems, + jvmRequirements).build(); case LEGACY_JAR -> new LegacyThinJarBuilder(curateOutcomeBuildItem, outputTargetBuildItem, applicationInfo, @@ -139,7 +142,8 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem generatedClasses, generatedResources, removedArtifactKeys, - buildExecutor).build(); + buildExecutor, + jvmRequirements).build(); case FAST_JAR, MUTABLE_JAR -> new FastJarBuilder(curateOutcomeBuildItem, outputTargetBuildItem, applicationInfo, @@ -152,7 +156,8 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem generatedResources, parentFirstArtifactKeys, removedArtifactKeys, - buildExecutor).build(); + buildExecutor, + jvmRequirements).build(); }; } @@ -171,7 +176,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem List generatedResources, MainClassBuildItem mainClassBuildItem, ClassLoadingConfig classLoadingConfig, - ExecutorService buildExecutor) throws Exception { + ExecutorService buildExecutor, + ResolvedJVMRequirements jvmRequirements) throws Exception { return new NativeImageSourceJarBuilder(curateOutcomeBuildItem, outputTargetBuildItem, @@ -184,7 +190,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem generatedResources, nativeImageResources, getRemovedArtifactKeys(classLoadingConfig), - buildExecutor).build(); + buildExecutor, + jvmRequirements).build(); } // the idea here is to just dump the class names of the generated and transformed classes into a file diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/JvmRequirementsBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/JvmRequirementsBuildStep.java new file mode 100644 index 0000000000000..30b02f57271d6 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/JvmRequirementsBuildStep.java @@ -0,0 +1,15 @@ +package io.quarkus.deployment.steps; + +import java.util.List; + +import io.quarkus.deployment.ResolvedJVMRequirements; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ModuleOpenBuildItem; + +public class JvmRequirementsBuildStep { + + @BuildStep + ResolvedJVMRequirements resolveJVMRequirements(List addOpens) { + return new ResolvedJVMRequirements(addOpens); + } +}