commit
04353e4961
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.build.architecture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Plugin} for verifying a project's architecture.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ArchitecturePlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().withType(JavaBasePlugin.class, (javaPlugin) -> registerTasks(project));
|
||||
}
|
||||
|
||||
private void registerTasks(Project project) {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
List<TaskProvider<PackageTangleCheck>> packageTangleChecks = new ArrayList<>();
|
||||
for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) {
|
||||
TaskProvider<PackageTangleCheck> checkPackageTangles = project.getTasks().register(
|
||||
"checkForPackageTangles" + StringUtils.capitalize(sourceSet.getName()), PackageTangleCheck.class,
|
||||
(task) -> {
|
||||
task.setClasses(sourceSet.getOutput().getClassesDirs());
|
||||
task.setDescription("Checks the classes of the " + sourceSet.getName()
|
||||
+ " source set for package tangles.");
|
||||
task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
});
|
||||
packageTangleChecks.add(checkPackageTangles);
|
||||
}
|
||||
if (!packageTangleChecks.isEmpty()) {
|
||||
TaskProvider<Task> checkTask = project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkTask.configure((check) -> check.dependsOn(packageTangleChecks));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 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.build.architecture;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import com.tngtech.archunit.library.dependencies.SliceRule;
|
||||
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.file.DirectoryProperty;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.file.FileTree;
|
||||
import org.gradle.api.tasks.IgnoreEmptyDirectories;
|
||||
import org.gradle.api.tasks.InputFiles;
|
||||
import org.gradle.api.tasks.Internal;
|
||||
import org.gradle.api.tasks.OutputDirectory;
|
||||
import org.gradle.api.tasks.PathSensitive;
|
||||
import org.gradle.api.tasks.PathSensitivity;
|
||||
import org.gradle.api.tasks.SkipWhenEmpty;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* {@link Task} that checks for package tangles.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class PackageTangleCheck extends DefaultTask {
|
||||
|
||||
private FileCollection classes;
|
||||
|
||||
public PackageTangleCheck() {
|
||||
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void checkForPackageTangles() throws IOException {
|
||||
JavaClasses javaClasses = new ClassFileImporter()
|
||||
.importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList()));
|
||||
SliceRule freeOfCycles = SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles();
|
||||
EvaluationResult result = freeOfCycles.evaluate(javaClasses);
|
||||
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
|
||||
outputFile.getParentFile().mkdirs();
|
||||
if (result.hasViolation()) {
|
||||
Files.write(outputFile.toPath(), result.getFailureReport().toString().getBytes(StandardCharsets.UTF_8),
|
||||
StandardOpenOption.CREATE);
|
||||
FileWriter writer = new FileWriter(outputFile);
|
||||
FileCopyUtils.copy(result.getFailureReport().toString(), writer);
|
||||
throw new GradleException("Package tangle check failed. See '" + outputFile + "' for details.");
|
||||
}
|
||||
else {
|
||||
outputFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void setClasses(FileCollection classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Internal
|
||||
public FileCollection getClasses() {
|
||||
return this.classes;
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
@IgnoreEmptyDirectories
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
final FileTree getInputClasses() {
|
||||
return this.classes.getAsFileTree();
|
||||
}
|
||||
|
||||
@OutputDirectory
|
||||
public abstract DirectoryProperty getOutputDirectory();
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.build.architecture;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link PackageTangleCheck}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class PackageTangleCheckTests {
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception {
|
||||
prepareTask("tangled", (packageTangleCheck) -> {
|
||||
assertThatExceptionOfType(GradleException.class)
|
||||
.isThrownBy(() -> packageTangleCheck.checkForPackageTangles());
|
||||
assertThat(
|
||||
new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt")
|
||||
.length()).isGreaterThan(0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception {
|
||||
prepareTask("untangled", (packageTangleCheck) -> {
|
||||
packageTangleCheck.checkForPackageTangles();
|
||||
assertThat(
|
||||
new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt")
|
||||
.length()).isEqualTo(0);
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareTask(String classes, Callback<PackageTangleCheck> callback) throws Exception {
|
||||
File projectDir = new File(this.temp, "project");
|
||||
projectDir.mkdirs();
|
||||
copyClasses(classes, projectDir);
|
||||
Project project = ProjectBuilder.builder().withProjectDir(projectDir).build();
|
||||
PackageTangleCheck packageTangleCheck = project.getTasks().create("checkForPackageTangles",
|
||||
PackageTangleCheck.class, (task) -> task.setClasses(project.files("classes")));
|
||||
callback.accept(packageTangleCheck);
|
||||
}
|
||||
|
||||
private void copyClasses(String name, File projectDir) throws IOException {
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name);
|
||||
FileSystemUtils.copyRecursively(root.getFile(),
|
||||
new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name));
|
||||
|
||||
}
|
||||
|
||||
private interface Callback<T> {
|
||||
|
||||
void accept(T item) throws Exception;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.build.architecture.tangled;
|
||||
|
||||
import org.springframework.boot.build.architecture.tangled.sub.TangledTwo;
|
||||
|
||||
public final class TangledOne {
|
||||
|
||||
public static final String ID = TangledTwo.class.getName() + "One";
|
||||
|
||||
private TangledOne() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.build.architecture.tangled.sub;
|
||||
|
||||
import org.springframework.boot.build.architecture.tangled.TangledOne;
|
||||
|
||||
public final class TangledTwo {
|
||||
|
||||
public static final String ID = TangledOne.ID + "-Two";
|
||||
|
||||
private TangledTwo() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.build.architecture.untangled;
|
||||
|
||||
import org.springframework.boot.build.architecture.untangled.sub.UntangledTwo;
|
||||
|
||||
public final class UntangledOne {
|
||||
|
||||
public static final String ID = UntangledTwo.class.getName() + "One";
|
||||
|
||||
private UntangledOne() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.build.architecture.untangled.sub;
|
||||
|
||||
public final class UntangledTwo {
|
||||
|
||||
public static final String ID = "Two";
|
||||
|
||||
private UntangledTwo() {
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue