Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
import org.gradle.util.GradleVersion;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

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

@SuppressWarnings({ "rawtypes", "unchecked" })
/**
* In Gradle < 9.1.0, we don't have access to {@link AttributeContainer#addAllLater},
* so we will just copy the attributes individually.
* @param from configuration from which to copy attributes
* @param to configuration to which attributes will be copied
*/
@SuppressWarnings({ "rawtypes", "unchecked", "NullAway" })
private static void copyAttributes(
@NonNull Provider<Configuration> from,
@NonNull Configuration to) {
if (GradleVersion.current().compareTo(GradleVersion.version("9.1.0")) < 0) {
to.attributes((attributes) -> {
AttributeContainer sourceAttributes = from.get().getAttributes();
for (Attribute attribute : sourceAttributes.keySet()) {
attributes.attributeProvider(attribute,
from.map((source) -> source.getAttributes().getAttribute(attribute)));
}
});
}
else {
to.getAttributes().addAllLater(from.map(Configuration::getAttributes).get());
}
}

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)));
}
Provider<Configuration> runtimeClasspath = project.getConfigurations()
.named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
project.getConfigurations().create(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME, (prc) -> {
copyAttributes(runtimeClasspath, prc);
prc.setExtendsFrom(runtimeClasspath.get().getExtendsFrom());
prc.setCanBeResolved(runtimeClasspath.get().isCanBeResolved());
prc.setCanBeConsumed(runtimeClasspath.get().isCanBeConsumed());
prc.shouldResolveConsistentlyWith(runtimeClasspath.get());
});
productionRuntimeClasspath.setExtendsFrom(runtimeClasspath.getExtendsFrom());
productionRuntimeClasspath.setCanBeResolved(runtimeClasspath.isCanBeResolved());
productionRuntimeClasspath.setCanBeConsumed(runtimeClasspath.isCanBeConsumed());
productionRuntimeClasspath.shouldResolveConsistentlyWith(runtimeClasspath);
}

private void configureDevelopmentOnlyConfiguration(Project project) {
Configuration developmentOnly = project.getConfigurations()
.create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME);
developmentOnly
.setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools.");
Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final var runtimeClasspath = project.getConfigurations().named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
final var developmentOnly = project.getConfigurations()
.create(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME, (dev) -> {
dev.setDescription("Configuration for development-only dependencies such as Spring Boot's DevTools.");
dev.setCanBeConsumed(false);
copyAttributes(runtimeClasspath, dev);
});
runtimeClasspath.configure((r) -> r.extendsFrom(developmentOnly));

runtimeClasspath.extendsFrom(developmentOnly);
}

private void configureTestAndDevelopmentOnlyConfiguration(Project project) {
final var runtimeClasspath = project.getConfigurations().named(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
Configuration testAndDevelopmentOnly = project.getConfigurations()
.create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME);
testAndDevelopmentOnly
.setDescription("Configuration for test and development-only dependencies such as Spring Boot's DevTools.");
Configuration runtimeClasspath = project.getConfigurations()
.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME);
runtimeClasspath.extendsFrom(testAndDevelopmentOnly);
Configuration testImplementation = project.getConfigurations()
.getByName(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME);
testImplementation.extendsFrom(testAndDevelopmentOnly);
.create(SpringBootPlugin.TEST_AND_DEVELOPMENT_ONLY_CONFIGURATION_NAME, (tado) -> {
tado.setDescription(
"Configuration for test and development-only dependencies such as Spring Boot's DevTools.");
tado.setCanBeConsumed(false);
copyAttributes(runtimeClasspath, tado);
});
runtimeClasspath.configure((it) -> it.extendsFrom(testAndDevelopmentOnly));
project.getConfigurations()
.named(JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME)
.configure((testImplementation) -> testImplementation.extendsFrom(testAndDevelopmentOnly));
}

private void configureSpringBootStarterTestToDependOnJUnitPlatformLauncher(Project project) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,24 @@ void productionRuntimeClasspathIsConfiguredWithAttributesThatMatchRuntimeClasspa
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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}")
}
Original file line number Diff line number Diff line change
@@ -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}")
}