From c46bef1858089db319d257ff0adca1d4ea0a518a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 16 Jun 2023 09:50:52 +0100 Subject: [PATCH] Protect against unconstrained dependencies in starters Closes gh-19775 --- ...athForUnconstrainedDirectDependencies.java | 78 +++++++++++++++++++ .../boot/build/starters/StarterPlugin.java | 13 ++++ 2 files changed, 91 insertions(+) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java diff --git a/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java new file mode 100644 index 0000000000..a48b140d0b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/classpath/CheckClasspathForUnconstrainedDirectDependencies.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 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.Set; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.component.ModuleComponentSelector; +import org.gradle.api.artifacts.result.DependencyResult; +import org.gradle.api.artifacts.result.ResolutionResult; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.TaskAction; + +/** + * Tasks to check that none of classpath's direct dependencies are unconstrained. + * + * @author Andy Wilkinson + */ +public class CheckClasspathForUnconstrainedDirectDependencies extends DefaultTask { + + private Configuration classpath; + + public CheckClasspathForUnconstrainedDirectDependencies() { + getOutputs().upToDateWhen((task) -> true); + } + + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + public void setClasspath(Configuration classpath) { + this.classpath = classpath; + } + + @TaskAction + void checkForUnconstrainedDirectDependencies() { + ResolutionResult resolutionResult = this.classpath.getIncoming().getResolutionResult(); + Set dependencies = resolutionResult.getRoot().getDependencies(); + Set unconstrainedDependencies = dependencies.stream() + .map(DependencyResult::getRequested) + .filter(ModuleComponentSelector.class::isInstance) + .map(ModuleComponentSelector.class::cast) + .map((selector) -> selector.getGroup() + ":" + selector.getModule()) + .collect(Collectors.toSet()); + Set constraints = resolutionResult.getAllDependencies() + .stream() + .filter(DependencyResult::isConstraint) + .map(DependencyResult::getRequested) + .filter(ModuleComponentSelector.class::isInstance) + .map(ModuleComponentSelector.class::cast) + .map((selector) -> selector.getGroup() + ":" + selector.getModule()) + .collect(Collectors.toSet()); + unconstrainedDependencies.removeAll(constraints); + if (!unconstrainedDependencies.isEmpty()) { + throw new GradleException("Found unconstrained direct dependencies: " + unconstrainedDependencies); + } + } + +} 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 846efd43be..fa28c3dfa5 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 @@ -33,6 +33,7 @@ import org.gradle.api.tasks.bundling.Jar; 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.CheckClasspathForUnconstrainedDirectDependencies; import org.springframework.boot.build.classpath.CheckClasspathForUnnecessaryExclusions; import org.springframework.util.StringUtils; @@ -63,6 +64,7 @@ public class StarterPlugin implements Plugin { (artifact) -> artifact.builtBy(starterMetadata)); createClasspathConflictsCheck(runtimeClasspath, project); createUnnecessaryExclusionsCheck(runtimeClasspath, project); + createUnconstrainedDirectDependenciesCheck(runtimeClasspath, project); configureJarManifest(project); } @@ -82,6 +84,17 @@ public class StarterPlugin implements Plugin { project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForUnnecessaryExclusions); } + private void createUnconstrainedDirectDependenciesCheck(Configuration classpath, Project project) { + CheckClasspathForUnconstrainedDirectDependencies checkClasspathForUnconstrainedDirectDependencies = project + .getTasks() + .create("check" + StringUtils.capitalize(classpath.getName() + "ForUnconstrainedDirectDependencies"), + CheckClasspathForUnconstrainedDirectDependencies.class); + checkClasspathForUnconstrainedDirectDependencies.setClasspath(classpath); + project.getTasks() + .getByName(JavaBasePlugin.CHECK_TASK_NAME) + .dependsOn(checkClasspathForUnconstrainedDirectDependencies); + } + private void configureJarManifest(Project project) { project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { jar.manifest((manifest) -> {