diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java new file mode 100644 index 00000000000..7270812fe41 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnnecessaryExclusions.java @@ -0,0 +1,126 @@ +/* + * Copyright 2012-2021 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. + */ + +package org.springframework.boot.build.classpath; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ExcludeRule; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; + +/** + * A {@link Task} for checking the classpath for unnecessary exclusions. + * + * @author Andy Wilkinson + */ +public class CheckClasspathForUnnecessaryExclusions extends DefaultTask { + + private final Map> exclusionsByDependencyId = new TreeMap<>(); + + private final Map dependencyById = new HashMap<>(); + + private final Dependency platform; + + private final DependencyHandler dependencyHandler; + + private final ConfigurationContainer configurations; + + @Inject + public CheckClasspathForUnnecessaryExclusions(DependencyHandler dependencyHandler, + ConfigurationContainer configurations) { + this.dependencyHandler = getProject().getDependencies(); + this.configurations = getProject().getConfigurations(); + this.platform = this.dependencyHandler.create(this.dependencyHandler.platform(this.dependencyHandler + .project(Collections.singletonMap("path", ":spring-boot-project:spring-boot-dependencies")))); + getOutputs().upToDateWhen((task) -> true); + } + + public void setClasspath(Configuration classpath) { + this.exclusionsByDependencyId.clear(); + this.dependencyById.clear(); + classpath.getAllDependencies().all((dependency) -> { + if (dependency instanceof ModuleDependency) { + String dependencyId = dependency.getGroup() + ":" + dependency.getName(); + Set excludeRules = ((ModuleDependency) dependency).getExcludeRules(); + TreeSet exclusions = excludeRules.stream() + .map((rule) -> rule.getGroup() + ":" + rule.getModule()) + .collect(Collectors.toCollection(TreeSet::new)); + this.exclusionsByDependencyId.put(dependencyId, exclusions); + if (!exclusions.isEmpty()) { + this.dependencyById.put(dependencyId, getProject().getDependencies().create(dependencyId)); + } + } + }); + } + + @Input + Map> getExclusionsByDependencyId() { + return this.exclusionsByDependencyId; + } + + @TaskAction + public void checkForUnnecessaryExclusions() { + Map> unnecessaryExclusions = new HashMap<>(); + for (Entry> entry : this.exclusionsByDependencyId.entrySet()) { + String dependencyId = entry.getKey(); + Set exclusions = entry.getValue(); + if (!exclusions.isEmpty()) { + Dependency toCheck = this.dependencyById.get(dependencyId); + List dependencies = this.configurations.detachedConfiguration(toCheck, this.platform) + .getIncoming().getArtifacts().getArtifacts().stream().map((artifact) -> { + ModuleComponentIdentifier id = (ModuleComponentIdentifier) artifact.getId() + .getComponentIdentifier(); + return id.getGroup() + ":" + id.getModule(); + }).collect(Collectors.toList()); + exclusions.removeAll(dependencies); + if (!exclusions.isEmpty()) { + unnecessaryExclusions.put(dependencyId, exclusions); + } + } + } + if (!unnecessaryExclusions.isEmpty()) { + StringBuilder message = new StringBuilder("Unnecessary exclusions detected:"); + for (Entry> entry : unnecessaryExclusions.entrySet()) { + message.append(String.format("%n %s", entry.getKey())); + for (String exclusion : entry.getValue()) { + message.append(String.format("%n %s", exclusion)); + } + } + throw new GradleException(message.toString()); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java index d77f737139b..58494606dae 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/starters/StarterPlugin.java @@ -34,6 +34,7 @@ import org.springframework.boot.build.ConventionsPlugin; import org.springframework.boot.build.DeployedPlugin; import org.springframework.boot.build.classpath.CheckClasspathForConflicts; import org.springframework.boot.build.classpath.CheckClasspathForProhibitedDependencies; +import org.springframework.boot.build.classpath.CheckClasspathForUnnecessaryExclusions; import org.springframework.util.StringUtils; /** @@ -62,6 +63,7 @@ public class StarterPlugin implements Plugin { (artifact) -> artifact.builtBy(starterMetadata)); createClasspathConflictsCheck(runtimeClasspath, project); createProhibitedDependenciesCheck(runtimeClasspath, project); + createUnnecessaryExclusionsCheck(runtimeClasspath, project); configureJarManifest(project); } @@ -81,6 +83,14 @@ public class StarterPlugin implements Plugin { project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies); } + private void createUnnecessaryExclusionsCheck(Configuration classpath, Project project) { + CheckClasspathForUnnecessaryExclusions checkClasspathForUnnecessaryExclusions = project.getTasks().create( + "check" + StringUtils.capitalize(classpath.getName() + "ForUnnecessaryExclusions"), + CheckClasspathForUnnecessaryExclusions.class); + checkClasspathForUnnecessaryExclusions.setClasspath(classpath); + project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForUnnecessaryExclusions); + } + private void configureJarManifest(Project project) { project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { jar.manifest((manifest) -> { diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra-reactive/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra-reactive/build.gradle index ed3412b9f51..e619b60eef5 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra-reactive/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra-reactive/build.gradle @@ -7,8 +7,6 @@ description = "Starter for using Cassandra distributed database and Spring Data dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) api("org.springframework:spring-tx") - api("org.springframework.data:spring-data-cassandra") { - exclude group: "org.slf4j", module: "jcl-over-slf4j" - } + api("org.springframework.data:spring-data-cassandra") api("io.projectreactor:reactor-core") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra/build.gradle index 1f062dda7ce..def14ae7296 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-cassandra/build.gradle @@ -7,7 +7,5 @@ description = "Starter for using Cassandra distributed database and Spring Data dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) api("org.springframework:spring-tx") - api("org.springframework.data:spring-data-cassandra") { - exclude group: "org.slf4j", module: "jcl-over-slf4j" - } + api("org.springframework.data:spring-data-cassandra") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase-reactive/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase-reactive/build.gradle index 3526546e1b1..ec8c533472e 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase-reactive/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase-reactive/build.gradle @@ -8,7 +8,5 @@ dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) api("io.projectreactor:reactor-core") api("io.reactivex:rxjava-reactive-streams") - api("org.springframework.data:spring-data-couchbase") { - exclude group: "com.couchbase.client", module: "encryption" - } + api("org.springframework.data:spring-data-couchbase") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase/build.gradle index 1ab91a08771..17742429df2 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-data-couchbase/build.gradle @@ -6,7 +6,5 @@ description = "Starter for using Couchbase document-oriented database and Spring dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter")) - api("org.springframework.data:spring-data-couchbase") { - exclude group: "com.couchbase.client", module: "encryption" - } + api("org.springframework.data:spring-data-couchbase") } diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle index 74ba78aab07..12ba82f587e 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-test/build.gradle @@ -18,7 +18,5 @@ dependencies { api("org.skyscreamer:jsonassert") api("org.springframework:spring-core") api("org.springframework:spring-test") - api("org.xmlunit:xmlunit-core") { - exclude group: "javax.xml.bind", module: "jaxb-api" - } + api("org.xmlunit:xmlunit-core") }