Skip to content

Commit

Permalink
Merge pull request quarkusio#43448 from zakkak/2024-09-23-fix-43436
Browse files Browse the repository at this point in the history
Adapt locales support for GraalVM >= 24.2
  • Loading branch information
gsmet authored Oct 23, 2024
2 parents 43bd76f + 7f39ccc commit c814b75
Show file tree
Hide file tree
Showing 25 changed files with 361 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
{
"category": "Misc2",
"timeout": 75,
"test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some",
"test-modules": "hibernate-validator, test-extension/tests, logging-gelf, mailer, native-config-profile, locales/all, locales/some, locales/default",
"os-name": "ubuntu-latest"
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public interface NativeConfig {

/**
* Defines the user language used for building the native executable.
* It also serves as the default Locale language for the native executable application runtime.
* With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale language for the native executable
* application runtime.
* e.g. en or cs as defined by IETF BCP 47 language tags.
* <p>
*
Expand All @@ -100,7 +101,8 @@ public interface NativeConfig {

/**
* Defines the user country used for building the native executable.
* It also serves as the default Locale country for the native executable application runtime.
* With GraalVM versions prior to GraalVM for JDK 24 it also serves as the default Locale country for the native executable
* application runtime.
* e.g. US or FR as defined by ISO 3166-1 alpha-2 codes.
* <p>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ public static final class Version extends io.quarkus.runtime.graal.GraalVM.Versi
public static final Version VERSION_24_0_0 = new Version("GraalVM 24.0.0", "24.0.0", "22", Distribution.GRAALVM);
public static final Version VERSION_24_0_999 = new Version("GraalVM 24.0.999", "24.0.999", "22", Distribution.GRAALVM);
public static final Version VERSION_24_1_0 = new Version("GraalVM 24.1.0", "24.1.0", "23", Distribution.GRAALVM);
public static final Version VERSION_24_1_999 = new Version("GraalVM 24.1.999", "24.1.999", "23", Distribution.GRAALVM);
public static final Version VERSION_24_2_0 = new Version("GraalVM 24.2.0", "24.2.0", "24", Distribution.GRAALVM);

/**
* The minimum version of GraalVM supported by Quarkus.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -746,15 +746,6 @@ public NativeImageInvokerInfo build() {
}
}
}

final String userLanguage = LocaleProcessor.nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
if (!userLanguage.isEmpty()) {
nativeImageArgs.add("-J-Duser.language=" + userLanguage);
}
final String userCountry = LocaleProcessor.nativeImageUserCountry(nativeConfig, localesBuildTimeConfig);
if (!userCountry.isEmpty()) {
nativeImageArgs.add("-J-Duser.country=" + userCountry);
}
final String includeLocales = LocaleProcessor.nativeImageIncludeLocales(nativeConfig, localesBuildTimeConfig);
if (!includeLocales.isEmpty()) {
if ("all".equals(includeLocales)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.NativeConfig;
import io.quarkus.deployment.pkg.steps.NativeBuild;
Expand Down Expand Up @@ -59,6 +60,23 @@ void servicesResource(BuildProducer<NativeImageResourceBuildItem> nativeImageRes
"sun.util.resources.provider.LocaleDataProvider".getBytes(StandardCharsets.UTF_8)));
}

/**
* These exports are only required for GraalVM for JDK < 24, but don't cause any issues for newer versions.
* To be removed once we drop support for GraalVM for JDK < 24.
*/
@BuildStep(onlyIf = NativeBuild.class)
void setDefaults(BuildProducer<NativeImageSystemPropertyBuildItem> buildtimeSystemProperties,
NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
String language = nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
if (!language.isEmpty()) {
buildtimeSystemProperties.produce(new NativeImageSystemPropertyBuildItem("user.language", language));
}
String country = nativeImageUserCountry(nativeConfig, localesBuildTimeConfig);
if (!country.isEmpty()) {
buildtimeSystemProperties.produce(new NativeImageSystemPropertyBuildItem("user.country", country));
}
}

/**
* We activate additional resources in native-image executable only if user opts
* for anything else than what is already the system default.
Expand All @@ -80,7 +98,8 @@ public boolean getAsBoolean() {
(nativeConfig.userCountry().isPresent()
&& !Locale.getDefault().getCountry().equals(nativeConfig.userCountry().get()))
||
!Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale)
(localesBuildTimeConfig.defaultLocale.isPresent() &&
!Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale.get()))
||
localesBuildTimeConfig.locales.stream().anyMatch(l -> !Locale.getDefault().equals(l));
}
Expand All @@ -93,9 +112,14 @@ public boolean getAsBoolean() {
* @param localesBuildTimeConfig
* @return User language set by 'quarkus.default-locale' or by deprecated 'quarkus.native.user-language' or
* effectively LocalesBuildTimeConfig.DEFAULT_LANGUAGE if none of the aforementioned is set.
* @Deprecated
*/
@Deprecated
public static String nativeImageUserLanguage(NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
String language = localesBuildTimeConfig.defaultLocale.getLanguage();
String language = System.getProperty("user.language", "en");
if (localesBuildTimeConfig.defaultLocale.isPresent()) {
language = localesBuildTimeConfig.defaultLocale.get().getLanguage();
}
if (nativeConfig.userLanguage().isPresent()) {
log.warn(DEPRECATED_USER_LANGUAGE_WARNING);
// The deprecated option takes precedence for users who are already using it.
Expand All @@ -112,9 +136,14 @@ public static String nativeImageUserLanguage(NativeConfig nativeConfig, LocalesB
* @return User country set by 'quarkus.default-locale' or by deprecated 'quarkus.native.user-country' or
* effectively LocalesBuildTimeConfig.DEFAULT_COUNTRY (could be an empty string) if none of the aforementioned is
* set.
* @Deprecated
*/
@Deprecated
public static String nativeImageUserCountry(NativeConfig nativeConfig, LocalesBuildTimeConfig localesBuildTimeConfig) {
String country = localesBuildTimeConfig.defaultLocale.getCountry();
String country = System.getProperty("user.country", "");
if (localesBuildTimeConfig.defaultLocale.isPresent()) {
country = localesBuildTimeConfig.defaultLocale.get().getCountry();
}
if (nativeConfig.userCountry().isPresent()) {
log.warn(DEPRECATED_USER_COUNTRY_WARNING);
// The deprecated option takes precedence for users who are already using it.
Expand All @@ -124,7 +153,7 @@ public static String nativeImageUserCountry(NativeConfig nativeConfig, LocalesBu
}

/**
* Additional locales to be included in native-image executable.
* Locales to be included in native-image executable.
*
* @param nativeConfig
* @param localesBuildTimeConfig
Expand All @@ -139,17 +168,18 @@ public static String nativeImageIncludeLocales(NativeConfig nativeConfig, Locale
return "all";
}

// We subtract what we already declare for native-image's user.language or user.country.
// Note the deprecated options still count.
additionalLocales.remove(localesBuildTimeConfig.defaultLocale);
// GraalVM for JDK 24 doesn't include the default locale used at build time. We must explicitly include the
// specified locales - including the build-time locale if set by the user.
// Note the deprecated options still count and take precedence.
if (nativeConfig.userCountry().isPresent() && nativeConfig.userLanguage().isPresent()) {
additionalLocales.remove(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get()));
additionalLocales.add(new Locale(nativeConfig.userLanguage().get(), nativeConfig.userCountry().get()));
} else if (nativeConfig.userLanguage().isPresent()) {
additionalLocales.remove(new Locale(nativeConfig.userLanguage().get()));
additionalLocales.add(new Locale(nativeConfig.userLanguage().get()));
} else if (localesBuildTimeConfig.defaultLocale.isPresent()) {
additionalLocales.add(localesBuildTimeConfig.defaultLocale.get());
}

return additionalLocales.stream()
.filter(l -> !Locale.getDefault().equals(l))
.map(l -> l.getLanguage() + (l.getCountry().isEmpty() ? "" : "-" + l.getCountry()))
.collect(Collectors.joining(","));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,9 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
createAppCDS.returnValue(null);
}

// very first thing is to set system props (for run time, which use substitutions for a different
// storage from build-time)
// Make sure we set properties in doStartup as well. This is necessary because setting them in the static-init
// sets them at build-time, on the host JVM, while SVM has substitutions for System. get/ setProperty at
// run-time which will never see those properties unless we also set them at run-time.
for (SystemPropertyBuildItem i : properties) {
mv.invokeStaticMethod(ofMethod(System.class, "setProperty", String.class, String.class, String.class),
mv.load(i.getKey()), mv.load(i.getValue()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
import org.graalvm.nativeimage.hosted.RuntimeSystemProperties;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
Expand All @@ -18,13 +19,17 @@
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.UnsafeAccessedFieldBuildItem;
import io.quarkus.deployment.pkg.NativeConfig;
import io.quarkus.gizmo.BranchResult;
import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.gizmo.TryBlock;
import io.quarkus.runtime.LocalesBuildTimeConfig;
import io.quarkus.runtime.graal.GraalVM;

public class NativeImageFeatureStep {
Expand All @@ -35,6 +40,12 @@ public class NativeImageFeatureStep {
Class.class);
private static final MethodDescriptor BUILD_TIME_INITIALIZATION = ofMethod(RuntimeClassInitialization.class,
"initializeAtBuildTime", void.class, String[].class);
private static final MethodDescriptor REGISTER_RUNTIME_SYSTEM_PROPERTIES = ofMethod(RuntimeSystemProperties.class,
"register", void.class, String.class, String.class);
private static final MethodDescriptor GRAALVM_VERSION_GET_CURRENT = ofMethod(GraalVM.Version.class, "getCurrent",
GraalVM.Version.class);
private static final MethodDescriptor GRAALVM_VERSION_COMPARE_TO = ofMethod(GraalVM.Version.class, "compareTo", int.class,
int[].class);
private static final MethodDescriptor INITIALIZE_CLASSES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class,
"initializeAtRunTime", void.class, Class[].class);
private static final MethodDescriptor INITIALIZE_PACKAGES_AT_RUN_TIME = ofMethod(RuntimeClassInitialization.class,
Expand All @@ -58,11 +69,12 @@ void addExportsToNativeImage(BuildProducer<JPMSExportBuildItem> features) {

@BuildStep
void generateFeature(BuildProducer<GeneratedNativeImageClassBuildItem> nativeImageClass,
BuildProducer<JPMSExportBuildItem> exports,
List<RuntimeInitializedClassBuildItem> runtimeInitializedClassBuildItems,
List<RuntimeInitializedPackageBuildItem> runtimeInitializedPackageBuildItems,
List<RuntimeReinitializedClassBuildItem> runtimeReinitializedClassBuildItems,
List<UnsafeAccessedFieldBuildItem> unsafeAccessedFields) {
List<UnsafeAccessedFieldBuildItem> unsafeAccessedFields,
NativeConfig nativeConfig,
LocalesBuildTimeConfig localesBuildTimeConfig) {
ClassCreator file = new ClassCreator(new ClassOutput() {
@Override
public void write(String s, byte[] bytes) {
Expand All @@ -81,6 +93,38 @@ public void write(String s, byte[] bytes) {
overallCatch.invokeStaticMethod(BUILD_TIME_INITIALIZATION,
overallCatch.marshalAsArray(String.class, overallCatch.load(""))); // empty string means initialize everything

// Set the user.language and user.country system properties to the default locale
// The deprecated option takes precedence for users who are already using it.
if (nativeConfig.userLanguage().isPresent()) {
overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
overallCatch.load("user.language"), overallCatch.load(nativeConfig.userLanguage().get()));
if (nativeConfig.userCountry().isPresent()) {
overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
overallCatch.load("user.country"), overallCatch.load(nativeConfig.userCountry().get()));
}
} else if (localesBuildTimeConfig.defaultLocale.isPresent()) {
overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
overallCatch.load("user.language"),
overallCatch.load(localesBuildTimeConfig.defaultLocale.get().getLanguage()));
overallCatch.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
overallCatch.load("user.country"),
overallCatch.load(localesBuildTimeConfig.defaultLocale.get().getCountry()));
} else {
ResultHandle graalVMVersion = overallCatch.invokeStaticMethod(GRAALVM_VERSION_GET_CURRENT);
BranchResult graalVm24_2Test = overallCatch
.ifGreaterEqualZero(overallCatch.invokeVirtualMethod(GRAALVM_VERSION_COMPARE_TO, graalVMVersion,
overallCatch.marshalAsArray(int.class, overallCatch.load(24), overallCatch.load(2))));
/* GraalVM >= 24.2 */
try (BytecodeCreator greaterEqual24_2 = graalVm24_2Test.trueBranch()) {
greaterEqual24_2.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
greaterEqual24_2.load("user.language"),
greaterEqual24_2.load("en"));
greaterEqual24_2.invokeStaticMethod(REGISTER_RUNTIME_SYSTEM_PROPERTIES,
greaterEqual24_2.load("user.country"),
greaterEqual24_2.load("US"));
}
}

if (!runtimeInitializedClassBuildItems.isEmpty()) {
// Class[] runtimeInitializedClasses()
MethodCreator runtimeInitializedClasses = file
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.quarkus.runtime;

import java.util.Locale;
import java.util.Optional;
import java.util.Set;

import io.quarkus.runtime.annotations.ConfigDocPrefix;
Expand Down Expand Up @@ -44,8 +45,10 @@ public class LocalesBuildTimeConfig {
* For instance, the Hibernate Validator extension makes use of it.
* <p>
* Native-image build uses this property to derive {@code user.language} and {@code user.country} for the application's
* runtime.
* runtime. Starting with GraalVM for JDK 24 {@code user.language} and {@code user.country} can also be overridden at
* runtime, provided the selected locale was included at image build time.
*/
@ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, defaultValueDocumentation = "Build system locale")
public Locale defaultLocale;
@ConfigItem(defaultValueDocumentation = "Defaults to the JVM's default locale if not set. "
+ "Starting with GraalVM for JDK 24, it defaults to en-US for native executables.")
public Optional<Locale> defaultLocale;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void created(BeanContainer container) {
// Locales, Locale ROOT means all locales in this setting.
.locales(localesBuildTimeConfig.locales.contains(Locale.ROOT) ? Set.of(Locale.getAvailableLocales())
: localesBuildTimeConfig.locales)
.defaultLocale(localesBuildTimeConfig.defaultLocale)
.defaultLocale(localesBuildTimeConfig.defaultLocale.orElse(Locale.getDefault()))
.beanMetaDataClassNormalizer(new ArcProxyBeanMetaDataClassNormalizer());

if (hibernateValidatorBuildTimeConfig.expressionLanguage().constraintExpressionFeatureLevel().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
Expand Down Expand Up @@ -1417,7 +1418,7 @@ private String getDefaultLocale(AnnotationInstance bundleAnnotation, LocalesBuil
AnnotationValue localeValue = bundleAnnotation.value(BUNDLE_LOCALE);
String defaultLocale;
if (localeValue == null || localeValue.asString().equals(MessageBundle.DEFAULT_LOCALE)) {
defaultLocale = locales.defaultLocale.toLanguageTag();
defaultLocale = locales.defaultLocale.orElse(Locale.getDefault()).toLanguageTag();
} else {
defaultLocale = localeValue.asString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig
this.templateContents = Map.copyOf(context.getTemplateContents());
this.tags = context.getTags();
this.templatePathExclude = config.templatePathExclude;
this.defaultLocale = locales.defaultLocale;
this.defaultLocale = locales.defaultLocale.orElse(Locale.getDefault());
this.defaultCharset = config.defaultCharset;
this.container = Arc.container();

Expand Down
Loading

0 comments on commit c814b75

Please sign in to comment.