13 changed files with 692 additions and 161 deletions
@ -0,0 +1,135 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 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.autoconfigure; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UncheckedIOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Objects; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.asm.AnnotationVisitor; |
||||||
|
import org.springframework.asm.ClassReader; |
||||||
|
import org.springframework.asm.ClassVisitor; |
||||||
|
import org.springframework.asm.SpringAsmInfo; |
||||||
|
import org.springframework.asm.Type; |
||||||
|
|
||||||
|
/** |
||||||
|
* An {@code @AutoConfiguration} class. |
||||||
|
* |
||||||
|
* @param name name of the auto-configuration class
|
||||||
|
* @param before values of the {@code before} attribute |
||||||
|
* @param beforeName values of the {@code beforeName} attribute |
||||||
|
* @param after values of the {@code after} attribute |
||||||
|
* @param afterName values of the {@code afterName} attribute |
||||||
|
* @author Andy Wilkinson |
||||||
|
*/ |
||||||
|
public record AutoConfigurationClass(String name, List<String> before, List<String> beforeName, List<String> after, |
||||||
|
List<String> afterName) { |
||||||
|
|
||||||
|
private AutoConfigurationClass(String name, Map<String, List<String>> attributes) { |
||||||
|
this(name, attributes.getOrDefault("before", Collections.emptyList()), |
||||||
|
attributes.getOrDefault("beforeName", Collections.emptyList()), |
||||||
|
attributes.getOrDefault("after", Collections.emptyList()), |
||||||
|
attributes.getOrDefault("afterName", Collections.emptyList())); |
||||||
|
} |
||||||
|
|
||||||
|
static AutoConfigurationClass of(File classFile) { |
||||||
|
try (FileInputStream input = new FileInputStream(classFile)) { |
||||||
|
ClassReader classReader = new ClassReader(input); |
||||||
|
AutoConfigurationClassVisitor visitor = new AutoConfigurationClassVisitor(); |
||||||
|
classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); |
||||||
|
return visitor.autoConfigurationClass; |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final class AutoConfigurationClassVisitor extends ClassVisitor { |
||||||
|
|
||||||
|
private AutoConfigurationClass autoConfigurationClass; |
||||||
|
|
||||||
|
private String name; |
||||||
|
|
||||||
|
private AutoConfigurationClassVisitor() { |
||||||
|
super(SpringAsmInfo.ASM_VERSION); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void visit(int version, int access, String name, String signature, String superName, |
||||||
|
String[] interfaces) { |
||||||
|
this.name = Type.getObjectType(name).getClassName(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { |
||||||
|
String annotationClassName = Type.getType(descriptor).getClassName(); |
||||||
|
if ("org.springframework.boot.autoconfigure.AutoConfiguration".equals(annotationClassName)) { |
||||||
|
return new AutoConfigurationAnnotationVisitor(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private final class AutoConfigurationAnnotationVisitor extends AnnotationVisitor { |
||||||
|
|
||||||
|
private Map<String, List<String>> attributes = new HashMap<>(); |
||||||
|
|
||||||
|
private static final Set<String> INTERESTING_ATTRIBUTES = Set.of("before", "beforeName", "after", |
||||||
|
"afterName"); |
||||||
|
|
||||||
|
private AutoConfigurationAnnotationVisitor() { |
||||||
|
super(SpringAsmInfo.ASM_VERSION); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void visitEnd() { |
||||||
|
AutoConfigurationClassVisitor.this.autoConfigurationClass = new AutoConfigurationClass( |
||||||
|
AutoConfigurationClassVisitor.this.name, this.attributes); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public AnnotationVisitor visitArray(String attributeName) { |
||||||
|
if (INTERESTING_ATTRIBUTES.contains(attributeName)) { |
||||||
|
return new AnnotationVisitor(SpringAsmInfo.ASM_VERSION) { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void visit(String name, Object value) { |
||||||
|
if (value instanceof Type type) { |
||||||
|
value = type.getClassName(); |
||||||
|
} |
||||||
|
AutoConfigurationAnnotationVisitor.this.attributes |
||||||
|
.computeIfAbsent(attributeName, (n) -> new ArrayList<>()) |
||||||
|
.add(Objects.toString(value)); |
||||||
|
} |
||||||
|
|
||||||
|
}; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 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.autoconfigure; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UncheckedIOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask; |
||||||
|
import org.gradle.api.Task; |
||||||
|
import org.gradle.api.file.FileCollection; |
||||||
|
import org.gradle.api.file.FileTree; |
||||||
|
import org.gradle.api.tasks.InputFiles; |
||||||
|
import org.gradle.api.tasks.PathSensitive; |
||||||
|
import org.gradle.api.tasks.PathSensitivity; |
||||||
|
import org.gradle.api.tasks.SkipWhenEmpty; |
||||||
|
|
||||||
|
/** |
||||||
|
* A {@link Task} that uses a project's auto-configuration imports. |
||||||
|
* |
||||||
|
* @author Andy Wilkinson |
||||||
|
*/ |
||||||
|
public abstract class AutoConfigurationImportsTask extends DefaultTask { |
||||||
|
|
||||||
|
static final String IMPORTS_FILE = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"; |
||||||
|
|
||||||
|
private FileCollection sourceFiles = getProject().getObjects().fileCollection(); |
||||||
|
|
||||||
|
@InputFiles |
||||||
|
@SkipWhenEmpty |
||||||
|
@PathSensitive(PathSensitivity.RELATIVE) |
||||||
|
public FileTree getSource() { |
||||||
|
return this.sourceFiles.getAsFileTree().matching((filter) -> filter.include(IMPORTS_FILE)); |
||||||
|
} |
||||||
|
|
||||||
|
public void setSource(Object source) { |
||||||
|
this.sourceFiles = getProject().getObjects().fileCollection().from(source); |
||||||
|
} |
||||||
|
|
||||||
|
protected List<String> loadImports() { |
||||||
|
File importsFile = getSource().getSingleFile(); |
||||||
|
try { |
||||||
|
return Files.readAllLines(importsFile.toPath()); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,212 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2025 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.autoconfigure; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UncheckedIOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.nio.file.StandardOpenOption; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.TreeMap; |
||||||
|
import java.util.jar.JarEntry; |
||||||
|
import java.util.jar.JarFile; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import org.gradle.api.file.DirectoryProperty; |
||||||
|
import org.gradle.api.file.FileCollection; |
||||||
|
import org.gradle.api.provider.SetProperty; |
||||||
|
import org.gradle.api.tasks.Classpath; |
||||||
|
import org.gradle.api.tasks.OutputDirectory; |
||||||
|
import org.gradle.api.tasks.TaskAction; |
||||||
|
import org.gradle.api.tasks.VerificationException; |
||||||
|
import org.gradle.language.base.plugins.LifecycleBasePlugin; |
||||||
|
|
||||||
|
/** |
||||||
|
* Task to check a project's {@code @AutoConfiguration} classes. |
||||||
|
* |
||||||
|
* @author Andy Wilkinson |
||||||
|
*/ |
||||||
|
public abstract class CheckAutoConfigurationClasses extends AutoConfigurationImportsTask { |
||||||
|
|
||||||
|
private FileCollection classpath = getProject().getObjects().fileCollection(); |
||||||
|
|
||||||
|
private FileCollection optionalDependencies = getProject().getObjects().fileCollection(); |
||||||
|
|
||||||
|
private FileCollection requiredDependencies = getProject().getObjects().fileCollection(); |
||||||
|
|
||||||
|
private SetProperty<String> optionalDependencyClassNames = getProject().getObjects().setProperty(String.class); |
||||||
|
|
||||||
|
private SetProperty<String> requiredDependencyClassNames = getProject().getObjects().setProperty(String.class); |
||||||
|
|
||||||
|
public CheckAutoConfigurationClasses() { |
||||||
|
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); |
||||||
|
setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); |
||||||
|
this.optionalDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.optionalDependencies))); |
||||||
|
this.requiredDependencyClassNames.set(getProject().provider(() -> classNamesOf(this.requiredDependencies))); |
||||||
|
} |
||||||
|
|
||||||
|
private static List<String> classNamesOf(FileCollection classpath) { |
||||||
|
return classpath.getFiles().stream().flatMap((file) -> { |
||||||
|
try (JarFile jarFile = new JarFile(file)) { |
||||||
|
return Collections.list(jarFile.entries()) |
||||||
|
.stream() |
||||||
|
.filter((entry) -> !entry.isDirectory()) |
||||||
|
.map(JarEntry::getName) |
||||||
|
.filter((entryName) -> entryName.endsWith(".class")) |
||||||
|
.map((entryName) -> entryName.substring(0, entryName.length() - ".class".length())) |
||||||
|
.map((entryName) -> entryName.replace("/", ".")); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
}).toList(); |
||||||
|
} |
||||||
|
|
||||||
|
@Classpath |
||||||
|
public FileCollection getClasspath() { |
||||||
|
return this.classpath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setClasspath(Object classpath) { |
||||||
|
this.classpath = getProject().getObjects().fileCollection().from(classpath); |
||||||
|
} |
||||||
|
|
||||||
|
@Classpath |
||||||
|
public FileCollection getOptionalDependencies() { |
||||||
|
return this.optionalDependencies; |
||||||
|
} |
||||||
|
|
||||||
|
public void setOptionalDependencies(Object classpath) { |
||||||
|
this.optionalDependencies = getProject().getObjects().fileCollection().from(classpath); |
||||||
|
} |
||||||
|
|
||||||
|
@Classpath |
||||||
|
public FileCollection getRequiredDependencies() { |
||||||
|
return this.requiredDependencies; |
||||||
|
} |
||||||
|
|
||||||
|
public void setRequiredDependencies(Object classpath) { |
||||||
|
this.requiredDependencies = getProject().getObjects().fileCollection().from(classpath); |
||||||
|
} |
||||||
|
|
||||||
|
@OutputDirectory |
||||||
|
public abstract DirectoryProperty getOutputDirectory(); |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
void execute() { |
||||||
|
Map<String, List<String>> problems = new TreeMap<>(); |
||||||
|
Set<String> optionalOnlyClassNames = new HashSet<>(this.optionalDependencyClassNames.get()); |
||||||
|
Set<String> requiredClassNames = this.requiredDependencyClassNames.get(); |
||||||
|
optionalOnlyClassNames.removeAll(requiredClassNames); |
||||||
|
classFiles().forEach((classFile) -> { |
||||||
|
AutoConfigurationClass autoConfigurationClass = AutoConfigurationClass.of(classFile); |
||||||
|
if (autoConfigurationClass != null) { |
||||||
|
check(autoConfigurationClass, optionalOnlyClassNames, requiredClassNames, problems); |
||||||
|
} |
||||||
|
}); |
||||||
|
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); |
||||||
|
writeReport(problems, outputFile); |
||||||
|
if (!problems.isEmpty()) { |
||||||
|
throw new VerificationException( |
||||||
|
"Auto-configuration class check failed. See '%s' for details".formatted(outputFile)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private List<File> classFiles() { |
||||||
|
List<File> classFiles = new ArrayList<>(); |
||||||
|
for (File root : this.classpath.getFiles()) { |
||||||
|
try (Stream<Path> files = Files.walk(root.toPath())) { |
||||||
|
files.forEach((file) -> { |
||||||
|
if (Files.isRegularFile(file) && file.getFileName().toString().endsWith(".class")) { |
||||||
|
classFiles.add(file.toFile()); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
return classFiles; |
||||||
|
} |
||||||
|
|
||||||
|
private void check(AutoConfigurationClass autoConfigurationClass, Set<String> optionalOnlyClassNames, |
||||||
|
Set<String> requiredClassNames, Map<String, List<String>> problems) { |
||||||
|
if (!autoConfigurationClass.name().endsWith("AutoConfiguration")) { |
||||||
|
problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) |
||||||
|
.add("Name of a class annotated with @AutoConfiguration should end with AutoConfiguration"); |
||||||
|
} |
||||||
|
autoConfigurationClass.before().forEach((before) -> { |
||||||
|
if (optionalOnlyClassNames.contains(before)) { |
||||||
|
problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) |
||||||
|
.add("before '%s' is from an optional dependency and should be declared in beforeName" |
||||||
|
.formatted(before)); |
||||||
|
} |
||||||
|
}); |
||||||
|
autoConfigurationClass.beforeName().forEach((beforeName) -> { |
||||||
|
if (!optionalOnlyClassNames.contains(beforeName)) { |
||||||
|
String problem = requiredClassNames.contains(beforeName) |
||||||
|
? "beforeName '%s' is from a required dependency and should be declared in before" |
||||||
|
.formatted(beforeName) |
||||||
|
: "beforeName '%s' not found".formatted(beforeName); |
||||||
|
problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); |
||||||
|
} |
||||||
|
}); |
||||||
|
autoConfigurationClass.after().forEach((after) -> { |
||||||
|
if (optionalOnlyClassNames.contains(after)) { |
||||||
|
problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()) |
||||||
|
.add("after '%s' is from an optional dependency and should be declared in afterName" |
||||||
|
.formatted(after)); |
||||||
|
} |
||||||
|
}); |
||||||
|
autoConfigurationClass.afterName().forEach((afterName) -> { |
||||||
|
if (!optionalOnlyClassNames.contains(afterName)) { |
||||||
|
String problem = requiredClassNames.contains(afterName) |
||||||
|
? "afterName '%s' is from a required dependency and should be declared in after" |
||||||
|
.formatted(afterName) |
||||||
|
: "afterName '%s' not found".formatted(afterName); |
||||||
|
problems.computeIfAbsent(autoConfigurationClass.name(), (name) -> new ArrayList<>()).add(problem); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void writeReport(Map<String, List<String>> problems, File outputFile) { |
||||||
|
outputFile.getParentFile().mkdirs(); |
||||||
|
StringBuilder report = new StringBuilder(); |
||||||
|
if (!problems.isEmpty()) { |
||||||
|
report.append("Found auto-configuration class problems:%n".formatted()); |
||||||
|
problems.forEach((className, classProblems) -> { |
||||||
|
report.append(" - %s:%n".formatted(className)); |
||||||
|
classProblems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); |
||||||
|
}); |
||||||
|
} |
||||||
|
try { |
||||||
|
Files.writeString(outputFile.toPath(), report.toString(), StandardOpenOption.CREATE, |
||||||
|
StandardOpenOption.TRUNCATE_EXISTING); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,133 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2025 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.autoconfigure; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.UncheckedIOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.gradle.api.file.DirectoryProperty; |
||||||
|
import org.gradle.api.file.FileCollection; |
||||||
|
import org.gradle.api.tasks.Classpath; |
||||||
|
import org.gradle.api.tasks.OutputDirectory; |
||||||
|
import org.gradle.api.tasks.TaskAction; |
||||||
|
import org.gradle.api.tasks.VerificationException; |
||||||
|
import org.gradle.language.base.plugins.LifecycleBasePlugin; |
||||||
|
|
||||||
|
/** |
||||||
|
* Task to check the contents of a project's |
||||||
|
* {@code META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports} |
||||||
|
* file. |
||||||
|
* |
||||||
|
* @author Andy Wilkinson |
||||||
|
*/ |
||||||
|
public abstract class CheckAutoConfigurationImports extends AutoConfigurationImportsTask { |
||||||
|
|
||||||
|
private FileCollection classpath = getProject().getObjects().fileCollection(); |
||||||
|
|
||||||
|
public CheckAutoConfigurationImports() { |
||||||
|
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); |
||||||
|
setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); |
||||||
|
} |
||||||
|
|
||||||
|
@Classpath |
||||||
|
public FileCollection getClasspath() { |
||||||
|
return this.classpath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setClasspath(Object classpath) { |
||||||
|
this.classpath = getProject().getObjects().fileCollection().from(classpath); |
||||||
|
} |
||||||
|
|
||||||
|
@OutputDirectory |
||||||
|
public abstract DirectoryProperty getOutputDirectory(); |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
void execute() { |
||||||
|
File importsFile = getSource().getSingleFile(); |
||||||
|
check(importsFile); |
||||||
|
} |
||||||
|
|
||||||
|
private void check(File importsFile) { |
||||||
|
List<String> imports = loadImports(); |
||||||
|
List<String> problems = new ArrayList<>(); |
||||||
|
for (String imported : imports) { |
||||||
|
File classFile = find(imported); |
||||||
|
if (classFile == null) { |
||||||
|
problems.add("'%s' was not found".formatted(imported)); |
||||||
|
} |
||||||
|
else if (!correctlyAnnotated(classFile)) { |
||||||
|
problems.add("'%s' is not annotated with @AutoConfiguration".formatted(imported)); |
||||||
|
} |
||||||
|
} |
||||||
|
List<String> sortedValues = new ArrayList<>(imports); |
||||||
|
Collections.sort(sortedValues); |
||||||
|
if (!sortedValues.equals(imports)) { |
||||||
|
File sortedOutputFile = getOutputDirectory().file("sorted-" + importsFile.getName()).get().getAsFile(); |
||||||
|
writeString(sortedOutputFile, |
||||||
|
sortedValues.stream().collect(Collectors.joining(System.lineSeparator())) + System.lineSeparator()); |
||||||
|
problems.add("Entries should be sorted alphabetically (expect content written to " |
||||||
|
+ sortedOutputFile.getAbsolutePath() + ")"); |
||||||
|
} |
||||||
|
File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); |
||||||
|
writeReport(importsFile, problems, outputFile); |
||||||
|
if (!problems.isEmpty()) { |
||||||
|
throw new VerificationException("%s check failed. See '%s' for details" |
||||||
|
.formatted(AutoConfigurationImportsTask.IMPORTS_FILE, outputFile)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private File find(String className) { |
||||||
|
for (File root : this.classpath.getFiles()) { |
||||||
|
String classFilePath = className.replace(".", "/") + ".class"; |
||||||
|
File classFile = new File(root, classFilePath); |
||||||
|
if (classFile.isFile()) { |
||||||
|
return classFile; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private boolean correctlyAnnotated(File classFile) { |
||||||
|
return AutoConfigurationClass.of(classFile) != null; |
||||||
|
} |
||||||
|
|
||||||
|
private void writeReport(File importsFile, List<String> problems, File outputFile) { |
||||||
|
outputFile.getParentFile().mkdirs(); |
||||||
|
StringBuilder report = new StringBuilder(); |
||||||
|
if (!problems.isEmpty()) { |
||||||
|
report.append("Found problems in '%s':%n".formatted(importsFile)); |
||||||
|
problems.forEach((problem) -> report.append(" - %s%n".formatted(problem))); |
||||||
|
} |
||||||
|
writeString(outputFile, report.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
private void writeString(File file, String content) { |
||||||
|
try { |
||||||
|
Files.writeString(file.toPath(), content); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new UncheckedIOException(ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue