Skip to content

Commit

Permalink
Adapt locales support for GraalVM >= 24.2
Browse files Browse the repository at this point in the history
Starting with GraalVM for JDK 24 (24.2) native image will no longer set
the locale default at build time. As a result, the default locale won't
be included by default in the native image unless explicitly specified.

See oracle/graal#9694

# Conflicts:
#	core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java
#	integration-tests/locales/some/src/test/java/io/quarkus/locales/it/LocalesIT.java
#	test-framework/junit5/src/main/java/io/quarkus/test/junit/GraalVMVersion.java
  • Loading branch information
zakkak committed Oct 3, 2024
1 parent e2c1d31 commit ede2be2
Show file tree
Hide file tree
Showing 18 changed files with 271 additions and 18 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 @@ -196,6 +196,7 @@ public static final class Version implements Comparable<Version> {
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_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 @@ -747,6 +747,14 @@ public NativeImageInvokerInfo build() {
}
}

if (!Locale.getDefault().equals(localesBuildTimeConfig.defaultLocale)
&& graalVMVersion.compareTo(GraalVM.Version.VERSION_24_2_0) >= 0) {
log.warn(
"Your application is setting the 'quarkus.default-locale' configuration key. " +
"Starting with GraalVM/Mandrel for JDK 24 this configuration is being ignored and the " +
"default locale is always set at runtime based on the system default locale.");
}

final String userLanguage = LocaleProcessor.nativeImageUserLanguage(nativeConfig, localesBuildTimeConfig);
if (!userLanguage.isEmpty()) {
nativeImageArgs.add("-J-Duser.language=" + userLanguage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,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 +139,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.
// 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 {
additionalLocales.add(localesBuildTimeConfig.defaultLocale);
}

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 @@ -32,8 +32,8 @@ public class LocalesBuildTimeConfig {
* A special string "all" is translated as ROOT Locale and then used in native-image
* to include all locales. Image size penalty applies.
*/
@ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-"
+ DEFAULT_COUNTRY, defaultValueDocumentation = "Set containing the build system locale")
@ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY,
defaultValueDocumentation = "Set containing the build system locale")
public Set<Locale> locales;

/**
Expand All @@ -44,8 +44,11 @@ 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 this option will not result in setting the default runtime locale.
* Instead, the Java configuration {@code user.language} and {@code user.country} will be used as the
* default when running the native image.
*/
@ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY, defaultValueDocumentation = "Build system locale")
@ConfigItem(defaultValue = DEFAULT_LANGUAGE + "-" + DEFAULT_COUNTRY,
defaultValueDocumentation = "Build system locale prior to GraalVM for JDK 24, or system default locale at image runtime with GraalVM for JDK 24 and better")
public Locale defaultLocale;
}
97 changes: 97 additions & 0 deletions integration-tests/locales/default/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-locales-parent</artifactId>
<version>999-SNAPSHOT</version>
</parent>
<artifactId>quarkus-integration-test-locales-default</artifactId>
<name>Quarkus - Integration Tests - Locales - Default</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-integration-test-locales-app</artifactId>
<version>${project.version}</version>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>

<!-- Minimal test dependencies to *-deployment artifacts for consistent build order -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.locales.it;

import jakarta.validation.constraints.Pattern;
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 org.jboss.logging.Logger;

