Skip to content

Commit 6a432fc

Browse files
committed
copy configuration attributes from runtimeClasspath to developmentOnly and testAndDevelopmentOnly
fixes gh-51492 include refactors in the modified code to better comply with Gradle lazy APIs Signed-off-by: John Burns <wakingrufus@gmail.com>
1 parent 4c37d12 commit 6a432fc

4 files changed

Lines changed: 147 additions & 33 deletions

File tree

build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@
3636
import org.gradle.api.plugins.JavaPlugin;
3737
import org.gradle.api.plugins.JavaPluginExtension;
3838
import org.gradle.api.provider.Provider;
39-
import org.gradle.api.provider.ProviderFactory;
4039
import org.gradle.api.tasks.SourceSet;
4140
import org.gradle.api.tasks.SourceSetContainer;
4241
import org.gradle.api.tasks.TaskProvider;
4342
import org.gradle.api.tasks.bundling.Jar;
4443
import org.gradle.api.tasks.compile.JavaCompile;
4544
import org.gradle.jvm.toolchain.JavaToolchainService;
4645
import org.gradle.jvm.toolchain.JavaToolchainSpec;
46+
import org.gradle.util.GradleVersion;
47+
import org.jspecify.annotations.NonNull;
4748
import org.jspecify.annotations.Nullable;
4849

4950
import org.springframework.boot.gradle.dsl.SpringBootExtension;
@@ -281,48 +282,67 @@ private void configureAdditionalMetadataLocations(JavaCompile compile) {
281282
.ifPresent((locations) -> compile.doFirst(new AdditionalMetadataLocationsConfigurer(locations)));
282283
}
283284

284-
@SuppressWarnings({ "rawtypes", "unchecked" })
285+
/**
286+
* In Gradle < 9.1.0, we don't have access to {@link AttributeContainer#addAllLater},
287+
* so we will just copy the attributes individually.
288+
* @param from configuration from which to copy attributes
289+
* @param to configuration to which attributes will be copied
290+
*/
291+
@SuppressWarnings({ "rawtypes", "unchecked", "NullAway" })
292+
private static void copyAttributes(
293+
@NonNull Provider<Configuration> from,
294+
@NonNull Configuration to) {
295+
if (GradleVersion.current().compareTo(GradleVersion.version("9.1.0")) < 0) {
296+
to.attributes((attributes) -> {
297+
AttributeContainer sourceAttributes = from.get().getAttributes();
298+
for (Attribute attribute : sourceAttributes.keySet()) {
299+
attributes.attributeProvider(attribute,
300+
from.map((source) -> source.getAttributes().getAttribute(attribute)));
301+
}
302+
});
303+
}
304+
else {
305+
to.getAttributes().addAllLater(from.map(Configuration::getAttributes).get());
306+
}
307+
}
308+
285309
private void configureProductionRuntimeClasspathConfiguration(Project project) {
286-
Configuration productionRuntimeClasspath = project.getConfigurations()
287-
.create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
288-
Configuration runtimeClasspath = project.getConfigurations()
289-
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
290-
productionRuntimeClasspath.attributes((attributes) -> {
291-
ProviderFactory providers = project.getProviders();
292-
AttributeContainer sourceAttributes = runtimeClasspath.getAttributes();
293-
for (Attribute attribute : sourceAttributes.keySet()) {
294-
attributes.attributeProvider(attribute,
295-
providers.provider(() -> sourceAttributes.getAttribute(attribute)));
296-
}
310+
Provider<Configuration> runtimeClasspath = project.getConfigurations()
311+
.named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
312+
project.getConfigurations().create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME, (prc) -> {
313+
copyAttributes(runtimeClasspath, prc);
314+
prc.setExtendsFrom(runtimeClasspath.get().getExtendsFrom());
315+
prc.setCanBeResolved(runtimeClasspath.get().isCanBeResolved());
316+
prc.setCanBeConsumed(runtimeClasspath.get().isCanBeConsumed());
317+
prc.shouldResolveConsistentlyWith(runtimeClasspath.get());
297318
});
298-
productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom());
299-
productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved());
300-
productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed());
301-
productionRuntimeClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
302319
}
303320

304321
private void configureDevelopmentOnlyConfiguration(Project project) {
305-
Configuration developmentOnly = project.getConfigurations()
306-
.create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
307-
developmentOnly
308-
.setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools.");
309-
Configuration runtimeClasspath = project.getConfigurations()
310-
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
322+
final var runtimeClasspath = project.getConfigurations().named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
323+
final var developmentOnly = project.getConfigurations()
324+
.create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME, (dev) -> {
325+
dev.setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools.");
326+
dev.setCanBeConsumed(false);
327+
copyAttributes(runtimeClasspath, dev);
328+
});
329+
runtimeClasspath.configure((r) -> r.extendsFrom(developmentOnly));
311330

