Work around Kotlin plugin breaking AOT compile classpaths
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:24], map[id:windows-latest name:Windows]) (push) Waiting to run Details
Run CodeQL Analysis / run-analysis (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run Details

Compilation of AOT-generated source code requires runtime
dependencies to be on the classpath. This is necessary as a class
from a runtime dependency may appear in the signature of a generated
method that defines a bean. To accomplish this, Boot's AOT plugin
sets the org.gradle.usage attribute of the compile classpath
configurations of the aot and aotTest source sets to java-runtime.

When the Kotlin plugin is applied after Boot's AOT plugin it breaks
this arrangement by setting org.gradle.usage to java-api. There
doesn't appear to be a way to prevent it from messing with the aot
and aotTest source sets despite them not using Kotlin. This commit
works around the problem by repairing the damage and setting the
attribute back to java-runtime again.

Fixes gh-46397
This commit is contained in:
Andy Wilkinson 2025-07-10 15:59:37 +01:00
parent 4a4ab26141
commit f61c54632a
5 changed files with 136 additions and 1 deletions

View File

@ -34,12 +34,17 @@ class KotlinPluginAction implements PluginApplicationAction {
@Override
public void execute(Project project) {
configureKotlinVersionProperty(project);
enableJavaParametersOption(project);
repairDamageToAotCompileConfigurations(project);
}
private void configureKotlinVersionProperty(Project project) {
ExtraPropertiesExtension extraProperties = project.getExtensions().getExtraProperties();
if (!extraProperties.has("kotlin.version")) {
String kotlinVersion = getKotlinVersion(project);
extraProperties.set("kotlin.version", kotlinVersion);
}
enableJavaParametersOption(project);
}
private String getKotlinVersion(Project project) {
@ -52,6 +57,13 @@ class KotlinPluginAction implements PluginApplicationAction {
.configureEach((compile) -> compile.getKotlinOptions().setJavaParameters(true));
}
private void repairDamageToAotCompileConfigurations(Project project) {
SpringBootAotPlugin aotPlugin = project.getPlugins().findPlugin(SpringBootAotPlugin.class);
if (aotPlugin != null) {
aotPlugin.repairKotlinPluginDamage(project);
}
}
@Override
public Class<? extends Plugin<? extends Project>> getPluginClass() {
return KotlinPluginWrapper.class;

View File

@ -237,4 +237,19 @@ public class SpringBootAotPlugin implements Plugin<Project> {
dependencies.add(dependencyHandler.create("org.junit.platform:junit-platform-launcher"));
}
void repairKotlinPluginDamage(Project project) {
project.getPlugins().withType(JavaPlugin.class).configureEach((javaPlugin) -> {
repairKotlinPluginDamage(project, SpringBootAotPlugin.AOT_SOURCE_SET_NAME);
repairKotlinPluginDamage(project, SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME);
});
}
private void repairKotlinPluginDamage(Project project, String sourceSetName) {
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
SourceSetContainer sourceSets = javaPluginExtension.getSourceSets();
Configuration compileClasspath = project.getConfigurations()
.getByName(sourceSets.getByName(sourceSetName).getCompileClasspathConfigurationName());
configureJavaRuntimeUsageAttribute(project, compileClasspath.getAttributes());
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.gradle.plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
@ -90,9 +91,31 @@ class KotlinPluginActionIntegrationTests {
assertThat(configured).containsExactlyInAnyOrder("help", "compileJava", "clean");
}
@Test
void compileAotJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin() {
expectConfigurationCacheRequestedDeprecationWarning();
expectResolvableUsageIsAlreadyAllowedWarning();
String output = this.gradleBuild.build("compileAotJavaClasspath").getOutput();
assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging");
}
@Test
void compileAotTestJavaHasTransitiveRuntimeDependenciesOnItsClasspathWhenUsingKotlin() {
expectConfigurationCacheRequestedDeprecationWarning();
expectResolvableUsageIsAlreadyAllowedWarning();
String output = this.gradleBuild.build("compileAotTestJavaClasspath").getOutput();
assertThat(output).contains("org.jboss.logging" + File.separatorChar + "jboss-logging");
}
private void expectConfigurationCacheRequestedDeprecationWarning() {
this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.14")
.expectDeprecationMessages("The StartParameter.isConfigurationCacheRequested property has been deprecated");
}
private void expectResolvableUsageIsAlreadyAllowedWarning() {
this.gradleBuild.expectDeprecationWarningsWithAtLeastVersion("8.4")
.expectDeprecationMessages("The resolvable usage is already allowed on configuration "
+ "':aotRuntimeClasspath'. This behavior has been deprecated.");
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2012-present the original author or authors.
*
* 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
*
* https://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.
*/
plugins {
id 'org.springframework.boot'
id 'org.springframework.boot.aot'
id 'java'
id 'org.jetbrains.kotlin.jvm'
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.hibernate.orm:hibernate-core:6.1.1.Final"
}
task('compileAotJavaClasspath') {
doFirst {
tasks.findByName('compileAotJava').classpath.files.each { println it }
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2012-present the original author or authors.
*
* 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
*
* https://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.
*/
plugins {
id 'org.springframework.boot'
id 'org.springframework.boot.aot'
id 'java'
id "org.jetbrains.kotlin.jvm"
}
repositories {
mavenCentral()
maven {
url = 'repository'
}
}
configurations.all {
resolutionStrategy {
eachDependency {
if (it.requested.group == 'org.springframework.boot') {
it.useVersion project.bootVersion
}
}
}
}
dependencies {
implementation "org.hibernate.orm:hibernate-core:6.1.1.Final"
}
task('compileAotTestJavaClasspath') {
doFirst {
tasks.findByName('compileAotTestJava').classpath.files.each { println it }
}
}