Skip to content

Commit 3a4a310

Browse files
committed
Fix GraalVM version parsing for JDK 26-based builds
JDK 26+ based GraalVM builds won't have a version mapping any more. They use the JDK version scheme instead. Closes: #48403
1 parent da9f88e commit 3a4a310

File tree

3 files changed

+125
-11
lines changed

3 files changed

+125
-11
lines changed

core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/GraalVM.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ static final class VersionParseHelper {
2828
private static final String OPT = "(?:-(?<OPT>[-a-zA-Z0-9.]+))?";
2929
private static final String VSTR_FORMAT = VNUM + PRE + BUILD + OPT;
3030

31-
private static final String VNUM_GROUP = "VNUM";
3231
private static final String VENDOR_VERSION_GROUP = "VENDOR";
3332
private static final String BUILD_INFO_GROUP = "BUILDINFO";
3433

@@ -67,7 +66,7 @@ static Version parse(List<String> lines) {
6766

6867
String vendorVersion = secondMatcher.group(VENDOR_VERSION_GROUP);
6968

70-
String graalVersion = graalVersion(javaVersion, v.feature());
69+
String graalVersion = graalVersion(javaVersion, v);
7170
if (vendorVersion.contains("-dev")) {
7271
graalVersion = graalVersion + "-dev";
7372
}
@@ -137,7 +136,7 @@ private static String matchVersion(String version) {
137136
return null;
138137
}
139138

140-
private static String graalVersion(String buildInfo, int jdkFeature) {
139+
private static String graalVersion(String buildInfo, Runtime.Version v) {
141140
if (buildInfo == null) {
142141
return null;
143142
}
@@ -152,7 +151,10 @@ private static String graalVersion(String buildInfo, int jdkFeature) {
152151
if (versMatcher.find()) {
153152
return matchVersion(version);
154153
} else {
155-
return Version.GRAAL_MAPPING.get(Integer.toString(jdkFeature));
154+
// Only versions from JDK 22 to JDK 25 had GraalVM version mappings.
155+
// Use the JDK version triplet for JDK N where N > 25.
156+
String fullJDKVersion = String.format("%d.%d.%d", v.feature(), v.interim(), v.update());
157+
return Version.GRAAL_MAPPING.getOrDefault(Integer.toString(v.feature()), fullJDKVersion);
156158
}
157159
}
158160

core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/GraalVMTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.util.stream.Stream;
99

10+
import org.junit.jupiter.api.AfterAll;
1011
import org.junit.jupiter.api.Assertions;
1112
import org.junit.jupiter.api.Test;
1213
import org.junit.jupiter.params.ParameterizedTest;
@@ -18,8 +19,21 @@
1819

1920
public class GraalVMTest {
2021

22+
public static final String GRAALVM_VENDOR_VERSION_PROP = "org.graalvm.vendorversion";
23+
24+
@AfterAll
25+
public static void tearDownAfterAll() {
26+
System.clearProperty(GRAALVM_VENDOR_VERSION_PROP);
27+
}
28+
2129
@Test
2230
public void testGraalVMVersionDetected() {
31+
// Version detection after JDK 25 which no longer uses a GraalVM version mapping,
32+
// but uses the JDK version instead
33+
assertVersion(new Version("GraalVM 26.0.0", "26.0.0", GRAALVM), GRAALVM,
34+
Version.of(Stream.of(("native-image 26 2026-03-17\n"
35+
+ "GraalVM Runtime Environment GraalVM CE 26-dev+1.1 (build 26+1-jvmci-b01)\n"
36+
+ "Substrate VM GraalVM CE 26-dev+1.1 (build 26+1, serial gc)").split("\\n"))));
2337
// Version detection after: https://github.com/oracle/graal/pull/6302 (3 lines of version output)
2438
assertVersion(new Version("GraalVM 23.0.0", "23.0.0", GRAALVM), MANDREL,
2539
Version.of(Stream.of(("native-image 17.0.6 2023-01-17\n"
@@ -168,6 +182,19 @@ public void testGraalVM23_1CommunityVersionParser() {
168182
assertThat(version.javaVersion.update()).isEqualTo(5);
169183
}
170184

185+
@Test
186+
public void testGraalVM26CommunityVersionParser() {
187+
final Version version = Version.of(Stream.of(("native-image 26 2026-03-17\n"
188+
+ "GraalVM Runtime Environment GraalVM CE 26-dev+1.1 (build 26+1-jvmci-b01)\n"
189+
+ "Substrate VM GraalVM CE 26-dev+1.1 (build 26+1, serial gc)").split("\\n")));
190+
assertThat(version.toString().contains(GRAALVM.name()));
191+
assertThat(version.getVersionAsString()).isEqualTo("26.0.0-dev");
192+
assertThat(version.javaVersion.toString()).isEqualTo("26+1-jvmci-b01");
193+
assertThat(version.javaVersion.feature()).isEqualTo(26);
194+
assertThat(version.javaVersion.interim()).isEqualTo(0);
195+
assertThat(version.javaVersion.update()).isEqualTo(0);
196+
}
197+
171198
@Test
172199
public void testGraalVMVersionsOlderThan() {
173200
assertOlderThan("native-image 21 2023-09-19\n" +
@@ -184,6 +211,40 @@ public void testGraalVMVersionsOlderThan() {
184211
"Substrate VM GraalVM CE 21+35.1 (build 21+35, serial gc)\n");
185212
}
186213

214+
/*
215+
* Exercise the code path used at native-image build time where the org.graalvm.vendorversion
216+
* property is being fed to the GraalVM version parsing machinery.
217+
*/
218+
@ParameterizedTest
219+
// @formatter:off
220+
@ValueSource(strings = {
221+
// GraalVM CE/Community
222+
"GraalVM CE 26-dev+1.1 |26.0|26|0",
223+
"GraalVM CE 25-dev+26.1|25.0|25|0",
224+
"GraalVM CE 24.0.1+9.1 |24.2|24|1",
225+
// Mandrel
226+
"Mandrel-24.2.1.0-Final|24.2|24|1",
227+
"Mandrel-23.1.7.0-1b2 |23.1|21|7",
228+
// Liberica-NIK
229+
"Liberica-NIK-23.1.7-1 |23.1|21|7",
230+
"Liberica-NIK-24.2.1-1 |24.2|24|1",
231+
})
232+
// @formatter:on
233+
public void testGraalVMRuntimeVersion(String param) {
234+
String tokens[] = param.split("\\|", 4);
235+
Assertions.assertTrue(tokens.length == 4);
236+
String propertyVal = tokens[0].trim();
237+
String expectedMajorMinor = tokens[1].trim();
238+
int expectedJDKFeature = Integer.parseInt(tokens[2].trim());
239+
int expectedJDKUpdate = Integer.parseInt(tokens[3].trim());
240+
241+
System.setProperty(GRAALVM_VENDOR_VERSION_PROP, propertyVal);
242+
io.quarkus.runtime.graal.GraalVM.Version v = io.quarkus.runtime.graal.GraalVM.Version.getCurrent();
243+
Assertions.assertEquals(expectedMajorMinor, v.getMajorMinorAsString());
244+
Assertions.assertEquals(expectedJDKFeature, v.javaVersion.feature());
245+
Assertions.assertEquals(expectedJDKUpdate, v.javaVersion.update());
246+
}
247+
187248
/**
188249
* Asserts that one version is older than the other.
189250
*/

core/runtime/src/main/java/io/quarkus/runtime/graal/GraalVM.java

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.quarkus.runtime.graal;
22

33
import java.util.Arrays;
4+
import java.util.Collections;
5+
import java.util.HashMap;
46
import java.util.Map;
7+
import java.util.Map.Entry;
58
import java.util.regex.Matcher;
69
import java.util.regex.Pattern;
710
import java.util.stream.Collectors;
@@ -40,16 +43,23 @@ static final class VersionParseHelper {
4043

4144
private static final String VERSION_GROUP = "VNUM";
4245

43-
private static final Version UNKNOWN_VERSION = null;
44-
4546
static Version parse(String value) {
4647
Matcher versionMatcher = VENDOR_VERS_PATTERN.matcher(value);
4748
if (versionMatcher.find()) {
4849
String vendor = versionMatcher.group(VENDOR_PREFIX_GROUP);
4950
if (GRAALVM_CE_VERS_PREFIX.equals(vendor) || ORACLE_GRAALVM_VERS_PREFIX.equals(vendor)) {
5051
String version = versionMatcher.group(VERSION_GROUP);
51-
String jdkFeature = version.split("\\.", 2)[0];
52-
return new Version(value, Version.GRAAL_MAPPING.get(jdkFeature), Distribution.GRAALVM);
52+
String tokens[] = version.split("\\.", 3);
53+
String jdkFeature = tokens[0];
54+
String jdkVers = jdkFeature;
55+
if (tokens.length == 3) {
56+
String interim = tokens[1];
57+
String update = tokens[2].split("\\+")[0];
58+
jdkVers = String.format("%s.%s.%s", jdkFeature, interim, update);
59+
}
60+
// For JDK 26+ there is no more version mapping use the JDK version
61+
String versionMapping = Version.GRAAL_MAPPING.getOrDefault(jdkFeature, version);
62+
return new Version(value, versionMapping, jdkVers, Distribution.GRAALVM);
5363
} else if (LIBERICA_NIK_VERS_PREFIX.equals(vendor)) {
5464
return new Version(value, versionMatcher.group(VERSION_GROUP), Distribution.LIBERICA);
5565
} else if (MANDREL_VERS_PREFIX.equals(vendor)) {
@@ -78,8 +88,18 @@ public static class Version implements Comparable<Version> {
7888
"21", "23.1",
7989
"22", "24.0",
8090
"23", "24.1",
81-
"24", "24.2",
82-
"25", "25.0");
91+
"24", "24.2");
92+
// Mapping of community major.minor pair to the JDK major version based on
93+
// GRAAL_MAPPING
94+
private static final Map<String, String> MANDREL_JDK_REV_MAP;
95+
96+
static {
97+
Map<String, String> reverseMap = new HashMap<>(GRAAL_MAPPING.size());
98+
for (Entry<String, String> entry : GRAAL_MAPPING.entrySet()) {
99+
reverseMap.put(entry.getValue(), entry.getKey());
100+
}
101+
MANDREL_JDK_REV_MAP = Collections.unmodifiableMap(reverseMap);
102+
}
83103

84104
/**
85105
* The minimum version of GraalVM supported by Quarkus.
@@ -97,14 +117,18 @@ public static class Version implements Comparable<Version> {
97117
*/
98118
public static final Version MINIMUM_SUPPORTED = CURRENT;
99119

120+
private static final String DEFAULT_JDK_VERSION = "21";
100121
protected final String fullVersion;
101122
public final Runtime.Version javaVersion;
102123
protected final Distribution distribution;
103124
private int[] versions;
104125
private String suffix;
105126

106127
Version(String fullVersion, String version, Distribution distro) {
107-
this(fullVersion, version, "21", distro);
128+
this(fullVersion, version,
129+
distro == Distribution.MANDREL || distro == Distribution.LIBERICA ? communityJDKvers(version)
130+
: DEFAULT_JDK_VERSION,
131+
distro);
108132
}
109133

110134
Version(String fullVersion, String version, String javaVersion, Distribution distro) {
@@ -127,6 +151,33 @@ private void breakdownVersion(String version) {
127151
this.versions = Arrays.stream(version.split("\\.")).mapToInt(Integer::parseInt).toArray();
128152
}
129153

154+
/*
155+
* Reconstruct the JDK version from the given GraalVM community version (Mandrel or Liberica)
156+
*/
157+
private static String communityJDKvers(String communityVersion) {
158+
try {
159+
String[] parts = communityVersion.split("\\.", 4);
160+
int major = Integer.parseInt(parts[0]);
161+
int minor = Integer.parseInt(parts[1]);
162+
if ((major == 23 && minor > 0) ||
163+
major > 23) {
164+
String mandrelMajorMinor = String.format("%s.%s", parts[0], parts[1]);
165+
// If we don't find a reverse mapping we use a JDK version >= 25, thus
166+
// the feature version is the first part of the quadruple.
167+
String feature = MANDREL_JDK_REV_MAP.getOrDefault(mandrelMajorMinor, parts[0]);
168+
// Heuristic: The update version of Mandrel and the JDK match.
169+
// Interim is usually 0 for the JDK version.
170+
return String.format("%s.%s.%s", feature, "0", parts[2]);
171+
}
172+
} catch (Throwable e) {
173+
// fall-through do default
174+
Log.warnf("Failed to parse JDK version from GraalVM version: %s. Defaulting to currently supported version %s ",
175+
communityVersion,
176+
DEFAULT_JDK_VERSION);
177+
}
178+
return DEFAULT_JDK_VERSION;
179+
}
180+
130181
@Override
131182
public int compareTo(Version o) {
132183
return compareTo(o.versions);

0 commit comments

Comments
 (0)