312-
runtimeClasspath.extendsFrom(developmentOnly);
313331
}
314332

315333
private void configureTestAndDevelopmentOnlyConfiguration(Project project) {
334+
final var runtimeClasspath = project.getConfigurations().named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
316335
Configuration testAndDevelopmentOnly = project.getConfigurations()
317-
.create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME);
318-
testAndDevelopmentOnly
319-
.setDescription("Configuration for test and development-only dependencies such as Spring Boot's DevTools.");
320-
Configuration runtimeClasspath = project.getConfigurations()
321-
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
322-
runtimeClasspath.extendsFrom(testAndDevelopmentOnly);
323-
Configuration testImplementation = project.getConfigurations()
324-
.getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME);
325-
testImplementation.extendsFrom(testAndDevelopmentOnly);
336+
.create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME, (tado) -> {
337+
tado.setDescription(
338+
"Configuration for test and development-only dependencies such as Spring Boot's DevTools.");
339+
tado.setCanBeConsumed(false);
340+
copyAttributes(runtimeClasspath, tado);
341+
});
342+
runtimeClasspath.configure((it) -> it.extendsFrom(testAndDevelopmentOnly));
343+
project.getConfigurations()
344+
.named(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME)
345+
.configure((testImplementation) -> testImplementation.extendsFrom(testAndDevelopmentOnly));
326346
}
327347

328348
private void configureSpringBootStarterTestToDependOnJUnitPlatformLauncher(Project project) {

build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,24 @@ void productionRuntimeClasspathIsConfiguredWithAttributesThatMatchRuntimeClasspa
213213
assertThat(output).contains("productionRuntimeClasspath: " + attributes);
214214
}
215215

216+
@TestTemplate
217+
void developmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath() {
218+
String output = this.gradleBuild.build("build").getOutput();
219+
Matcher matcher = Pattern.compile("runtimeClasspath: (\\[.*])").matcher(output);
220+
assertThat(matcher.find()).as("%s found in %s", matcher, output).isTrue();
221+
String attributes = matcher.group(1);
222+
assertThat(output).contains("developmentOnly: " + attributes);
223+
}
224+
225+
@TestTemplate
226+
void testAndDevelopmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath() {
227+
String output = this.gradleBuild.build("build").getOutput();
228+
Matcher matcher = Pattern.compile("runtimeClasspath: (\\[.*])").matcher(output);
229+
assertThat(matcher.find()).as("%s found in %s", matcher, output).isTrue();
230+
String attributes = matcher.group(1);
231+
assertThat(output).contains("testAndDevelopmentOnly: " + attributes);
232+
}
233+
216234
@TestTemplate
217235
void productionRuntimeClasspathIsConfiguredWithResolvabilityAndConsumabilityThatMatchesRuntimeClasspath() {
218236
String output = this.gradleBuild.build("build").getOutput();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
def collectAttributes(String configurationName) {
18+
def attributes = configurations.findByName(configurationName).attributes
19+
def keys = new TreeSet<>((a1, a2) -> a1.name.compareTo(a2.name))
20+
keys.addAll(attributes.keySet())
21+
keys.collect { key -> "${key}: ${attributes.getAttribute(key)}" }
22+
}
23+
24+
plugins {
25+
id 'org.springframework.boot' version '{version}'
26+
id 'java'
27+
}
28+
29+
springBoot {
30+
mainClass = "com.example.Main"
31+
}
32+
33+
gradle.taskGraph.whenReady {
34+
def runtimeClasspathAttributes = collectAttributes("runtimeClasspath")
35+
def developmentOnlyAttributes = collectAttributes("developmentOnly")
36+
println("runtimeClasspath: ${runtimeClasspathAttributes}")
37+
println("developmentOnly: ${developmentOnlyAttributes}")
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2012-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
def collectAttributes(String configurationName) {
18+
def attributes = configurations.findByName(configurationName).attributes
19+
def keys = new TreeSet<>((a1, a2) -> a1.name.compareTo(a2.name))
20+
keys.addAll(attributes.keySet())
21+
keys.collect { key -> "${key}: ${attributes.getAttribute(key)}" }
22+
}
23+
24+
plugins {
25+
id 'org.springframework.boot' version '{version}'
26+
id 'java'
27+
}
28+
29+
springBoot {
30+
mainClass = "com.example.Main"
31+
}
32+
33+
gradle.taskGraph.whenReady {
34+
def runtimeClasspathAttributes = collectAttributes("runtimeClasspath")
35+
def testAndDevelopmentOnlyAttributes = collectAttributes("testAndDevelopmentOnly")
36+
println("runtimeClasspath: ${runtimeClasspathAttributes}")
37+
println("testAndDevelopmentOnly: ${testAndDevelopmentOnlyAttributes}")
38+
}

0 commit comments

Comments
 (0)