Prohibit use of APIs that prevent task configuration avoidance

Closes gh-29809
This commit is contained in:
Andy Wilkinson 2022-02-14 20:03:11 +00:00
parent 865a829d29
commit 03352b0a8c
3 changed files with 164 additions and 5 deletions

View File

@ -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")

View File

@ -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);
}
});
});
}

View File

@ -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<JavaMethodCall> {
private final List<Predicate<JavaMethodCall>> prohibited = new ArrayList<>();
private ProhibitedConfigurer on(Class<?> type) {
return new ProhibitedConfigurer(type);
}
@Override
public boolean apply(JavaMethodCall methodCall) {
for (Predicate<JavaMethodCall> 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<JavaMethodCall> {
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<Class<?>> parameterTypes;
private ProhibitMethod(Class<?> owner, String name, List<Class<?>> 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<JavaType> 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;
}
}
}
}