Configure attributes on dev-only configurations

Closes gh-51492
This commit is contained in:
Andy Wilkinson
2026-09-08 13:56:26 +01:00
parent bb1ad4ce36
commit f956f588d5
4 changed files with 111 additions and 10 deletions
@@ -281,40 +281,46 @@ final class JavaPluginAction implements PluginApplicationAction {
.ifPresent((locations) -> compile.doFirst(new AdditionalMetadataLocationsConfigurer(locations)));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void configureProductionRuntimeClasspathConfiguration(Project project) {
Configuration productionRuntimeClasspath = project.getConfigurations()
.create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
productionRuntimeClasspath.attributes((attributes) -> {
ProviderFactory providers = project.getProviders();
AttributeContainer sourceAttributes = runtimeClasspath.getAttributes();
for (Attribute attribute : sourceAttributes.keySet()) {
attributes.attributeProvider(attribute,
providers.provider(() -> sourceAttributes.getAttribute(attribute)));
}
});
copyAttributes(project, runtimeClasspath, productionRuntimeClasspath);
productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom());
productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved());
productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed());
productionRuntimeClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void copyAttributes(Project project, Configuration sourceConfiguration, Configuration targetConfiguration) {
targetConfiguration.attributes((attributes) -> {
ProviderFactory providers = project.getProviders();
AttributeContainer sourceAttributes = sourceConfiguration.getAttributes();
for (Attribute attribute : sourceAttributes.keySet()) {
attributes.attributeProvider(attribute,
providers.provider(() -> sourceAttributes.getAttribute(attribute)));
}
});
}
private void configureDevelopmentOnlyConfiguration(Project project) {
Configuration developmentOnly = project.getConfigurations()
.create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
developmentOnly.setCanBeConsumed(false);
developmentOnly
.setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools.");
Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
runtimeClasspath.extendsFrom(developmentOnly);
copyAttributes(project, runtimeClasspath, developmentOnly);
}
private void configureTestAndDevelopmentOnlyConfiguration(Project project) {
Configuration testAndDevelopmentOnly = project.getConfigurations()
.create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME);
testAndDevelopmentOnly.setCanBeConsumed(false);
testAndDevelopmentOnly
.setDescription("Configuration for test and development-only dependencies such as Spring Boot's DevTools.");
Configuration runtimeClasspath = project.getConfigurations()
@@ -323,6 +329,7 @@ final class JavaPluginAction implements PluginApplicationAction {
Configuration testImplementation = project.getConfigurations()
.getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME);
testImplementation.extendsFrom(testAndDevelopmentOnly);
copyAttributes(project, runtimeClasspath, testAndDevelopmentOnly);
}
private void configureSpringBootStarterTestToDependOnJUnitPlatformLauncher(Project project) {
@@ -213,6 +213,24 @@ class JavaPluginActionIntegrationTests {
assertThat(output).contains("productionRuntimeClasspath: " + attributes);
}
@TestTemplate
void developmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath() {
String output = this.gradleBuild.build("build").getOutput();
Matcher matcher = Pattern.compile("runtimeClasspath: (\\[.*])").matcher(output);
assertThat(matcher.find()).as("%s found in %s", matcher, output).isTrue();
String attributes = matcher.group(1);
assertThat(output).contains("developmentOnly: " + attributes);
}
@TestTemplate
void testAndDevelopmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath() {
String output = this.gradleBuild.build("build").getOutput();
Matcher matcher = Pattern.compile("runtimeClasspath: (\\[.*])").matcher(output);
assertThat(matcher.find()).as("%s found in %s", matcher, output).isTrue();
String attributes = matcher.group(1);
assertThat(output).contains("testAndDevelopmentOnly: " + attributes);
}
@TestTemplate
void productionRuntimeClasspathIsConfiguredWithResolvabilityAndConsumabilityThatMatchesRuntimeClasspath() {
String output = this.gradleBuild.build("build").getOutput();
@@ -0,0 +1,38 @@
/*
* 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.
*/
def collectAttributes(String configurationName) {
def attributes = configurations.findByName(configurationName).attributes
def keys = new TreeSet<>((a1, a2) -> a1.name.compareTo(a2.name))
keys.addAll(attributes.keySet())
keys.collect { key -> "${key}: ${attributes.getAttribute(key)}" }
}
plugins {
id 'org.springframework.boot' version '{version}'
id 'java'
}
springBoot {
mainClass = "com.example.Main"
}
gradle.taskGraph.whenReady {
def runtimeClasspathAttributes = collectAttributes("runtimeClasspath")
def developmentOnlyAttributes = collectAttributes("developmentOnly")
println("runtimeClasspath: ${runtimeClasspathAttributes}")
println("developmentOnly: ${developmentOnlyAttributes}")
}
@@ -0,0 +1,38 @@
/*
* 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.
*/
def collectAttributes(String configurationName) {
def attributes = configurations.findByName(configurationName).attributes
def keys = new TreeSet<>((a1, a2) -> a1.name.compareTo(a2.name))
keys.addAll(attributes.keySet())
keys.collect { key -> "${key}: ${attributes.getAttribute(key)}" }
}
plugins {
id 'org.springframework.boot' version '{version}'
id 'java'
}
springBoot {
mainClass = "com.example.Main"
}
gradle.taskGraph.whenReady {
def runtimeClasspathAttributes = collectAttributes("runtimeClasspath")
def testAndDevelopmentOnlyAttributes = collectAttributes("testAndDevelopmentOnly")
println("runtimeClasspath: ${runtimeClasspathAttributes}")
println("testAndDevelopmentOnly: ${testAndDevelopmentOnlyAttributes}")
}