From 8cf63a28b8736e8839b019fa9e0178b70380f468 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 5 May 2022 15:14:37 -0700 Subject: [PATCH] Update AOT work to latest Framework code --- .../gradle/tasks/aot/GenerateAotSources.java | 9 ++ .../boot/maven/AotGenerateTests.java | 2 +- .../boot/maven/AotGenerateMojo.java | 8 ++ .../springframework/boot/AotProcessor.java | 85 +++++++++++-------- .../boot/AotProcessorTests.java | 12 +-- 5 files changed, 73 insertions(+), 43 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/GenerateAotSources.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/GenerateAotSources.java index 3ec84bbd17..92d599f9b7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/GenerateAotSources.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/GenerateAotSources.java @@ -42,6 +42,8 @@ public class GenerateAotSources extends JavaExec { private final DirectoryProperty resourcesDir; + private final DirectoryProperty classesDir; + private final Property groupId; private final Property artifactId; @@ -50,6 +52,7 @@ public class GenerateAotSources extends JavaExec { this.applicationClass = getProject().getObjects().property(String.class); this.sourcesDir = getProject().getObjects().directoryProperty(); this.resourcesDir = getProject().getObjects().directoryProperty(); + this.classesDir = getProject().getObjects().directoryProperty(); this.groupId = getProject().getObjects().property(String.class); this.artifactId = getProject().getObjects().property(String.class); getMainClass().set("org.springframework.boot.AotProcessor"); @@ -80,6 +83,11 @@ public class GenerateAotSources extends JavaExec { return this.resourcesDir; } + @OutputDirectory + public DirectoryProperty getClassesDir() { + return this.classesDir; + } + @Override @TaskAction public void exec() { @@ -87,6 +95,7 @@ public class GenerateAotSources extends JavaExec { args.add(this.applicationClass.get()); args.add(this.sourcesDir.getAsFile().get().getAbsolutePath()); args.add(this.resourcesDir.getAsFile().get().getAbsolutePath()); + args.add(this.classesDir.getAsFile().get().getAbsolutePath()); args.addAll(super.getArgs()); this.setArgs(args); super.exec(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotGenerateTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotGenerateTests.java index 9be3b2a20a..066bb891ea 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotGenerateTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotGenerateTests.java @@ -39,7 +39,7 @@ public class AotGenerateTests { mavenBuild.project("aot").goals("package").execute((project) -> { Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); assertThat(collectRelativeFileNames(aotDirectory.resolve("sources"))) - .containsOnly("org/test/SampleApplication__ApplicationContextInitializer.java"); + .contains("org/test/SampleApplication__ApplicationContextInitializer.java"); assertThat(collectRelativeFileNames(aotDirectory.resolve("resources"))).containsOnly( "META-INF/native-image/org.springframework.boot.maven.it/aot/reflect-config.json", "META-INF/native-image/org.springframework.boot.maven.it/aot/native-image.properties"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AotGenerateMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AotGenerateMojo.java index b33c73601f..e7eb3a30cf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AotGenerateMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AotGenerateMojo.java @@ -73,6 +73,12 @@ public class AotGenerateMojo extends AbstractRunMojo { @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/resources", required = true) private File generatedResources; + /** + * Directory containing the generated classes. + */ + @Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true) + private File generatedClasses; + @Override protected void run(File workingDirectory, String startClassName, Map environmentVariables) throws MojoExecutionException, MojoFailureException { @@ -90,6 +96,7 @@ public class AotGenerateMojo extends AbstractRunMojo { private void deletePreviousAotAssets() { FileSystemUtils.deleteRecursively(this.generatedSources); FileSystemUtils.deleteRecursively(this.generatedResources); + FileSystemUtils.deleteRecursively(this.generatedClasses); } private void generateAotAssets(File workingDirectory, String startClassName, @@ -102,6 +109,7 @@ public class AotGenerateMojo extends AbstractRunMojo { args.add(startClassName); args.add(this.generatedSources.toString()); args.add(this.generatedResources.toString()); + args.add(this.generatedClasses.toString()); args.add(this.project.getGroupId()); args.add(this.project.getArtifactId()); addArgs(args); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/AotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/AotProcessor.java index a2969e295b..3d28dae824 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/AotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/AotProcessor.java @@ -24,18 +24,19 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; -import org.springframework.aot.generator.DefaultGeneratedTypeContext; -import org.springframework.aot.generator.GeneratedType; -import org.springframework.aot.generator.GeneratedTypeReference; +import org.springframework.aot.generate.DefaultGenerationContext; +import org.springframework.aot.generate.FileSystemGeneratedFiles; +import org.springframework.aot.generate.GeneratedFiles.Kind; +import org.springframework.aot.hint.ExecutableHint; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.nativex.FileNativeConfigurationWriter; -import org.springframework.context.generator.ApplicationContextAotGenerator; +import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.support.GenericApplicationContext; import org.springframework.javapoet.ClassName; -import org.springframework.javapoet.JavaFile; import org.springframework.util.Assert; /** @@ -45,10 +46,14 @@ import org.springframework.util.Assert; * * @author Stephane Nicoll * @author Andy Wilkinson + * @author Phillip Webb * @since 3.0 */ public class AotProcessor { + private static final Consumer INVOKE_CONSTRUCTOR_HINT = (hint) -> hint + .setModes(ExecutableMode.INVOKE); + private final Class application; private final String[] applicationArgs; @@ -57,6 +62,8 @@ public class AotProcessor { private final Path resourceOutput; + private final Path classOutput; + private final String groupId; private final String artifactId; @@ -67,17 +74,19 @@ public class AotProcessor { * @param applicationArgs the arguments to provide to the main method * @param sourceOutput the location of generated sources * @param resourceOutput the location of generated resources + * @param classOutput the location of generated classes * @param groupId the group ID of the application, used to locate * native-image.properties * @param artifactId the artifact ID of the application, used to locate * native-image.properties */ public AotProcessor(Class application, String[] applicationArgs, Path sourceOutput, Path resourceOutput, - String groupId, String artifactId) { + Path classOutput, String groupId, String artifactId) { this.application = application; this.applicationArgs = applicationArgs; this.sourceOutput = sourceOutput; this.resourceOutput = resourceOutput; + this.classOutput = classOutput; this.groupId = groupId; this.artifactId = artifactId; } @@ -104,36 +113,39 @@ public class AotProcessor { } private void performAotProcessing(GenericApplicationContext applicationContext) { - DefaultGeneratedTypeContext generationContext = new DefaultGeneratedTypeContext( - this.application.getPackageName(), (packageName) -> GeneratedType.of(ClassName.get(packageName, - this.application.getSimpleName() + "__ApplicationContextInitializer"))); + FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot); + DefaultGenerationContext generationContext = new DefaultGenerationContext(generatedFiles); ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); - generator.generateApplicationContext(applicationContext, generationContext); - - // Register reflection hint for entry point as we access it via reflection - generationContext.runtimeHints().reflection() - .registerType(GeneratedTypeReference.of(generationContext.getMainGeneratedType().getClassName()), - (hint) -> hint.onReachableType(TypeReference.of(this.application)).withConstructor( - Collections.emptyList(), - (constructorHint) -> constructorHint.setModes(ExecutableMode.INVOKE))); - - writeGeneratedSources(generationContext.toJavaFiles()); - writeGeneratedResources(generationContext.runtimeHints()); + ClassName generatedInitializerClassName = generationContext.getClassNameGenerator() + .generateClassName(this.application, "ApplicationContextInitializer"); + generator.generateApplicationContext(applicationContext, generationContext, generatedInitializerClassName); + registerEntryPointHint(generationContext, generatedInitializerClassName); + generationContext.writeGeneratedContent(); + writeHints(generationContext.getRuntimeHints()); writeNativeImageProperties(); } - private void writeGeneratedSources(List sources) { - for (JavaFile source : sources) { - try { - source.writeTo(this.sourceOutput); - } - catch (IOException ex) { - throw new IllegalStateException("Failed to write " + source.typeSpec.name, ex); - } - } + private void registerEntryPointHint(DefaultGenerationContext generationContext, + ClassName generatedInitializerClassName) { + TypeReference generatedType = TypeReference.of(generatedInitializerClassName.canonicalName()); + TypeReference applicationType = TypeReference.of(this.application); + generationContext.getRuntimeHints().reflection().registerType(generatedType, (hint) -> hint + .onReachableType(applicationType).withConstructor(Collections.emptyList(), INVOKE_CONSTRUCTOR_HINT)); } - private void writeGeneratedResources(RuntimeHints hints) { + private Path getRoot(Kind kind) { + switch (kind) { + case SOURCE: + return this.sourceOutput; + case RESOURCE: + return this.resourceOutput; + case CLASS: + return this.classOutput; + } + throw new IllegalStateException("Unsupported kind " + kind); + } + + private void writeHints(RuntimeHints hints) { FileNativeConfigurationWriter writer = new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId); writer.write(hints); @@ -164,21 +176,22 @@ public class AotProcessor { } public static void main(String[] args) throws Exception { - int requiredArgs = 5; + int requiredArgs = 6; if (args.length < requiredArgs) { throw new IllegalArgumentException("Usage: " + AotProcessor.class.getName() - + " "); + + " "); } String applicationName = args[0]; Path sourceOutput = Paths.get(args[1]); Path resourceOutput = Paths.get(args[2]); - String groupId = args[3]; - String artifactId = args[4]; + Path classOutput = Paths.get(args[3]); + String groupId = args[4]; + String artifactId = args[5]; String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length) : new String[0]; Class application = Class.forName(applicationName); - AotProcessor aotProcess = new AotProcessor(application, applicationArgs, sourceOutput, resourceOutput, groupId, - artifactId); + AotProcessor aotProcess = new AotProcessor(application, applicationArgs, sourceOutput, resourceOutput, + classOutput, groupId, artifactId); aotProcess.process(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AotProcessorTests.java index c4d913098b..4aeed4a84e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AotProcessorTests.java @@ -45,7 +45,7 @@ class AotProcessorTests { void processApplicationInvokesRunMethod(@TempDir Path directory) { String[] arguments = new String[] { "1", "2" }; AotProcessor processor = new AotProcessor(SampleApplication.class, arguments, directory.resolve("source"), - directory.resolve("resource"), "com.example", "example"); + directory.resolve("resource"), directory.resolve("class"), "com.example", "example"); processor.process(); assertThat(SampleApplication.argsHolder).isEqualTo(arguments); assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication()); @@ -54,7 +54,7 @@ class AotProcessorTests { @Test void processApplicationWithMainMethodThatDoesNotRun(@TempDir Path directory) { AotProcessor processor = new AotProcessor(BrokenApplication.class, new String[0], directory.resolve("source"), - directory.resolve("resource"), "com.example", "example"); + directory.resolve("resource"), directory.resolve("class"), "com.example", "example"); assertThatIllegalArgumentException().isThrownBy(processor::process) .withMessageContaining("Does it run a SpringApplication?"); assertThat(directory).isEmptyDirectory(); @@ -63,8 +63,8 @@ class AotProcessorTests { @Test void invokeMainParseArgumentsAndInvokesRunMethod(@TempDir Path directory) throws Exception { String[] mainArguments = new String[] { SampleApplication.class.getName(), - directory.resolve("source").toString(), directory.resolve("resource").toString(), "com.example", - "example", "1", "2" }; + directory.resolve("source").toString(), directory.resolve("resource").toString(), + directory.resolve("class").toString(), "com.example", "example", "1", "2" }; AotProcessor.main(mainArguments); assertThat(SampleApplication.argsHolder).containsExactly("1", "2"); assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication()); @@ -78,8 +78,8 @@ class AotProcessorTests { private Consumer hasGeneratedAssetsForSampleApplication() { return (directory) -> { - assertThat(directory - .resolve("source/org/springframework/boot/SampleApplication__ApplicationContextInitializer.java")) + assertThat(directory.resolve( + "source/org/springframework/boot/AotProcessorTests_SampleApplication__ApplicationContextInitializer.java")) .exists().isRegularFile(); assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reflect-config.json")) .exists().isRegularFile();