Skip to content

Commit bb3cf6c

Browse files
nosanwilkinsona
authored andcommitted
Use ClassLoader with ArchitectureCheck
Prior to this commit, certain rules, like BeanPostProcessor, did not work with external classes. This commit ensures that ArchRules are executed within a context ClassLoader that includes all classes from the compile classpath. See gh-45202 Signed-off-by: Dmytro Nosan <[email protected]>
1 parent 482f56d commit bb3cf6c

File tree

7 files changed

+190
-142
lines changed

7 files changed

+190
-142
lines changed

Diff for: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java

+39-9
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.net.URL;
22+
import java.net.URLClassLoader;
2123
import java.nio.file.Files;
2224
import java.nio.file.Path;
2325
import java.nio.file.StandardOpenOption;
26+
import java.util.ArrayList;
2427
import java.util.Collections;
2528
import java.util.List;
29+
import java.util.concurrent.Callable;
2630
import java.util.function.Supplier;
2731
import java.util.stream.Stream;
2832

@@ -33,11 +37,13 @@
3337
import org.gradle.api.DefaultTask;
3438
import org.gradle.api.Task;
3539
import org.gradle.api.Transformer;
40+
import org.gradle.api.file.ConfigurableFileCollection;
3641
import org.gradle.api.file.DirectoryProperty;
3742
import org.gradle.api.file.FileCollection;
3843
import org.gradle.api.file.FileTree;
3944
import org.gradle.api.provider.ListProperty;
4045
import org.gradle.api.provider.Property;
46+
import org.gradle.api.tasks.Classpath;
4147
import org.gradle.api.tasks.IgnoreEmptyDirectories;
4248
import org.gradle.api.tasks.Input;
4349
import org.gradle.api.tasks.InputFiles;
@@ -58,6 +64,7 @@
5864
* @author Scott Frederick
5965
* @author Ivan Malutin
6066
* @author Phillip Webb
67+
* @author Dmytro Nosan
6168
*/
6269
public abstract class ArchitectureCheck extends DefaultTask {
6370

@@ -80,14 +87,17 @@ private List<String> asDescriptions(List<ArchRule> rules) {
8087
}
8188

8289
@TaskAction
83-
void checkArchitecture() throws IOException {
84-
JavaClasses javaClasses = new ClassFileImporter().importPaths(classFilesPaths());
85-
List<EvaluationResult> violations = evaluate(javaClasses).filter(EvaluationResult::hasViolation).toList();
86-
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
87-
writeViolationReport(violations, outputFile);
88-
if (!violations.isEmpty()) {
89-
throw new VerificationException("Architecture check failed. See '" + outputFile + "' for details.");
90-
}
90+
void checkArchitecture() throws Exception {
91+
withCompileClasspath(() -> {
92+
JavaClasses javaClasses = new ClassFileImporter().importPaths(classFilesPaths());
93+
List<EvaluationResult> violations = evaluate(javaClasses).filter(EvaluationResult::hasViolation).toList();
94+
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile();
95+
writeViolationReport(violations, outputFile);
96+
if (!violations.isEmpty()) {
97+
throw new VerificationException("Architecture check failed. See '" + outputFile + "' for details.");
98+
}
99+
return null;
100+
});
91101
}
92102

93103
private List<Path> classFilesPaths() {
@@ -98,8 +108,24 @@ private Stream<EvaluationResult> evaluate(JavaClasses javaClasses) {
98108
return getRules().get().stream().map((rule) -> rule.evaluate(javaClasses));
99109
}
100110

111+
private void withCompileClasspath(Callable<?> callable) throws Exception {
112+
ClassLoader previous = Thread.currentThread().getContextClassLoader();
113+
try {
114+
List<URL> urls = new ArrayList<>();
115+
for (File file : getCompileClasspath().getFiles()) {
116+
urls.add(file.toURI().toURL());
117+
}
118+
ClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[0]), getClass().getClassLoader());
119+
Thread.currentThread().setContextClassLoader(classLoader);
120+
callable.call();
121+
}
122+
finally {
123+
Thread.currentThread().setContextClassLoader(previous);
124+
}
125+
}
126+
101127
private void writeViolationReport(List<EvaluationResult> violations, File outputFile) throws IOException {
102-
outputFile.getParentFile().mkdirs();
128+
Files.createDirectories(outputFile.getParentFile().toPath());
103129
StringBuilder report = new StringBuilder();
104130
for (EvaluationResult violation : violations) {
105131
report.append(violation.getFailureReport());
@@ -126,6 +152,10 @@ final FileTree getInputClasses() {
126152
return this.classes.getAsFileTree();
127153
}
128154

155+
@InputFiles
156+
@Classpath
157+
public abstract ConfigurableFileCollection getCompileClasspath();
158+
129159
@Optional
130160
@InputFiles
131161
@PathSensitive(PathSensitivity.RELATIVE)

Diff for: buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@ private void registerTasks(Project project) {
4949
TaskProvider<ArchitectureCheck> checkPackageTangles = project.getTasks()
5050
.register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class,
5151
(task) -> {
52+
task.getCompileClasspath().from(sourceSet.getCompileClasspath());
5253
task.setClasses(sourceSet.getOutput().getClassesDirs());
5354
task.getResourcesDirectory().set(sourceSet.getOutput().getResourcesDir());
5455
task.dependsOn(sourceSet.getProcessResourcesTaskName());

0 commit comments

Comments
 (0)