Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be two separate properties. Or maybe a set of packages to open. Plus the destination module as I said earlier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the Jar's Manifest format doesn't allow to specify a destination module.

We could add it here as a hint for users of the new buildItem that they should specify it - in hope for us to being able to use it better in the future, but I'm concerned that since it won't have any effect there is no way to test that people are specifying the correct values.

Perhaps it's best to not have a misleading API and stick to offer what it can do?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're thinking only of what it can do, today, this week. But I already have a parallel branch which modularizes the application fully. I'd rather only maybe have to rework one or two mistakes than have to rework everything. The JAR manifest is an edge case in the greater scheme; it just happens to be the first case we've encountered.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then! Sounds great, I just wasn't sure if you were aware of these limitations - sounds like you are :)

}

}
Original file line number Diff line number Diff line change
@@ -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<String> modulesToAddOpens;

public ResolvedJVMRequirements(List<ModuleOpenBuildItem> 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));
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,6 +48,7 @@ public abstract class AbstractJarBuilder<T extends BuildItem> implements JarBuil
protected final List<GeneratedClassBuildItem> generatedClasses;
protected final List<GeneratedResourceBuildItem> generatedResources;
protected final Set<ArtifactKey> removedArtifactKeys;
protected final ResolvedJVMRequirements jvmRequirements;

public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome,
OutputTargetBuildItem outputTarget,
Expand All @@ -57,7 +59,8 @@ public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome,
TransformedClassesBuildItem transformedClasses,
List<GeneratedClassBuildItem> generatedClasses,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> removedArtifactKeys) {
Set<ArtifactKey> removedArtifactKeys,
ResolvedJVMRequirements jvmRequirements) {
this.curateOutcome = curateOutcome;
this.outputTarget = outputTarget;
this.applicationInfo = applicationInfo;
Expand All @@ -68,6 +71,7 @@ public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome,
this.generatedClasses = generatedClasses;
this.generatedResources = generatedResources;
this.removedArtifactKeys = removedArtifactKeys;
this.jvmRequirements = jvmRequirements;
}

/**
Expand Down Expand Up @@ -176,15 +180,16 @@ 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 {
final Manifest manifest = new Manifest();

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<String, String> attribute : config.jar().manifest().attributes().entrySet()) {
attributes.putValue(attribute.getKey(), attribute.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -49,9 +50,10 @@ public AbstractLegacyThinJarBuilder(CurateOutcomeBuildItem curateOutcome,
List<GeneratedClassBuildItem> generatedClasses,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> 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;
}
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -82,9 +83,10 @@ public FastJarBuilder(CurateOutcomeBuildItem curateOutcome,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> parentFirstArtifactKeys,
Set<ArtifactKey> 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;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,9 +37,10 @@ public LegacyThinJarBuilder(CurateOutcomeBuildItem curateOutcome,
List<GeneratedClassBuildItem> generatedClasses,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -46,10 +47,11 @@ public NativeImageSourceJarBuilder(CurateOutcomeBuildItem curateOutcome,
List<GeneratedResourceBuildItem> generatedResources,
List<GeneratedNativeImageClassBuildItem> nativeImageResources,
Set<ArtifactKey> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -73,9 +74,10 @@ public UberJarBuilder(CurateOutcomeBuildItem curateOutcome,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> removedArtifactKeys,
List<UberJarMergedResourceBuildItem> mergedResources,
List<UberJarIgnoredResourceBuildItem> ignoredResources) {
List<UberJarIgnoredResourceBuildItem> ignoredResources,
ResolvedJVMRequirements jvmRequirements) {
super(curateOutcome, outputTarget, applicationInfo, packageConfig, mainClass, applicationArchives, transformedClasses,
generatedClasses, generatedResources, removedArtifactKeys);
generatedClasses, generatedResources, removedArtifactKeys, jvmRequirements);

this.mergedResources = mergedResources;
this.ignoredResources = ignoredResources;
Expand Down Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -152,7 +156,8 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem
generatedResources,
parentFirstArtifactKeys,
removedArtifactKeys,
buildExecutor).build();
buildExecutor,
jvmRequirements).build();
};
}

Expand All @@ -171,7 +176,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem
List<GeneratedResourceBuildItem> generatedResources,
MainClassBuildItem mainClassBuildItem,
ClassLoadingConfig classLoadingConfig,
ExecutorService buildExecutor) throws Exception {
ExecutorService buildExecutor,
ResolvedJVMRequirements jvmRequirements) throws Exception {

return new NativeImageSourceJarBuilder(curateOutcomeBuildItem,
outputTargetBuildItem,
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ModuleOpenBuildItem> addOpens) {
return new ResolvedJVMRequirements(addOpens);
}
}