Skip to content

Commit

Permalink
feat: support for Java Toolchains in Quarkus Gradle plugin
Browse files Browse the repository at this point in the history
Closes #20452
  • Loading branch information
joschi committed Nov 8, 2024
1 parent 901675f commit fb911c8
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.artifacts.ModuleDependency;
import org.gradle.api.attributes.Category;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.jvm.toolchain.internal.SpecificInstallationToolchainSpec;
import org.gradle.process.internal.DefaultJavaExecSpec;

import javax.annotation.Nullable;

public class GradleUtils {

Expand Down Expand Up @@ -58,4 +64,13 @@ public static List<Dependency> listProjectBoms(Project project) {
return boms;
}

@Nullable
public static JavaToolchainSpec getExecutableOverrideToolchainSpec(ObjectFactory objectFactory) {
String customExecutable = objectFactory.newInstance(DefaultJavaExecSpec.class).getExecutable();
if (customExecutable != null) {
return SpecificInstallationToolchainSpec.fromJavaExecutable(objectFactory, customExecutable);
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

import javax.inject.Inject;
Expand All @@ -26,6 +27,7 @@
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.specs.Spec;
Expand All @@ -35,6 +37,8 @@
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.api.tasks.testing.Test;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
import org.gradle.util.GradleVersion;

Expand Down Expand Up @@ -208,21 +212,22 @@ private void registerTasks(Project project, QuarkusPluginExtension quarkusExt) {
// quarkusGenerateCode
TaskProvider<QuarkusGenerateCode> quarkusGenerateCode = tasks.register(QUARKUS_GENERATE_CODE_TASK_NAME,
QuarkusGenerateCode.class, LaunchMode.NORMAL, SourceSet.MAIN_SOURCE_SET_NAME);
quarkusGenerateCode.configure(task -> configureGenerateCodeTask(task, quarkusGenerateAppModelTask,
quarkusGenerateCode.configure(task -> configureGenerateCodeTask(project, task, quarkusGenerateAppModelTask,
QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES));
// quarkusGenerateCodeDev
TaskProvider<QuarkusGenerateCode> quarkusGenerateCodeDev = tasks.register(QUARKUS_GENERATE_CODE_DEV_TASK_NAME,
QuarkusGenerateCode.class, LaunchMode.DEVELOPMENT, SourceSet.MAIN_SOURCE_SET_NAME);
quarkusGenerateCodeDev.configure(task -> {
task.dependsOn(quarkusGenerateCode);
configureGenerateCodeTask(task, quarkusGenerateDevAppModelTask, QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES);
configureGenerateCodeTask(project, task, quarkusGenerateDevAppModelTask,
QuarkusGenerateCode.QUARKUS_GENERATED_SOURCES);
});
// quarkusGenerateCodeTests
TaskProvider<QuarkusGenerateCode> quarkusGenerateCodeTests = tasks.register(QUARKUS_GENERATE_CODE_TESTS_TASK_NAME,
QuarkusGenerateCode.class, LaunchMode.TEST, SourceSet.TEST_SOURCE_SET_NAME);
quarkusGenerateCodeTests.configure(task -> {
task.dependsOn("compileQuarkusTestGeneratedSourcesJava");
configureGenerateCodeTask(task, quarkusGenerateTestAppModelTask,
configureGenerateCodeTask(project, task, quarkusGenerateTestAppModelTask,
QuarkusGenerateCode.QUARKUS_TEST_GENERATED_SOURCES);
});

Expand Down Expand Up @@ -534,9 +539,24 @@ private static void configureQuarkusBuildTask(Project project, QuarkusPluginExte
task.setCompileClasspath(mainSourceSet.getCompileClasspath().plus(mainSourceSet.getRuntimeClasspath())
.plus(mainSourceSet.getAnnotationProcessorPath())
.plus(mainSourceSet.getResources()));

Provider<JavaToolchainSpec> toolchainOverrideSpec = project.provider(() ->
GradleUtils.getExecutableOverrideToolchainSpec(project.getObjects()));
task.getJavaLauncher().convention(getToolchainTool(project, JavaToolchainService::launcherFor, toolchainOverrideSpec));
}

private static <T> Provider<T> getToolchainTool(
Project project,
BiFunction<JavaToolchainService, JavaToolchainSpec, Provider<T>> toolMapper,
Provider<JavaToolchainSpec> toolchainOverride
) {
JavaToolchainService service = project.getExtensions().getByType(JavaToolchainService.class);
JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class);
return toolchainOverride.orElse(extension.getToolchain())
.flatMap(spec -> toolMapper.apply(service, spec));
}

private static void configureGenerateCodeTask(QuarkusGenerateCode task,
private static void configureGenerateCodeTask(Project project, QuarkusGenerateCode task,
TaskProvider<QuarkusApplicationModelTask> applicationModelTaskTaskProvider, String generateSourcesDir) {
SourceSet generatedSources = getSourceSet(task.getProject(), generateSourcesDir);
Set<File> sourceSetOutput = generatedSources.getOutput().filter(f -> f.getName().equals(generateSourcesDir)).getFiles();
Expand All @@ -547,6 +567,10 @@ private static void configureGenerateCodeTask(QuarkusGenerateCode task,
task.getApplicationModel()
.set(applicationModelTaskTaskProvider.flatMap(QuarkusApplicationModelTask::getApplicationModel));
task.getGeneratedOutputDirectory().set(generatedSources.getJava().getClassesDirectory());

Provider<JavaToolchainSpec> toolchainOverrideSpec = project.provider(() ->
GradleUtils.getExecutableOverrideToolchainSpec(project.getObjects()));
task.getJavaLauncher().convention(getToolchainTool(project, JavaToolchainService::launcherFor, toolchainOverrideSpec));
}

private void createSourceSets(Project project) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@

import javax.inject.Inject;

import io.quarkus.gradle.GradleUtils;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.Nested;
import org.gradle.jvm.toolchain.JavaLauncher;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.process.JavaForkOptions;
import org.gradle.workers.ProcessWorkerSpec;
import org.gradle.workers.WorkQueue;
Expand All @@ -24,6 +32,7 @@ public abstract class QuarkusTask extends DefaultTask {
private final transient QuarkusPluginExtension extension;
protected final File projectDir;
protected final File buildDir;
private final Property<JavaLauncher> javaLauncher;

QuarkusTask(String description) {
this(description, false);
Expand All @@ -36,16 +45,38 @@ public abstract class QuarkusTask extends DefaultTask {
this.projectDir = getProject().getProjectDir();
this.buildDir = getProject().getBuildDir();

ObjectFactory objectFactory = getObjectFactory();
JavaToolchainService javaToolchainService = getJavaToolchainService();
Provider<JavaLauncher> javaLauncherConvention = getProviderFactory()
.provider(() -> GradleUtils.getExecutableOverrideToolchainSpec(objectFactory))
.flatMap(javaToolchainService::launcherFor)
.orElse(javaToolchainService.launcherFor(it -> {}));
this.javaLauncher = objectFactory.property(JavaLauncher.class).convention(javaLauncherConvention);

// Calling this method tells Gradle that it should not fail the build. Side effect is that the configuration
// cache will be at least degraded, but the build will not fail.
if (!configurationCacheCompatible) {
notCompatibleWithConfigurationCache("The Quarkus Plugin isn't compatible with the configuration cache");
}
}

@Inject
protected abstract ObjectFactory getObjectFactory();

@Inject
protected abstract ProviderFactory getProviderFactory();

@Inject
protected abstract JavaToolchainService getJavaToolchainService();

@Inject
protected abstract WorkerExecutor getWorkerExecutor();

@Nested
public Property<JavaLauncher> getJavaLauncher() {
return javaLauncher;
}

QuarkusPluginExtension extension() {
return extension;
}
Expand All @@ -60,14 +91,18 @@ WorkQueue workQueue(Map<String, String> configMap, List<Action<? super JavaForkO
}

return workerExecutor.processIsolation(processWorkerSpec -> configureProcessWorkerSpec(processWorkerSpec,
configMap, forkOptionsSupplier));
configMap, forkOptionsSupplier, getJavaLauncher().get()));
}

private void configureProcessWorkerSpec(ProcessWorkerSpec processWorkerSpec, Map<String, String> configMap,
List<Action<? super JavaForkOptions>> customizations) {
List<Action<? super JavaForkOptions>> customizations, JavaLauncher launcher) {
JavaForkOptions forkOptions = processWorkerSpec.getForkOptions();
customizations.forEach(a -> a.execute(forkOptions));

if (launcher != null) {
forkOptions.executable(launcher.getExecutablePath().getAsFile().getAbsolutePath());
}

// Propagate user.dir to load config sources that use it (instead of the worker user.dir)
String userDir = configMap.get("user.dir");
if (userDir != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.gradle.tasks;

import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.net.URL;
import java.nio.file.Path;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;

public class JavaToolchainTest {

@TempDir
Path testProjectDir;

@Test
void quarkusIsUsingJavaToolchain() throws Exception {
URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/toolchain/main");
FileUtils.copyDirectory(new File(url.toURI()), testProjectDir.toFile());
FileUtils.copyFile(new File("../gradle.properties"), testProjectDir.resolve("gradle.properties").toFile());

GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir.toFile())
.withArguments("build", "--info", "--stacktrace", "--build-cache", "--configuration-cache")
// .build() checks whether the build failed, which is good enough for this test
.build();
}

@Test
void quarkusPluginCanOverrideJavaToolchain() throws Exception {
URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/toolchain/custom");
FileUtils.copyDirectory(new File(url.toURI()), testProjectDir.toFile());
FileUtils.copyFile(new File("../gradle.properties"), testProjectDir.resolve("gradle.properties").toFile());

GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir.toFile())
.withArguments("build", "--info", "--stacktrace", "--build-cache", "--configuration-cache")
// .build() checks whether the build failed, which is good enough for this test
.build();
}

@Test
void quarkusPluginFailsWithIncompatibleToolchains() throws Exception {
URL url = getClass().getClassLoader().getResource("io/quarkus/gradle/tasks/toolchain/fail");
FileUtils.copyDirectory(new File(url.toURI()), testProjectDir.toFile());
FileUtils.copyFile(new File("../gradle.properties"), testProjectDir.resolve("gradle.properties").toFile());

BuildResult buildResult = GradleRunner.create()
.withPluginClasspath()
.withProjectDir(testProjectDir.toFile())
.withArguments("build", "--info", "--stacktrace", "--build-cache", "--configuration-cache")
.buildAndFail();

assertThat(buildResult.task(":quarkusAppPartsBuild").getOutcome()).isEqualTo(TaskOutcome.FAILED);
assertThat(buildResult.getOutput()).contains("java.lang.UnsupportedClassVersionError");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
java
id("io.quarkus")
}

buildscript {
repositories {
mavenLocal()
mavenCentral()
}
}

repositories {
mavenLocal()
mavenCentral()
}

dependencies {
implementation(enforcedPlatform("io.quarkus:quarkus-bom:${project.property("version")}"))
implementation("io.quarkus:quarkus-core")
implementation("jakarta.inject:jakarta.inject-api:2.0.1")
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

tasks.withType(io.quarkus.gradle.tasks.QuarkusTask::class.java) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0")
}

rootProject.name = "gradle-java-toolchain"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.acme;

import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "foo")
public interface Foo {
String string();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
java
id("io.quarkus")
}

buildscript {
repositories {
mavenLocal()
mavenCentral()
}
}

repositories {
mavenLocal()
mavenCentral()
}

dependencies {
implementation(enforcedPlatform("io.quarkus:quarkus-bom:${project.property("version")}"))
implementation("io.quarkus:quarkus-core")
implementation("jakarta.inject:jakarta.inject-api:2.0.1")
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

tasks.withType(io.quarkus.gradle.tasks.QuarkusTask::class.java) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(17)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0")
}

rootProject.name = "gradle-java-toolchain"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.acme;

import io.smallrye.config.ConfigMapping;

@ConfigMapping(prefix = "foo")
public interface Foo {
String string();
}
Loading

0 comments on commit fb911c8

Please sign in to comment.