@Path("")
public class DefaultLocaleResource extends LocalesResource {
private static final Logger LOG = Logger.getLogger(DefaultLocaleResource.class);

// @Pattern validation does nothing when placed in LocalesResource.
@GET
@Path("/hibernate-validator-test-validation-message-locale/{id}/")
@Produces(MediaType.TEXT_PLAIN)
public Response validationMessageLocale(
@Pattern(regexp = "A.*", message = "{pattern.message}") @PathParam("id") String id) {
LOG.infof("Triggering test: id: %s", id);
return Response.ok(id).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.quarkus.locales.it;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

import org.apache.http.HttpStatus;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan;
import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan;
import io.quarkus.test.junit.GraalVMVersion;
import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.restassured.RestAssured;

/**
* For the Native test cases to function, the operating system has to have locales support installed. A barebone system with
* only C.UTF-8 default locale available won't be able to pass the tests.
* <p>
* For example, this package satisfies the dependency on a RHEL 9 type of OS: glibc-all-langpacks
*/
@QuarkusIntegrationTest
public class LocalesIT {

private static final Logger LOG = Logger.getLogger(LocalesIT.class);

@Test
@DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
public void testDefaultLocaleBefore24_2() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
.statusCode(HttpStatus.SC_OK)
/*
* "l-Iżvizzera" is the correct name for Switzerland in Maltese language.
* Maltese is the default language as per quarkus.default-locale=mt-MT.
*/
.body(is("l-Iżvizzera"))
.log().all();
}

@Test
@DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
public void testDefaultLocaleAfter24_1() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
.statusCode(HttpStatus.SC_OK)
/*
* "l-Iżvizzera" is the correct name for Switzerland in Maltese language.
* Maltese is the default build-time language as per quarkus.default-locale=mt-MT, but not at run-time.
* Note that this test will fail if the default run-time language is Maltese on the test machine,
* this is unfortunate but also unlikely given the small population of Malta.
*/
.body(not("l-Iżvizzera"))
.log().all();
}

/**
* @see integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java
*/
@ParameterizedTest
@CsvSource(value = {
// French locale is included, so it's used, because Croatian locale is not included
// and thus its property file ValidationMessages_hr_HR.properties is ignored.
"en-US;q=0.25,hr-HR;q=0.9,fr-FR;q=0.5,uk-UA;q=0.1|La valeur ne correspond pas à l'échantillon",
// Silent fallback to lingua franca.
"invalid string|Value is not in line with the pattern",
// French locale is available and included.
"en-US;q=0.25,hr-HR;q=1,fr-FR;q=0.5|La valeur ne correspond pas à l'échantillon"
}, delimiter = '|')
public void testValidationMessageLocale(String acceptLanguage, String expectedMessage) {
RestAssured.given()
.header("Accept-Language", acceptLanguage)
.when()
.get("/hibernate-validator-test-validation-message-locale/1")
.then()
.body(containsString(expectedMessage));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.locales.it;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class LocalesTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=Value is not in line with the pattern
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=La valeur ne correspond pas à l'échantillon
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pattern.message=Vrijednost ne zadovoljava uzorak
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
quarkus.locales=de,fr-FR,ja,uk-UA
# Note that quarkus.native.user-language is deprecated and solely quarkus.default-locale should be
# used in your application properties. This test uses it only to verify compatibility.
quarkus.default-locale=mt-MT
1 change: 1 addition & 0 deletions integration-tests/locales/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<modules>
<module>app</module>
<module>all</module>
<module>default</module>
<module>some</module>
</modules>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.junit.jupiter.params.provider.CsvSource;

import io.quarkus.test.junit.DisableIfBuiltWithGraalVMNewerThan;
import io.quarkus.test.junit.DisableIfBuiltWithGraalVMOlderThan;
import io.quarkus.test.junit.GraalVMVersion;
import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.restassured.RestAssured;
Expand Down Expand Up @@ -82,11 +83,9 @@ public void testTimeZones(String zone, String language, String name) {
.log().all();
}

// Disable test with GraalVM 24.2 for JDK 24 and later till we reach a conclusion in
// https://github.com/quarkusio/quarkus/discussions/43533
@Test
@DisableIfBuiltWithGraalVMNewerThan(value = GraalVMVersion.GRAALVM_24_1_0)
public void testDefaultLocale() {
public void testDefaultLocaleBefore24_2() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
Expand All @@ -99,6 +98,21 @@ public void testDefaultLocale() {
.log().all();
}

@Test
@DisableIfBuiltWithGraalVMOlderThan(value = GraalVMVersion.GRAALVM_24_2_0)
public void testDefaultLocaleAfter24_1() {
RestAssured.given().when()
.get("/default/de-CH")
.then()
.statusCode(HttpStatus.SC_OK)
/*
* "Schweiz" is the correct name for Switzerland in German.
* German is the default language as per the `quarkus.test.arg-line` in application.properties.
*/
.body(is("Schweiz"))
.log().all();
}

@Test
public void testMissingLocaleSorryItaly() {
RestAssured.given().when()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ quarkus.locales=de,fr-FR,ja,uk-UA
# used in your application properties. This test uses it only to verify compatibility.
quarkus.native.user-language=cs
quarkus.default-locale=en-US
quarkus.test.arg-line=-Duser.language=de
Loading

0 comments on commit ede2be2

Please sign in to comment.