Skip to content

Commit

Permalink
[Gradle JDK] Fix when palantir-gradle-jdks is not installed and jdk…
Browse files Browse the repository at this point in the history
…s are missing (#382)

[Gradle JDK] Fix when `palantir-gradle-jdks` is not installed and jdks are missing
  • Loading branch information
crogoz authored Jul 29, 2024
1 parent 257836b commit 5e0198a
Show file tree
Hide file tree
Showing 18 changed files with 436 additions and 178 deletions.
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-382.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: fix
fix:
description: '[Gradle JDK] Fix when `palantir-gradle-jdks` is not installed and
jdks are missing'
links:
- https://github.com/palantir/gradle-jdks/pull/382
13 changes: 13 additions & 0 deletions gradle-jdks-settings/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ dependencies {
// settings classLoader and the build classLoaders.
shadeTransitively project(':gradle-jdks-setup-common')
shadeTransitively project(':gradle-jdks-enablement')

testImplementation gradleTestKit()
testImplementation 'com.netflix.nebula:nebula-test'
testImplementation 'org.apache.commons:commons-compress'
testImplementation project(':gradle-jdks-test-common')
}

gradlePlugin {
Expand Down Expand Up @@ -44,3 +49,11 @@ publishing.publications {
}
}
}

// Changing the location where the gradle jdks are installed such that the settings plugin can run the
// gradle-jdks-setup.sh configuration script to install the missing jdks.
tasks.withType(Test) {
environment("HOME", "/tmp/gradleJdksSettingsTest")
systemProperty 'user.home', '/tmp/gradleJdksSettingsTest'
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@

import com.palantir.gradle.jdks.enablement.GradleJdksEnablement;
import com.palantir.gradle.jdks.setup.common.Arch;
import com.palantir.gradle.jdks.setup.common.CommandRunner;
import com.palantir.gradle.jdks.setup.common.CurrentArch;
import com.palantir.gradle.jdks.setup.common.CurrentOs;
import com.palantir.gradle.jdks.setup.common.Os;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -57,7 +59,8 @@ public final class ToolchainJdksSettingsPlugin implements Plugin<Settings> {

@Override
public void apply(Settings settings) {
if (!GradleJdksEnablement.isGradleJdkSetupEnabled(settings.getRootDir().toPath())) {
Path rootProjectDir = settings.getRootDir().toPath();
if (!GradleJdksEnablement.isGradleJdkSetupEnabled(rootProjectDir)) {
logger.debug("Skipping Gradle JDK gradle properties patching");
return;
}
Expand All @@ -67,14 +70,17 @@ public void apply(Settings settings) {
+ " Please upgrade to a higher Gradle version in order to use the JDK setup.",
GradleJdksEnablement.MINIMUM_SUPPORTED_GRADLE_VERSION));
}
Path gradleJdksLocalDirectory = settings.getRootDir().toPath().resolve("gradle/jdks");
Path gradleJdksLocalDirectory = rootProjectDir.resolve("gradle/jdks");
// Not failing here because the plugin might be applied before the `./gradlew setupJdks` is run, hence not
// having the expected directory structure.
if (!Files.exists(gradleJdksLocalDirectory)) {
logger.debug("Not setting the Gradle JDK properties because gradle/jdks directory doesn't exist. Please run"
logger.info("Not setting the Gradle JDK properties because gradle/jdks directory doesn't exist. Please run"
+ " ./gradlew setupJdks to set up the JDKs.");
return;
}
// Forces the installation of the configured jdks if they are not installed. Fixes the case when a user doesn't
// have the Intellij plugin installed and some jdks are missing.
getOrInstallJdkPaths(rootProjectDir, gradleJdksLocalDirectory);
ProviderFactory providerFactory =
((DefaultSettings) settings).getServices().get(ProviderFactory.class);
if (!(providerFactory instanceof DefaultProviderFactory)) {
Expand All @@ -97,7 +103,8 @@ public void apply(Settings settings) {
GradleProperties ourGradleProperties = (GradleProperties) Proxy.newProxyInstance(
GradleProperties.class.getClassLoader(),
new Class[] {GradleProperties.class},
new GradlePropertiesInvocationHandler(gradleJdksLocalDirectory, originalGradleProperties));
new GradlePropertiesInvocationHandler(
rootProjectDir, gradleJdksLocalDirectory, originalGradleProperties));
field.set(defaultValueSourceProviderFactory, ourGradleProperties);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Failed to update the Gradle JDK properties using reflection", e);
Expand All @@ -107,30 +114,28 @@ public void apply(Settings settings) {
private static class GradlePropertiesInvocationHandler implements InvocationHandler {
private final GradleProperties originalGradleProperties;
private final Path gradleJdksLocalDirectory;
private final Path rootProjectDir;

GradlePropertiesInvocationHandler(Path gradleJdksLocalDirectory, GradleProperties originalGradleProperties) {
GradlePropertiesInvocationHandler(
Path rootProjectDir, Path gradleJdksLocalDirectory, GradleProperties originalGradleProperties) {
this.rootProjectDir = rootProjectDir;
this.gradleJdksLocalDirectory = gradleJdksLocalDirectory;
this.originalGradleProperties = originalGradleProperties;
}

@Override
public Object invoke(Object _proxy, Method method, Object[] args) throws Throwable {
List<Path> localToolchains = getInstalledToolchains(gradleJdksLocalDirectory);
if (localToolchains.isEmpty()) {
throw new RuntimeException(
"Gradle JDK setup is enabled (palantir.jdk.setup.enabled is true) but no toolchains could be"
+ " configured");
}
// see: https://github.com/gradle/gradle/blob/4bd1b3d3fc3f31db5a26eecb416a165b8cc36082/subprojects/core-api/
// src/main/java/org/gradle/api/internal/properties/GradleProperties.java#L28
if (method.getName().equals("find") && args.length == 1) {
List<Path> installedLocalToolchains = getOrInstallJdkPaths(rootProjectDir, gradleJdksLocalDirectory);
String onlyArg = (String) args[0];
if (onlyArg.equals("org.gradle.java.installations.auto-detect")
|| onlyArg.equals("org.gradle.java.installations.auto-download")) {
return "false";
}
if (onlyArg.equals("org.gradle.java.installations.paths")) {
return localToolchains.stream()
return installedLocalToolchains.stream()
.map(Path::toAbsolutePath)
.map(Path::toString)
.collect(Collectors.joining(","));
Expand All @@ -144,37 +149,45 @@ public Object invoke(Object _proxy, Method method, Object[] args) throws Throwab
throw e.getCause();
}
}
}

private static List<Path> getInstalledToolchains(Path gradleJdksLocalDirectory) {
Path installationDirectory = getToolchainInstallationDir();
Os os = CurrentOs.get();
Arch arch = CurrentArch.get();
try (Stream<Path> stream = Files.list(gradleJdksLocalDirectory).filter(Files::isDirectory)) {
return stream.map(path -> path.resolve(os.toString())
.resolve(arch.toString())
.resolve("local-path"))
.map(path -> getToolchain(path, installationDirectory))
.collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException("Unable to list the local installation paths", e);
}
private static List<Path> getOrInstallJdkPaths(Path rootProjectDir, Path gradleJdksLocalDirectory) {
List<Path> jdkPaths = getConfiguredJdkPaths(gradleJdksLocalDirectory);
List<Path> missingJdkPaths = getMissingPaths(jdkPaths);
if (!missingJdkPaths.isEmpty()) {
logger.error(
"Gradle JDK setup is enabled (palantir.jdk.setup.enabled is true) but some jdks were not"
+ " installed: {}. If running from Intellij, please make sure the"
+ " `palantir-gradle-jdks` Intellij plugin is installed"
+ " https://plugins.jetbrains.com/plugin/24776-palantir-gradle-jdks/versions."
+ " To unblock the workflow, the jdks will be manually installed now ...",
missingJdkPaths);
runGradleJdkSetup(rootProjectDir);
}
return jdkPaths;
}

private static Path getToolchain(Path gradleJdkConfigurationPath, Path installationDirectory) {
try {
String localFilename =
Files.readString(gradleJdkConfigurationPath).trim();
Path installationPath = installationDirectory.resolve(localFilename);
if (!Files.exists(installationPath)) {
throw new RuntimeException(
String.format("Failed to find the toolchain at path=%s", installationPath));
}
return installationPath;
} catch (IOException e) {
throw new RuntimeException(
String.format("Failed to get the toolchain configured at path=%s", gradleJdkConfigurationPath),
e);
}
private static List<Path> getConfiguredJdkPaths(Path gradleJdksLocalDirectory) {
Path installationDirectory = getToolchainInstallationDir();
Os os = CurrentOs.get();
Arch arch = CurrentArch.get();
try (Stream<Path> stream = Files.list(gradleJdksLocalDirectory).filter(Files::isDirectory)) {
return stream.map(path ->
path.resolve(os.toString()).resolve(arch.toString()).resolve("local-path"))
.map(path -> resolveJdkPath(path, installationDirectory))
.collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException("Unable to list the local JDK installation paths", e);
}
}

private static Path resolveJdkPath(Path gradleJdkConfigurationPath, Path installationDirectory) {
try {
String localFilename = Files.readString(gradleJdkConfigurationPath).trim();
return installationDirectory.resolve(localFilename);
} catch (IOException e) {
throw new RuntimeException(
String.format("Failed to read gradle jdk configuration file %s", gradleJdkConfigurationPath), e);
}
}

Expand All @@ -189,4 +202,33 @@ private static boolean isGradleVersionSupported() {
.compareTo(GradleVersion.version(GradleJdksEnablement.MINIMUM_SUPPORTED_GRADLE_VERSION))
>= 0;
}

private static void runGradleJdkSetup(Path rootProjectDir) {
Path buildDirectory = rootProjectDir.resolve("build");
createDirectories(buildDirectory);
CommandRunner.runWithLogger(
new ProcessBuilder().command("./gradle/gradle-jdks-setup.sh").directory(rootProjectDir.toFile()),
ToolchainJdksSettingsPlugin::writeStdOutput,
ToolchainJdksSettingsPlugin::writeStdErr);
}

private static void writeStdOutput(InputStream inputStream) {
CommandRunner.processStream(inputStream, logger::lifecycle);
}

private static void writeStdErr(InputStream inputStream) {
CommandRunner.processStream(inputStream, logger::error);
}

private static void createDirectories(Path path) {
try {
Files.createDirectories(path);
} catch (IOException e) {
throw new RuntimeException("Failed to create directory", e);
}
}

private static List<Path> getMissingPaths(List<Path> paths) {
return paths.stream().filter(path -> !Files.exists(path)).collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.gradle.jdks.settings

import com.palantir.gradle.jdks.GradleJdkTestUtils
import com.palantir.gradle.jdks.setup.common.CurrentArch
import com.palantir.gradle.jdks.setup.common.CurrentOs
import nebula.test.IntegrationSpec
import nebula.test.functional.ExecutionResult

import java.nio.file.Files
import java.nio.file.Path

class ToolchainJdksSettingsPluginTest extends IntegrationSpec {

private final Path USER_HOME_PATH = Path.of(System.getProperty("user.home"))

def '#gradleVersionNumber: non-existing jdks are installed by the settings plugin'() {
setup:
gradleVersion = gradleVersionNumber
Files.createDirectories(USER_HOME_PATH)
GradleJdkTestUtils.applyJdksPlugins(settingsFile, buildFile)

// language=groovy
buildFile << """
jdks {
jdk(17) {
distribution = JDK_17_DISTRO
jdkVersion = JDK_17_VERSION
}
daemonTarget = '17'
}
""".replace("JDK_17_DISTRO", GradleJdkTestUtils.quoted(GradleJdkTestUtils.JDK_17.getLeft()))
.replace("JDK_17_VERSION", GradleJdkTestUtils.quoted(GradleJdkTestUtils.JDK_17.getRight()))
.stripIndent(true)

when:
file('gradle.properties') << 'palantir.jdk.setup.enabled=true'
runTasksSuccessfully("generateGradleJdkConfigs")

then: 'only gradle configuration files are generated, no jdks are installed'
String os = CurrentOs.get().uiName()
String arch = CurrentArch.get().uiName()
Path jdk17LocalPath = projectDir.toPath().resolve("gradle/jdks/17/${os}/${arch}/local-path")
String originalJdk17LocalPath = jdk17LocalPath.text.trim()
Path originalJdkPath = Path.of(System.getProperty("user.home")).resolve(".gradle/gradle-jdks")
.resolve(originalJdk17LocalPath).toAbsolutePath()
!Files.exists(originalJdkPath)

when: 'trigger a task'
jdk17LocalPath.text = "amazon-corretto-${gradleVersion}-test1\n"
ExecutionResult executionResult = runTasksSuccessfully("javaToolchains")

then: 'the jdks are installed by the settings plugin'
executionResult.standardError.contains("Gradle JDK setup is enabled (palantir.jdk.setup.enabled is true)" +
" but some jdks were not installed")
executionResult.standardOutput.contains("Auto-detection: Disabled")
executionResult.standardOutput.contains("Auto-download: Disabled")
executionResult.standardOutput.contains("JDK ${GradleJdkTestUtils.SIMPLIFIED_JDK_17_VERSION}")
Path expectedJdkPath = USER_HOME_PATH.resolve(".gradle/gradle-jdks")
.resolve("amazon-corretto-${gradleVersion}-test1").toAbsolutePath()
Files.exists(expectedJdkPath)

when: 'if the jdk configured path is changed'
jdk17LocalPath.text = "amazon-corretto-${gradleVersion}-test2\n"
ExecutionResult resultAfterJdkChange = runTasksSuccessfully("javaToolchains")

then:
resultAfterJdkChange.standardError.contains("Gradle JDK setup is enabled (palantir.jdk.setup.enabled is true)" +
" but some jdks were not installed")
Path newInstalledJdkPath = USER_HOME_PATH.resolve(".gradle/gradle-jdks")
.resolve("amazon-corretto-${gradleVersion}-test2").toAbsolutePath()
Files.exists(newInstalledJdkPath)

cleanup:
Files.walk(expectedJdkPath)
.sorted(Comparator.reverseOrder())
.forEach(Files::delete)

Files.walk(newInstalledJdkPath)
.sorted(Comparator.reverseOrder())
.forEach(Files::delete)

where:
// testing for the different gradle versions to make sure the reflection in the settings plugin works
gradleVersionNumber << [GradleJdkTestUtils.GRADLE_7_6_VERSION, GradleJdkTestUtils.GRADLE_8_5_VERSION, GradleJdkTestUtils.GRADLE_8_8_VERSION]
}

}
Loading

0 comments on commit 5e0198a

Please sign in to comment.