-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Java Toolchains in Quarkus Gradle plugin #44392
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -36,16 +45,39 @@ 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; | ||
} | ||
|
@@ -60,14 +92,18 @@ WorkQueue workQueue(Map<String, String> configMap, List<Action<? super JavaForkO | |
} | ||
|
||
return workerExecutor.processIsolation(processWorkerSpec -> configureProcessWorkerSpec(processWorkerSpec, | ||
configMap, forkOptionsSupplier)); | ||
configMap, forkOptionsSupplier, getJavaLauncher().get())); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling What I am trying to say is: It seems in this case you don't need to check |
||
} | ||
|
||
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) { | ||
|
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(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just passing by:
JavaExec is doing that because for JavaExec you can set java as executable. For your task that doesn't make a lot of sense and
objectFactory.newInstance(DefaultJavaExecSpec.class).getExecutable();
will always return null anyway. Also you are referencing internal type, it would be great if you could avoid that :)I assume you want to achieve logic like:
You can achieve that with something like:
See also https://docs.gradle.org/current/userguide/toolchains.html#sec:plugins_toolchains
I hope that helps! 👋