Skip to content

Commit 8cfd01c

Browse files
committed
Fix reproducability of builds against Java EA versions
This fixes a reproducability issue when using the gradle javaToolChain api. There is no way to test toolchain candidates reliable against the build number in use. This meant that ones you e.g. have resolved some version of java 25, gradle toolchain detection does not detect the difference between certain builds (or even ea vs. released version) Therefore we fallback to rely on our custom JDKDownloadPlugin for now here. Syncing with the gradle team, they want to fix this somewhen in 9.x. We will revisit our solution ones something is available. Ultimately we want to get rid of usages of the JDK download plugin.
1 parent b6c96c7 commit 8cfd01c

File tree

18 files changed

+232
-227
lines changed

18 files changed

+232
-227
lines changed

BUILDING.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ uses the changed dependencies. In most cases, `precommit` or `check` are good ca
9292
We prefer sha256 checksums as md5 and sha1 are not considered safe anymore these days. The generated entry
9393
will have the `origin` attribute been set to `Generated by Gradle`.
9494

95-
> [!Tip]
95+
> [!Tip]
9696
> A manual confirmation of the Gradle generated checksums is currently not mandatory.
9797
> If you want to add a level of verification you can manually confirm the checksum (e.g. by looking it up on the website of the library)
9898
> Please replace the content of the `origin` attribute by `official site` in that case.
@@ -186,6 +186,25 @@ dependencies {
186186

187187
To test an unreleased development version of a third party dependency you have several options.
188188

189+
### How do I test against java early access (ea) versions?
190+
191+
Currently only openjdk EA builds by oracle are supported.
192+
To test against an early access version java version you can pass the major java version
193+
to test against via system property (e.g. -Druntime.java=26):
194+
195+
```
196+
./gradlew clean test -Druntime.java=26
197+
```
198+
199+
This will run the tests using the JDK 26 EA version and pick the latest available build of the matching JDK EA version we expose
200+
in our custom jdk catalogue at `https://storage.googleapis.com/elasticsearch-jdk-archive/jdks/openjdk/latest.json`.
201+
202+
To run against a specific build number of the EA build you can pass a second system property (e.g. `-Druntime.java.build=6`):
203+
204+
```
205+
./gradlew clean test -Druntime.java=26 -Druntime.java.build=6
206+
```
207+
189208
#### How to use a Maven based third party dependency via `mavenlocal`?
190209

191210
1. Clone the third party repository locally
@@ -229,7 +248,7 @@ In addition to snapshot builds JitPack supports building Pull Requests. Simply u
229248
3. Run the Gradle build as needed. Keep in mind the initial resolution might take a bit longer as this needs to be built
230249
by JitPack in the background before we can resolve the adhoc built dependency.
231250

232-
> [!Note]
251+
> [!Note]
233252
> You should only use that approach locally or on a developer branch for production dependencies as we do
234253
not want to ship unreleased libraries into our releases.
235254

@@ -261,7 +280,7 @@ allprojects {
261280
```
262281
4. Run the Gradle build as needed with `--write-verification-metadata` to ensure the Gradle dependency verification does not fail on your custom dependency.
263282

264-
> [!Note]
283+
> [!Note]
265284
> As Gradle prefers to use modules whose descriptor has been created from real meta-data rather than being generated,
266285
flat directory repositories cannot be used to override artifacts with real meta-data from other repositories declared in the build.
267286
> For example, if Gradle finds only `jmxri-1.2.1.jar` in a flat directory repository, but `jmxri-1.2.1.pom` in another repository

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/fixtures/AbstractGitAwareGradleFuncTest.groovy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.elasticsearch.gradle.fixtures
1111

1212
import org.apache.commons.io.FileUtils
13-
import org.elasticsearch.gradle.internal.test.InternalAwareGradleRunner
1413
import org.gradle.testkit.runner.GradleRunner
1514
import org.junit.Rule
1615
import org.junit.rules.TemporaryFolder

build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/JdkDownloadPluginFuncTest.groovy

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,18 @@ class JdkDownloadPluginFuncTest extends AbstractGradleFuncTest {
3333
private static final String ADOPT_JDK_VERSION_15 = "15.0.2+7"
3434
private static final String AZUL_JDK_VERSION_8 = "8u302+b08"
3535
private static final String AZUL_8_DISTRO_VERSION = "8.56.0.23"
36+
private static final String CATALOG_EA_VERSION = "25-ea+30"
3637
private static final String OPEN_JDK_VERSION = "12.0.1+99@123456789123456789123456789abcde"
3738
private static final Pattern JDK_HOME_LOGLINE = Pattern.compile("JDK HOME: (.*)")
3839

40+
def setup() {
41+
configurationCacheCompatible = false // JDK class references configurations which break configuration cache
42+
}
43+
3944
@Unroll
40-
def "jdk #jdkVendor for #platform#suffix are downloaded and extracted"() {
45+
def "jdk #distributionVersion #jdkVendor for #platform#suffix are downloaded and extracted"() {
4146
given:
42-
def mockRepoUrl = urlPath(jdkVendor, jdkVersion, platform, arch);
47+
def mockRepoUrl = urlPath(jdkVendor, jdkVersion, platform, arch, distributionVersion);
4348
def mockedContent = filebytes(jdkVendor, platform)
4449
buildFile.text = """
4550
plugins {
@@ -77,21 +82,26 @@ class JdkDownloadPluginFuncTest extends AbstractGradleFuncTest {
7782

7883
where:
7984
platform | arch | jdkVendor | jdkVersion | distributionVersion | expectedJavaBin | suffix
80-
"linux" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | null | "bin/java" | ""
81-
"linux" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | null | "bin/java" | ""
82-
"linux" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | null | "bin/java" | "(old version)"
83-
"windows" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | null | "bin/java" | ""
84-
"windows" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | null | "bin/java" | ""
85-
"windows" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | null | "bin/java" | "(old version)"
86-
"darwin" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | null | "Contents/Home/bin/java" | ""
87-
"darwin" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | null | "Contents/Home/bin/java" | ""
88-
"darwin" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | null | "Contents/Home/bin/java" | "(old version)"
89-
"mac" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | null | "Contents/Home/bin/java" | ""
90-
"mac" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | null | "Contents/Home/bin/java" | "(old version)"
91-
"darwin" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | null | "Contents/Home/bin/java" | ""
92-
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | null | "bin/java" | ""
93-
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION_11 | null | "bin/java" | "(jdk 11)"
94-
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION_15 | null | "bin/java" | "(jdk 15)"
85+
"linux" | "aarch64" | VENDOR_OPENJDK | CATALOG_EA_VERSION | "ea" | "bin/java" | ""
86+
"linux" | "x64" | VENDOR_OPENJDK | CATALOG_EA_VERSION | "ea" | "bin/java" | ""
87+
"darwin" | "aarch64" | VENDOR_OPENJDK | CATALOG_EA_VERSION | "ea" | "Contents/Home/bin/java" | ""
88+
"darwin" | "x64" | VENDOR_OPENJDK | CATALOG_EA_VERSION | "ea" | "Contents/Home/bin/java" | ""
89+
"windows" | "x64" | VENDOR_OPENJDK | CATALOG_EA_VERSION | "ea" | "bin/java" | ""
90+
"linux" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | "" | "bin/java" | ""
91+
"linux" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | "" | "bin/java" | ""
92+
"linux" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | "" | "bin/java" | "(old version)"
93+
"windows" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | "" | "bin/java" | ""
94+
"windows" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | "" | "bin/java" | ""
95+
"windows" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | "" | "bin/java" | "(old version)"
96+
"darwin" | "x64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | "" | "Contents/Home/bin/java" | ""
97+
"darwin" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | "" | "Contents/Home/bin/java" | ""
98+
"darwin" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | "" | "Contents/Home/bin/java" | "(old version)"
99+
"mac" | "x64" | VENDOR_OPENJDK | OPEN_JDK_VERSION | "" | "Contents/Home/bin/java" | ""
100+
"mac" | "x64" | VENDOR_OPENJDK | OPENJDK_VERSION_OLD | "" | "Contents/Home/bin/java" | "(old version)"
101+
"darwin" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | "" | "Contents/Home/bin/java" | ""
102+
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION | "" | "bin/java" | ""
103+
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION_11 | "" | "bin/java" | "(jdk 11)"
104+
"linux" | "aarch64" | VENDOR_ADOPTIUM | ADOPT_JDK_VERSION_15 | "" | "bin/java" | "(jdk 15)"
95105
"darwin" | "aarch64" | VENDOR_ZULU | AZUL_JDK_VERSION_8 | AZUL_8_DISTRO_VERSION | "Contents/Home/bin/java" | "(jdk 8)"
96106
}
97107

@@ -214,13 +224,19 @@ class JdkDownloadPluginFuncTest extends AbstractGradleFuncTest {
214224
private static String urlPath(final String vendor,
215225
final String version,
216226
final String platform,
217-
final String arch = 'x64') {
218-
if (vendor.equals(VENDOR_ADOPTIUM)) {
227+
final String arch = 'x64',
228+
final String distributedVersion = '') {
229+
final boolean isOld = version.equals(OPENJDK_VERSION_OLD);
230+
231+
if (distributedVersion.equals("ea")) {
232+
def effectivePlatform = isMac(platform) ? "macos" : platform;
233+
def fileExtension = platform.toLowerCase().equals("windows") ? "zip" : "tar.gz";
234+
return "/jdks/openjdk/25/openjdk-${version}/openjdk-${version}_$effectivePlatform-${arch}_bin.$fileExtension";
235+
} else if (vendor.equals(VENDOR_ADOPTIUM)) {
219236
final String module = isMac(platform) ? "mac" : platform;
220237
return "/jdk-" + version + "/" + module + "/${arch}/jdk/hotspot/normal/adoptium";
221238
} else if (vendor.equals(VENDOR_OPENJDK)) {
222239
final String effectivePlatform = isMac(platform) ? "macos" : platform;
223-
final boolean isOld = version.equals(OPENJDK_VERSION_OLD);
224240
final String versionPath = isOld ? "jdk1/99" : "jdk12.0.1/123456789123456789123456789abcde/99";
225241
final String filename = "openjdk-" + (isOld ? "1" : "12.0.1") + "_" + effectivePlatform + "-x64_bin." + extension(platform);
226242
return "/java/GA/" + versionPath + "/GPL/" + filename;

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/Jdk.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class Jdk implements Buildable, Iterable<File> {
3232
"(\\d+)(\\.\\d+\\.\\d+(?:\\.\\d+)?)?\\+(\\d+(?:\\.\\d+)?)(@([a-f0-9]{32}))?"
3333
);
3434
private static final Pattern LEGACY_VERSION_PATTERN = Pattern.compile("(\\d)(u\\d+)\\+(b\\d+?)(@([a-f0-9]{32}))?");
35+
private static final Pattern EA_VERSION_PATTERN = Pattern.compile("(\\d+)-ea\\+(\\d+)(@([a-f0-9]{32}))?");
3536

3637
private final String name;
3738
private final FileCollection configuration;
@@ -78,7 +79,9 @@ public String getVersion() {
7879
}
7980

8081
public void setVersion(String version) {
81-
if (VERSION_PATTERN.matcher(version).matches() == false && LEGACY_VERSION_PATTERN.matcher(version).matches() == false) {
82+
if (VERSION_PATTERN.matcher(version).matches() == false
83+
&& LEGACY_VERSION_PATTERN.matcher(version).matches() == false
84+
&& EA_VERSION_PATTERN.matcher(version).matches() == false) {
8285
throw new IllegalArgumentException("malformed version [" + version + "] for jdk [" + name + "]");
8386
}
8487
parseVersion(version);
@@ -218,9 +221,17 @@ private void parseVersion(String version) {
218221
if (jdkVersionMatcher.matches() == false) {
219222
// Try again with the pre-Java9 version format
220223
jdkVersionMatcher = LEGACY_VERSION_PATTERN.matcher(version);
221-
222224
if (jdkVersionMatcher.matches() == false) {
223-
throw new IllegalArgumentException("Malformed jdk version [" + version + "]");
225+
// Try again with the pre-Java9 version format
226+
jdkVersionMatcher = EA_VERSION_PATTERN.matcher(version);
227+
if (jdkVersionMatcher.matches() == false) {
228+
throw new IllegalArgumentException("Malformed jdk version [" + version + "]");
229+
}
230+
baseVersion = version;
231+
major = jdkVersionMatcher.group(1);
232+
build = jdkVersionMatcher.group(2);
233+
hash = null;
234+
return;
224235
}
225236
}
226237

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JdkDownloadPlugin.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ private void setupRepository(Project project, Jdk jdk) {
114114
+ jdk.getBuild()
115115
+ "/[module]/[classifier]/jdk/hotspot/normal/adoptium";
116116
}
117+
} else if (jdk.getVendor().equals(VENDOR_OPENJDK) && jdk.getDistributionVersion().equals("ea")) {
118+
119+
// "https://builds.es-jdk-archive.com/jdks/openjdk/25/openjdk-25-ea+30/openjdk-25-ea+30_linux-x64_bin.tar.gz"],
120+
// /jdks/openjdk/25/openjdk-25-ea+30/openjdk-25-ea+30_linux
121+
repoUrl = "https://builds.es-jdk-archive.com/";
122+
123+
// current pattern since 12.0.1
124+
artifactPattern = "jdks/openjdk/"
125+
+ jdk.getMajor()
126+
+ "/openjdk-"
127+
+ jdk.getBaseVersion()
128+
+ "/"
129+
+ "openjdk-[revision]_[module]-[classifier]_bin.[ext]";
117130
} else if (jdk.getVendor().equals(VENDOR_OPENJDK)) {
118131
repoUrl = "https://download.oracle.com";
119132
if (jdk.getHash() != null) {

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
import com.fasterxml.jackson.databind.ObjectMapper;
1313

1414
import org.apache.commons.io.IOUtils;
15+
import org.elasticsearch.gradle.Architecture;
16+
import org.elasticsearch.gradle.OS;
1517
import org.elasticsearch.gradle.VersionProperties;
1618
import org.elasticsearch.gradle.internal.BwcVersions;
19+
import org.elasticsearch.gradle.internal.Jdk;
20+
import org.elasticsearch.gradle.internal.JdkDownloadPlugin;
1721
import org.elasticsearch.gradle.internal.conventions.GitInfoPlugin;
1822
import org.elasticsearch.gradle.internal.conventions.info.GitInfo;
1923
import org.elasticsearch.gradle.internal.conventions.info.ParallelDetector;
@@ -22,6 +26,7 @@
2226
import org.gradle.api.Action;
2327
import org.gradle.api.GradleException;
2428
import org.gradle.api.JavaVersion;
29+
import org.gradle.api.NamedDomainObjectContainer;
2530
import org.gradle.api.Plugin;
2631
import org.gradle.api.Project;
2732
import org.gradle.api.logging.Logger;
@@ -52,12 +57,14 @@
5257
import java.io.InputStream;
5358
import java.io.InputStreamReader;
5459
import java.io.UncheckedIOException;
60+
import java.lang.reflect.Field;
5561
import java.nio.file.Files;
5662
import java.util.ArrayList;
5763
import java.util.List;
5864
import java.util.Locale;
5965
import java.util.Random;
6066
import java.util.concurrent.atomic.AtomicReference;
67+
import java.util.function.Predicate;
6168
import java.util.stream.Collectors;
6269
import java.util.stream.Stream;
6370

@@ -94,6 +101,7 @@ public GlobalBuildInfoPlugin(
94101

95102
@Override
96103
public void apply(Project project) {
104+
97105
if (project != project.getRootProject()) {
98106
throw new IllegalStateException(this.getClass().getName() + " can only be applied to the root project.");
99107
}
@@ -272,7 +280,9 @@ private JavaVersion determineJavaVersion(String description, File javaHome, Java
272280
private InstallationLocation getJavaInstallation(File javaHome) {
273281
return getAvailableJavaInstallationLocationSteam().filter(installationLocation -> isSameFile(javaHome, installationLocation))
274282
.findFirst()
275-
.orElseThrow(() -> new GradleException("Could not locate available Java installation in Gradle registry at: " + javaHome));
283+
.orElse(
284+
InstallationLocation.userDefined(javaHome, "Manually resolved JavaHome (not auto-detected by Gradle toolchain service)")
285+
);
276286
}
277287

278288
private boolean isSameFile(File javaHome, InstallationLocation installationLocation) {
@@ -337,11 +347,15 @@ private static void assertMinimumCompilerVersion(JavaVersion minimumCompilerVers
337347
}
338348

339349
private Provider<File> findRuntimeJavaHome() {
340-
String runtimeJavaProperty = System.getProperty("runtime.java");
341-
350+
Integer runtimeJavaProperty = Integer.getInteger("runtime.java");
342351
if (runtimeJavaProperty != null) {
343-
System.out.println("GlobalBuildInfoPlugin.findRuntimeJavaHome");
344-
return resolveJavaHomeFromToolChainService(runtimeJavaProperty);
352+
if (runtimeJavaProperty > Integer.parseInt(VersionProperties.getBundledJdkMajorVersion())) {
353+
// handle EA builds differently due to lack of support in Gradle toolchain service
354+
// we resolve them using JdkDownloadPlugin for now.
355+
return resolveEarlyAccessJavaHome(runtimeJavaProperty);
356+
} else {
357+
return resolveJavaHomeFromToolChainService(runtimeJavaProperty.toString());
358+
}
345359
}
346360
if (System.getenv("RUNTIME_JAVA_HOME") != null) {
347361
return providers.provider(() -> new File(System.getenv("RUNTIME_JAVA_HOME")));
@@ -356,28 +370,31 @@ private Provider<File> findRuntimeJavaHome() {
356370
});
357371
}
358372

373+
private Provider<File> resolveEarlyAccessJavaHome(Integer runtimeJavaProperty) {
374+
NamedDomainObjectContainer<Jdk> container = project.getPlugins().apply(JdkDownloadPlugin.class).getContainer(project);
375+
Integer buildNumber = Integer.getInteger("runtime.java.build");
376+
if (buildNumber == null) {
377+
buildNumber = Integer.getInteger("runtime.java." + runtimeJavaProperty + ".build");
378+
}
379+
if (buildNumber == null) {
380+
buildNumber = findLatestEABuildNumber(runtimeJavaProperty);
381+
}
382+
String eaVersionString = String.format("%d-ea+%d", runtimeJavaProperty, buildNumber);
383+
Jdk jdk = container.create("ea_jdk_" + runtimeJavaProperty, j -> {
384+
j.setVersion(eaVersionString);
385+
j.setVendor("openjdk");
386+
j.setPlatform(OS.current().javaOsReference);
387+
j.setArchitecture(Architecture.current().javaClassifier);
388+
j.setDistributionVersion("ea");
389+
});
390+
return providers.provider(() -> new File(jdk.getJavaHomePath().toString()));
391+
}
392+
359393
@NotNull
360394
private Provider<File> resolveJavaHomeFromToolChainService(String version) {
361-
JavaLanguageVersion languageVersion = JavaLanguageVersion.of(version);
362-
Property<JavaLanguageVersion> value = objectFactory.property(JavaLanguageVersion.class).value(languageVersion);
363-
return toolChainService.launcherFor(javaToolchainSpec -> {
364-
javaToolchainSpec.getLanguageVersion().value(value);
365-
366-
if (Integer.parseInt(VersionProperties.getBundledJdkMajorVersion()) < Integer.parseInt(version)) {
367-
// If the requested version is higher than the bundled JDK, we need trick the toolchain into using our early access catalog
368-
// Otherwise, we use the default implementation
369-
Integer buildNumber = Integer.getInteger("runtime.java.build");
370-
371-
if (buildNumber == null) {
372-
buildNumber = Integer.getInteger("runtime.java." + version + ".build");
373-
}
374-
if (buildNumber == null) {
375-
buildNumber = findLatestEABuildNumber(languageVersion);
376-
}
377-
javaToolchainSpec.getVendor()
378-
.set(DefaultJvmVendorSpec.of("Oracle[" + languageVersion + "/" + buildNumber + "]"));
379-
}
380-
}).map(launcher -> launcher.getMetadata().getInstallationPath().getAsFile());
395+
Property<JavaLanguageVersion> value = objectFactory.property(JavaLanguageVersion.class).value(JavaLanguageVersion.of(version));
396+
return toolChainService.launcherFor(javaToolchainSpec -> javaToolchainSpec.getLanguageVersion().value(value))
397+
.map(launcher -> launcher.getMetadata().getInstallationPath().getAsFile());
381398
}
382399

383400
public static String getResourceContents(String resourcePath) {

0 commit comments

Comments
 (0)