Skip to content
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

Restore an edge case of non-javac toolchain tools being used as a Java compiler (Gradle 8.0.x) #24072

Merged
merged 1 commit into from Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -469,6 +469,92 @@ class JavaCompileToolchainIntegrationTest extends AbstractIntegrationSpec implem
JavaVersion.current() | "[deprecation] foo() in Foo has been deprecated"
}

@Issue("https://github.com/gradle/gradle/issues/23990")
def "can compile with a custom compiler executable"() {
def otherJdk = AvailableJavaHomes.getJdk(JavaVersion.current())
def jdk = AvailableJavaHomes.getDifferentVersion {
def v = it.languageVersion.majorVersion.toInteger()
11 <= v && v <= 18 // Java versions supported by ECJ releases used in the test
}

buildFile << """
plugins {
id("java")
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(${otherJdk.javaVersion.majorVersion})
}
}

configurations {
ecj {
canBeConsumed = false
canBeResolved = true
}
}

${mavenCentralRepository()}

dependencies {
def changed = providers.gradleProperty("changed").isPresent()
ecj(!changed ? "org.eclipse.jdt:ecj:3.31.0" : "org.eclipse.jdt:ecj:3.32.0")
}

// Make sure the provider is up-to-date only if the ECJ classpath does not change
class EcjClasspathProvider implements CommandLineArgumentProvider {
@Classpath
final FileCollection ecjClasspath

EcjClasspathProvider(FileCollection ecjClasspath) {
this.ecjClasspath = ecjClasspath
}

@Override
List<String> asArguments() {
return ["-cp", ecjClasspath.asPath, "org.eclipse.jdt.internal.compiler.batch.Main"]
}
}

compileJava {
def customJavaLauncher = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(${jdk.javaVersion.majorVersion}))
}.get()

// ECJ does not support generating JNI headers
options.headerOutputDirectory.set(provider { null })
options.fork = true
options.forkOptions.executable = customJavaLauncher.executablePath.asFile.absolutePath
options.forkOptions.jvmArgumentProviders.add(new EcjClasspathProvider(configurations.ecj))
}
"""

when:
withInstallations(jdk, otherJdk).run(":compileJava", "--info")
then:
executedAndNotSkipped(":compileJava")
outputContains("Compiling with toolchain '${jdk.javaHome.absolutePath}'")
outputContains("Compiling with Java command line compiler '${jdk.javaExecutable.absolutePath}'")
classJavaVersion(javaClassFile("Foo.class")) == jdk.javaVersion

// Test up-to-date checks
when:
withInstallations(jdk, otherJdk).run(":compileJava")
then:
skipped(":compileJava")

when:
withInstallations(jdk, otherJdk).run(":compileJava", "-Pchanged")
then:
executedAndNotSkipped(":compileJava")

when:
withInstallations(jdk, otherJdk).run(":compileJava", "-Pchanged")
then:
skipped(":compileJava")
}

private TestFile configureForkOptionsExecutable(Jvm jdk) {
buildFile << """
compileJava {
Expand Down
Expand Up @@ -41,17 +41,7 @@ public T create() {
}

if (compileOptions.isFork()) {
File customJavaHome = compileOptions.getForkOptions().getJavaHome();
if (customJavaHome != null) {
return getCommandLineSpec(Jvm.forHome(customJavaHome).getJavacExecutable());
}

String customExecutable = compileOptions.getForkOptions().getExecutable();
if (customExecutable != null) {
return getCommandLineSpec(new File(customExecutable));
}

return getForkingSpec(Jvm.current().getJavaHome());
return chooseSpecFromCompileOptions(Jvm.current().getJavaHome());
}

return getDefaultSpec();
Expand All @@ -64,13 +54,7 @@ private T chooseSpecForToolchain() {
}

if (compileOptions.isFork()) {
// Presence of the fork options means that the user has explicitly requested a command-line compiler
if (compileOptions.getForkOptions().getJavaHome() != null || compileOptions.getForkOptions().getExecutable() != null) {
// We use the toolchain path because the fork options must agree with the selected toolchain
return getCommandLineSpec(Jvm.forHome(toolchainJavaHome).getJavacExecutable());
}

return getForkingSpec(toolchainJavaHome);
return chooseSpecFromCompileOptions(toolchainJavaHome);
}

if (!toolchain.isCurrentJvm()) {
Expand All @@ -80,6 +64,20 @@ private T chooseSpecForToolchain() {
return getDefaultSpec();
}

private T chooseSpecFromCompileOptions(File fallbackJavaHome) {
File forkJavaHome = compileOptions.getForkOptions().getJavaHome();
if (forkJavaHome != null) {
return getCommandLineSpec(Jvm.forHome(forkJavaHome).getJavacExecutable());
}

String forkExecutable = compileOptions.getForkOptions().getExecutable();
if (forkExecutable != null) {
return getCommandLineSpec(new File(forkExecutable));
}

return getForkingSpec(fallbackJavaHome);
}

abstract protected T getCommandLineSpec(File executable);

abstract protected T getForkingSpec(File javaHome);
Expand Down
Expand Up @@ -259,7 +259,6 @@ private void validateForkOptionsMatchToolchain() {

JavaCompiler javaCompilerTool = getJavaCompiler().get();
File toolchainJavaHome = javaCompilerTool.getMetadata().getInstallationPath().getAsFile();
File toolchainExecutable = javaCompilerTool.getExecutablePath().getAsFile();

ForkOptions forkOptions = getOptions().getForkOptions();
File customJavaHome = forkOptions.getJavaHome();
Expand All @@ -269,10 +268,16 @@ private void validateForkOptionsMatchToolchain() {
);

String customExecutablePath = forkOptions.getExecutable();
checkState(
customExecutablePath == null || new File(customExecutablePath).equals(toolchainExecutable),
"Toolchain from `executable` property on `ForkOptions` does not match toolchain from `javaCompiler` property"
);
// We do not match the custom executable against the compiler executable from the toolchain (javac),
// because the custom executable can be set to the path of another tool in the toolchain such as a launcher (java).
if (customExecutablePath != null) {
// Relying on the layout of the toolchain distribution: <JAVA HOME>/bin/<executable>
File customExecutableJavaHome = new File(customExecutablePath).getParentFile().getParentFile();
checkState(
customExecutableJavaHome.equals(toolchainJavaHome),
"Toolchain from `executable` property on `ForkOptions` does not match toolchain from `javaCompiler` property"
);
}
}

private boolean isToolchainCompatibleWithJava8() {
Expand Down