From 03352b0a8ca78d5e8507d914458fbe08e74fc056 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 14 Feb 2022 20:03:11 +0000 Subject: [PATCH] Prohibit use of APIs that prevent task configuration avoidance Closes gh-29809 --- .../spring-boot-gradle-plugin/build.gradle | 1 + .../boot/gradle/plugin/MavenPluginAction.java | 12 +- .../TaskConfigurationAvoidanceTests.java | 156 ++++++++++++++++++ 3 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/TaskConfigurationAvoidanceTests.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle index 4b39b1953e8..23032f6e90f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/build.gradle @@ -27,6 +27,7 @@ dependencies { } testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) + testImplementation("com.tngtech.archunit:archunit-junit5:0.22.0") testImplementation("org.assertj:assertj-core") testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.mockito:mockito-core") diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MavenPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MavenPluginAction.java index 49001e0d4c4..65772274e31 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MavenPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MavenPluginAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -45,10 +45,12 @@ final class MavenPluginAction implements PluginApplicationAction { @Override public void execute(Project project) { - project.getTasks().withType(Upload.class, (upload) -> { - if (this.uploadTaskName.equals(upload.getName())) { - project.afterEvaluate((evaluated) -> clearConfigurationMappings(upload)); - } + project.afterEvaluate((evaluated) -> { + project.getTasks().withType(Upload.class).configureEach((upload) -> { + if (this.uploadTaskName.equals(upload.getName())) { + clearConfigurationMappings(upload); + } + }); }); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/TaskConfigurationAvoidanceTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/TaskConfigurationAvoidanceTests.java new file mode 100644 index 00000000000..e8c779803bc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/TaskConfigurationAvoidanceTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-2022 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.gradle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.Predicate; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaMethodCall; +import com.tngtech.archunit.core.domain.JavaType; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.core.importer.Location; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import org.gradle.api.Action; +import org.gradle.api.tasks.TaskCollection; +import org.gradle.api.tasks.TaskContainer; + +/** + * Tests that verify the plugin's compliance with task configuration avoidance. + * + * @author Andy Wilkinson + */ +@AnalyzeClasses(packages = "org.springframework.boot.gradle", + importOptions = TaskConfigurationAvoidanceTests.DoNotIncludeTests.class) +class TaskConfigurationAvoidanceTests { + + @ArchTest + void noApisThatCauseEagerTaskConfigurationShouldBeCalled(JavaClasses classes) { + ProhibitedMethods prohibited = new ProhibitedMethods(); + prohibited.on(TaskContainer.class).methodsNamed("create", "findByPath, getByPath").method("withType", + Class.class, Action.class); + prohibited.on(TaskCollection.class).methodsNamed("findByName", "getByName"); + ArchRuleDefinition.noClasses().should() + .callMethodWhere(DescribedPredicate.describe("it would cause eager task configuration", prohibited)) + .check(classes); + } + + static class DoNotIncludeTests implements ImportOption { + + @Override + public boolean includes(Location location) { + return !location.matches(Pattern.compile(".*Tests\\.class")); + } + + } + + private static final class ProhibitedMethods implements Predicate { + + private final List> prohibited = new ArrayList<>(); + + private ProhibitedConfigurer on(Class type) { + return new ProhibitedConfigurer(type); + } + + @Override + public boolean apply(JavaMethodCall methodCall) { + for (Predicate spec : this.prohibited) { + if (spec.apply(methodCall)) { + return true; + } + } + return false; + } + + private final class ProhibitedConfigurer { + + private final Class type; + + private ProhibitedConfigurer(Class type) { + this.type = type; + } + + private ProhibitedConfigurer methodsNamed(String... names) { + for (String name : names) { + ProhibitedMethods.this.prohibited.add(new ProhibitMethodsNamed(this.type, name)); + } + return this; + } + + private ProhibitedConfigurer method(String name, Class... parameterTypes) { + ProhibitedMethods.this.prohibited + .add(new ProhibitMethod(this.type, name, Arrays.asList(parameterTypes))); + return this; + } + + } + + static class ProhibitMethodsNamed implements Predicate { + + private final Class owner; + + private final String name; + + ProhibitMethodsNamed(Class owner, String name) { + this.owner = owner; + this.name = name; + } + + @Override + public boolean apply(JavaMethodCall methodCall) { + return methodCall.getTargetOwner().isEquivalentTo(this.owner) && methodCall.getName().equals(this.name); + } + + } + + private static final class ProhibitMethod extends ProhibitMethodsNamed { + + private final List> parameterTypes; + + private ProhibitMethod(Class owner, String name, List> parameterTypes) { + super(owner, name); + this.parameterTypes = parameterTypes; + } + + @Override + public boolean apply(JavaMethodCall methodCall) { + return super.apply(methodCall) && match(methodCall.getTarget().getParameterTypes()); + } + + private boolean match(List callParameterTypes) { + if (this.parameterTypes.size() != callParameterTypes.size()) { + return false; + } + for (int i = 0; i < this.parameterTypes.size(); i++) { + if (!callParameterTypes.get(i).toErasure().isEquivalentTo(this.parameterTypes.get(i))) { + return false; + } + } + return true; + } + + } + + } + +}