diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index a78263c8b8d..7a858a08080 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -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) { diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java index 826797565bb..8816d8c2357 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests.java @@ -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(); diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-developmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-developmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle new file mode 100644 index 00000000000..b92efe3679c --- /dev/null +++ b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-developmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle @@ -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}") +} \ No newline at end of file diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testAndDevelopmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testAndDevelopmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle new file mode 100644 index 00000000000..fed71c5c5ea --- /dev/null +++ b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/plugin/JavaPluginActionIntegrationTests-testAndDevelopmentOnlyIsConfiguredWithAttributesThatMatchRuntimeClasspath.gradle @@ -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}") +} \ No newline at end of file