Redesign AOT processors for consistency and simplification

There's currently a considerable amount of overlap between the
implementations of AotProcessor and TestAotProcessor. In addition
AotProcessor is abstract and does not include a main() method; whereas,
TestAotProcessor is concrete and does include a main() method.

To address these issues, this commit:

- Introduces an AbstractAotProcessor base class that AotProcessor and
  TestAotProcessor now both extend

- Moves common properties/functionality to AbstractAotProcessor

- Renames AotProcessor to ContextAotProcessor

- Makes TestAotProcessor abstract like ContextAotProcessor

- Removes the main() method from TestAotProcessor

Closes gh-29266
This commit is contained in:
Sam Brannen
2022-10-10 15:25:33 +02:00
parent 061fa475ee
commit b2b3163fae
5 changed files with 200 additions and 163 deletions

View File

@@ -16,52 +16,29 @@
package org.springframework.test.context.aot;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
import org.springframework.aot.generate.FileSystemGeneratedFiles;
import org.springframework.aot.generate.GeneratedFiles;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.nativex.FileNativeConfigurationWriter;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
import org.springframework.context.aot.AbstractAotProcessor;
/**
* Filesystem-based ahead-of-time (AOT) processing base implementation that scans
* the provided classpath roots for Spring integration test classes and then
* generates AOT artifacts for those test classes in the configured output directories.
*
* <p>Typically used in a build tool.
* <p>Concrete implementations are typically used to kick off optimization of a
* test suite in a build tool.
*
* @author Sam Brannen
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @since 6.0
* @see TestContextAotGenerator
* @see FileNativeConfigurationWriter
* @see org.springframework.context.aot.AotProcessor
* @see org.springframework.context.aot.ContextAotProcessor
*/
public class TestAotProcessor {
public abstract class TestAotProcessor extends AbstractAotProcessor {
private final Path[] classpathRoots;
private final Path sourceOutput;
private final Path resourceOutput;
private final Path classOutput;
private final String groupId;
private final String artifactId;
private final Set<Path> classpathRoots;
/**
@@ -76,15 +53,11 @@ public class TestAotProcessor {
* @param artifactId the artifact ID of the application, used to locate
* {@code native-image.properties}
*/
public TestAotProcessor(Path[] classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
public TestAotProcessor(Set<Path> classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
String groupId, String artifactId) {
super(sourceOutput, resourceOutput, classOutput, groupId, artifactId);
this.classpathRoots = classpathRoots;
this.sourceOutput = sourceOutput;
this.resourceOutput = resourceOutput;
this.classOutput = classOutput;
this.groupId = groupId;
this.artifactId = artifactId;
}
@@ -98,24 +71,6 @@ public class TestAotProcessor {
performAotProcessing();
}
/**
* Delete the source, resource, and class output directories.
*/
protected void deleteExistingOutput() {
deleteExistingOutput(this.sourceOutput, this.resourceOutput, this.classOutput);
}
private void deleteExistingOutput(Path... paths) {
for (Path path : paths) {
try {
FileSystemUtils.deleteRecursively(path);
}
catch (IOException ex) {
throw new UncheckedIOException("Failed to delete existing output in '%s'".formatted(path), ex);
}
}
}
/**
* Perform ahead-of-time processing of Spring integration test classes.
* <p>Code, resources, and generated classes are stored in the configured
@@ -124,43 +79,14 @@ public class TestAotProcessor {
* components used by the tests.
*/
protected void performAotProcessing() {
TestClassScanner scanner = new TestClassScanner(Set.of(this.classpathRoots));
TestClassScanner scanner = new TestClassScanner(this.classpathRoots);
Stream<Class<?>> testClasses = scanner.scan();
GeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot);
GeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles);
generator.processAheadOfTime(testClasses);
writeHints(generator.getRuntimeHints());
}
private Path getRoot(Kind kind) {
return switch (kind) {
case SOURCE -> this.sourceOutput;
case RESOURCE -> this.resourceOutput;
case CLASS -> this.classOutput;
};
}
private void writeHints(RuntimeHints hints) {
FileNativeConfigurationWriter writer =
new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId);
writer.write(hints);
}
public static void main(String[] args) {
int requiredArgs = 6;
Assert.isTrue(args.length >= requiredArgs, () ->
"Usage: %s <classpathRoots> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId>"
.formatted(TestAotProcessor.class.getName()));
Path[] classpathRoots = Arrays.stream(args[0].split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new);
Path sourceOutput = Paths.get(args[1]);
Path resourceOutput = Paths.get(args[2]);
Path classOutput = Paths.get(args[3]);
String groupId = args[4];
String artifactId = args[5];
new TestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId).process();
}
}