diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/system/DevToolsEnablementDeducer.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/system/DevToolsEnablementDeducer.java index 435ee4704a..da76267952 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/system/DevToolsEnablementDeducer.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/system/DevToolsEnablementDeducer.java @@ -20,7 +20,7 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; -import org.springframework.boot.AotProcessor; +import org.springframework.boot.SpringApplicationAotProcessor; /** * Utility to deduce if DevTools should be enabled in the current context. @@ -37,7 +37,7 @@ public final class DevToolsEnablementDeducer { skipped.add("org.junit.runners."); skipped.add("org.junit.platform."); skipped.add("org.springframework.boot.test."); - skipped.add(AotProcessor.class.getName()); + skipped.add(SpringApplicationAotProcessor.class.getName()); skipped.add("cucumber.runtime."); SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java index 66dde61062..bad9aef2ec 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/aot/ProcessAot.java @@ -38,7 +38,7 @@ public class ProcessAot extends AbstractAot { public ProcessAot() { this.applicationClass = getProject().getObjects().property(String.class); - getMainClass().set("org.springframework.boot.AotProcessor"); + getMainClass().set("org.springframework.boot.SpringApplicationAotProcessor"); } @Input diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java index 047848ca2c..50442f4426 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/NativeImagePluginActionIntegrationTests.java @@ -55,7 +55,7 @@ class NativeImagePluginActionIntegrationTests { @TestTemplate void reachabilityMetadataConfigurationFilesAreCopiedToJar() throws IOException { - writeDummyAotProcessorMainClass(); + writeDummySpringApplicationAotProcessorMainClass(); BuildResult result = this.gradleBuild.build("bootJar"); assertThat(result.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs"); @@ -71,7 +71,7 @@ class NativeImagePluginActionIntegrationTests { @TestTemplate void reachabilityMetadataConfigurationFilesFromFileRepositoryAreCopiedToJar() throws IOException { - writeDummyAotProcessorMainClass(); + writeDummySpringApplicationAotProcessorMainClass(); FileSystemUtils.copyRecursively(new File("src/test/resources/reachability-metadata-repository"), new File(this.gradleBuild.getProjectDir(), "reachability-metadata-repository")); BuildResult result = this.gradleBuild.build("bootJar"); @@ -87,16 +87,16 @@ class NativeImagePluginActionIntegrationTests { "META-INF/native-image/org.jline/jline/3.21.0/resource-config.json"); } - private void writeDummyAotProcessorMainClass() { + private void writeDummySpringApplicationAotProcessorMainClass() { File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/org/springframework/boot"); examplePackage.mkdirs(); - File main = new File(examplePackage, "AotProcessor.java"); + File main = new File(examplePackage, "SpringApplicationAotProcessor.java"); try (PrintWriter writer = new PrintWriter(new FileWriter(main))) { writer.println("package org.springframework.boot;"); writer.println(); writer.println("import java.io.IOException;"); writer.println(); - writer.println("public class AotProcessor {"); + writer.println("public class SpringApplicationAotProcessor {"); writer.println(); writer.println(" public static void main(String[] args) {"); writer.println(" }"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java index 00aa32533d..963d54a5e1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/SpringBootAotPluginIntegrationTests.java @@ -103,7 +103,7 @@ class SpringBootAotPluginIntegrationTests { @TestTemplate void processAotRunsWhenProjectHasMainSource() throws IOException { - writeMainClass("org.springframework.boot", "AotProcessor"); + writeMainClass("org.springframework.boot", "SpringApplicationAotProcessor"); writeMainClass("com.example", "Main"); assertThat(this.gradleBuild.build("processAot").task(":processAot").getOutcome()) .isEqualTo(TaskOutcome.SUCCESS); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java index 5d645b6b11..01707b27bc 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java @@ -40,7 +40,7 @@ import org.springframework.util.ObjectUtils; requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) public class ProcessAotMojo extends AbstractAotMojo { - private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.AotProcessor"; + private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.SpringApplicationAotProcessor"; /** * Directory containing the classes and resource files that should be packaged into 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 deleted file mode 100644 index 46c8c70015..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/AotProcessor.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.springframework.aot.generate.ClassNameGenerator; -import org.springframework.aot.generate.DefaultGenerationContext; -import org.springframework.aot.generate.FileSystemGeneratedFiles; -import org.springframework.aot.generate.GeneratedFiles.Kind; -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.aot.nativex.FileNativeConfigurationWriter; -import org.springframework.boot.SpringApplication.AbandonedRunException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.aot.ApplicationContextAotGenerator; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.javapoet.ClassName; -import org.springframework.util.Assert; -import org.springframework.util.FileSystemUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.function.ThrowingSupplier; - -/** - * Entry point for AOT processing of a {@link SpringApplication}. - *

- * For internal use only. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - * @author Phillip Webb - * @since 3.0.0 - */ -public class AotProcessor { - - private final Class application; - - private final String[] applicationArgs; - - private final Path sourceOutput; - - private final Path resourceOutput; - - private final Path classOutput; - - private final String groupId; - - private final String artifactId; - - /** - * Create a new processor for the specified application and settings. - * @param application the application main class - * @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, - 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; - } - - /** - * Trigger the processing of the application managed by this instance. - */ - public void process() { - deleteExistingOutput(); - GenericApplicationContext applicationContext = new AotProcessorHook().run(() -> { - Method mainMethod = this.application.getMethod("main", String[].class); - return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs }); - }); - performAotProcessing(applicationContext); - } - - private 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 RuntimeException("Failed to delete existing output in '" + path + "'"); - } - } - } - - private void performAotProcessing(GenericApplicationContext applicationContext) { - FileSystemGeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot); - DefaultGenerationContext generationContext = new DefaultGenerationContext( - new ClassNameGenerator(ClassName.get(this.application)), generatedFiles); - ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator(); - ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext); - registerEntryPointHint(generationContext, generatedInitializerClassName); - generationContext.writeGeneratedContent(); - writeHints(generationContext.getRuntimeHints()); - writeNativeImageProperties(); - } - - private void registerEntryPointHint(DefaultGenerationContext generationContext, - ClassName generatedInitializerClassName) { - TypeReference generatedType = TypeReference.of(generatedInitializerClassName.canonicalName()); - TypeReference applicationType = TypeReference.of(this.application); - ReflectionHints reflection = generationContext.getRuntimeHints().reflection(); - reflection.registerType(applicationType); - reflection.registerType(generatedType, (typeHint) -> typeHint.onReachableType(applicationType) - .withConstructor(Collections.emptyList(), ExecutableMode.INVOKE)); - } - - 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); - } - - private void writeNativeImageProperties() { - List args = new ArrayList<>(); - args.add("-H:Class=" + this.application.getName()); - args.add("--report-unsupported-elements-at-runtime"); - args.add("--no-fallback"); - args.add("--install-exit-handlers"); - StringBuilder sb = new StringBuilder(); - sb.append("Args = "); - sb.append(String.join(String.format(" \\%n"), args)); - Path file = this.resourceOutput - .resolve("META-INF/native-image/" + this.groupId + "/" + this.artifactId + "/native-image.properties"); - try { - if (!Files.exists(file)) { - Files.createDirectories(file.getParent()); - Files.createFile(file); - } - Files.writeString(file, sb.toString()); - } - catch (IOException ex) { - throw new IllegalStateException("Failed to write native-image properties", ex); - } - } - - public static void main(String[] args) throws Exception { - int requiredArgs = 6; - Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + AotProcessor.class.getName() - + " "); - String applicationName = args[0]; - 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]; - String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length) - : new String[0]; - Class application = Class.forName(applicationName); - new AotProcessor(application, applicationArgs, sourceOutput, resourceOutput, classOutput, groupId, artifactId) - .process(); - } - - /** - * {@link SpringApplicationHook} used to capture the {@link ApplicationContext} and - * trigger early exit of main method. - */ - private class AotProcessorHook implements SpringApplicationHook { - - @Override - public SpringApplicationRunListener getRunListener(SpringApplication application) { - return new SpringApplicationRunListener() { - - @Override - public void contextLoaded(ConfigurableApplicationContext context) { - throw new AbandonedRunException(context); - } - - }; - } - - private GenericApplicationContext run(ThrowingSupplier action) { - try { - SpringApplication.withHook(this, action); - } - catch (AbandonedRunException ex) { - ApplicationContext context = ex.getApplicationContext(); - Assert.isInstanceOf(GenericApplicationContext.class, context, - () -> "AOT processing requires a GenericApplicationContext but got a " - + context.getClass().getName()); - return (GenericApplicationContext) context; - } - throw new IllegalStateException( - "No application context available after calling main method of '%s'. Does it run a SpringApplication?" - .formatted(AotProcessor.this.application.getName())); - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java new file mode 100644 index 0000000000..8fea5e8a68 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java @@ -0,0 +1,132 @@ +/* + * 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; + +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.springframework.boot.SpringApplication.AbandonedRunException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.aot.AotProcessor; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.function.ThrowingSupplier; + +/** + * Entry point for AOT processing of a {@link SpringApplication}. + *

+ * For internal use only. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + * @since 3.0.0 + */ +public class SpringApplicationAotProcessor extends AotProcessor { + + private final String[] applicationArgs; + + /** + * Create a new processor for the specified application and settings. + * @param application the application main class + * @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 SpringApplicationAotProcessor(Class application, String[] applicationArgs, Path sourceOutput, + Path resourceOutput, Path classOutput, String groupId, String artifactId) { + super(application, sourceOutput, resourceOutput, classOutput, groupId, artifactId); + this.applicationArgs = applicationArgs; + } + + @Override + protected GenericApplicationContext prepareApplicationContext(Class application) { + return new AotProcessorHook(application).run(() -> { + Method mainMethod = application.getMethod("main", String[].class); + return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs }); + }); + } + + public static void main(String[] args) throws Exception { + int requiredArgs = 6; + Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + SpringApplicationAotProcessor.class.getName() + + " "); + String applicationName = args[0]; + 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]; + String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length) + : new String[0]; + Class application = Class.forName(applicationName); + new SpringApplicationAotProcessor(application, applicationArgs, sourceOutput, resourceOutput, classOutput, + groupId, artifactId).process(); + } + + /** + * {@link SpringApplicationHook} used to capture the {@link ApplicationContext} and + * trigger early exit of main method. + */ + private static final class AotProcessorHook implements SpringApplicationHook { + + private final Class application; + + private AotProcessorHook(Class application) { + this.application = application; + } + + @Override + public SpringApplicationRunListener getRunListener(SpringApplication application) { + return new SpringApplicationRunListener() { + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + throw new AbandonedRunException(context); + } + + }; + } + + private GenericApplicationContext run(ThrowingSupplier action) { + try { + SpringApplication.withHook(this, action); + } + catch (AbandonedRunException ex) { + ApplicationContext context = ex.getApplicationContext(); + Assert.isInstanceOf(GenericApplicationContext.class, context, + () -> "AOT processing requires a GenericApplicationContext but got a " + + context.getClass().getName()); + return (GenericApplicationContext) context; + } + throw new IllegalStateException( + "No application context available after calling main method of '%s'. Does it run a SpringApplication?" + .formatted(this.application.getName())); + } + + } + +} 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 deleted file mode 100644 index 4d65218a92..0000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/AotProcessorTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.function.Consumer; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -/** - * Tests for {@link AotProcessor}. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -class AotProcessorTests { - - @BeforeEach - void setup() { - SampleApplication.argsHolder = null; - SampleApplication.postRunInvoked = false; - } - - @Test - void processApplicationInvokesRunMethod(@TempDir Path directory) throws IOException { - String[] arguments = new String[] { "1", "2" }; - AotProcessor processor = new AotProcessor(SampleApplication.class, arguments, directory.resolve("source"), - directory.resolve("resource"), directory.resolve("class"), "com.example", "example"); - processor.process(); - assertThat(SampleApplication.argsHolder).isEqualTo(arguments); - assertThat(SampleApplication.postRunInvoked).isFalse(); - assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication()); - } - - @Test - void processApplicationWithMainMethodThatDoesNotRun(@TempDir Path directory) { - AotProcessor processor = new AotProcessor(BrokenApplication.class, new String[0], directory.resolve("source"), - directory.resolve("resource"), directory.resolve("class"), "com.example", "example"); - assertThatIllegalStateException().isThrownBy(processor::process) - .withMessageContaining("Does it run a SpringApplication?"); - assertThat(directory).isEmptyDirectory(); - } - - @Test - void invokeMainParseArgumentsAndInvokesRunMethod(@TempDir Path directory) throws Exception { - String[] mainArguments = new String[] { SampleApplication.class.getName(), - 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(SampleApplication.postRunInvoked).isFalse(); - assertThat(directory).satisfies(hasGeneratedAssetsForSampleApplication()); - } - - @Test - void invokeMainWithMissingArguments() { - assertThatIllegalArgumentException().isThrownBy(() -> AotProcessor.main(new String[] { "Test" })) - .withMessageContaining("Usage:"); - } - - @Test - void processingDeletesExistingOutput(@TempDir Path directory) throws IOException { - Path sourceOutput = directory.resolve("source"); - Path resourceOutput = directory.resolve("resource"); - Path classOutput = directory.resolve("class"); - Path existingSourceOutput = createExisting(sourceOutput); - Path existingResourceOutput = createExisting(resourceOutput); - Path existingClassOutput = createExisting(classOutput); - AotProcessor processor = new AotProcessor(SampleApplication.class, new String[0], sourceOutput, resourceOutput, - classOutput, "com.example", "example"); - processor.process(); - assertThat(existingSourceOutput).doesNotExist(); - assertThat(existingResourceOutput).doesNotExist(); - assertThat(existingClassOutput).doesNotExist(); - } - - private Path createExisting(Path directory) throws IOException { - Path existing = directory.resolve("existing"); - Files.createDirectories(directory); - Files.createFile(existing); - return existing; - } - - private Consumer hasGeneratedAssetsForSampleApplication() { - return (directory) -> { - assertThat(directory.resolve( - "source/org/springframework/boot/AotProcessorTests_SampleApplication__ApplicationContextInitializer.java")) - .exists().isRegularFile(); - assertThat(directory.resolve("source/org/springframework/boot/AotProcessorTests__BeanDefinitions.java")) - .exists().isRegularFile(); - assertThat(directory.resolve( - "source/org/springframework/boot/AotProcessorTests_SampleApplication__BeanFactoryRegistrations.java")) - .exists().isRegularFile(); - assertThat(directory.resolve("resource/META-INF/native-image/com.example/example/reflect-config.json")) - .exists().isRegularFile(); - Path nativeImagePropertiesFile = directory - .resolve("resource/META-INF/native-image/com.example/example/native-image.properties"); - assertThat(nativeImagePropertiesFile).exists().isRegularFile().hasContent(""" - Args = -H:Class=org.springframework.boot.AotProcessorTests$SampleApplication \\ - --report-unsupported-elements-at-runtime \\ - --no-fallback \\ - --install-exit-handlers - """); - }; - } - - @Configuration(proxyBeanMethods = false) - public static class SampleApplication { - - public static String[] argsHolder; - - public static boolean postRunInvoked; - - public static void main(String[] args) { - argsHolder = args; - SpringApplication.run(SampleApplication.class, args); - postRunInvoked = true; - } - - } - - public static class BrokenApplication { - - public static void main(String[] args) { - // Does not run an application - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationAotProcessorTests.java new file mode 100644 index 0000000000..047d8cc747 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationAotProcessorTests.java @@ -0,0 +1,106 @@ +/* + * 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; + +import java.nio.file.Path; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link SpringApplicationAotProcessor}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class SpringApplicationAotProcessorTests { + + @BeforeEach + void setup() { + SampleApplication.argsHolder = null; + SampleApplication.postRunInvoked = false; + } + + @Test + void processApplicationInvokesRunMethod(@TempDir Path directory) { + String[] arguments = new String[] { "1", "2" }; + SpringApplicationAotProcessor processor = new SpringApplicationAotProcessor(SampleApplication.class, arguments, + directory.resolve("source"), directory.resolve("resource"), directory.resolve("class"), "com.example", + "example"); + processor.process(); + assertThat(SampleApplication.argsHolder).isEqualTo(arguments); + assertThat(SampleApplication.postRunInvoked).isFalse(); + } + + @Test + void processApplicationWithMainMethodThatDoesNotRun(@TempDir Path directory) { + SpringApplicationAotProcessor processor = new SpringApplicationAotProcessor(BrokenApplication.class, + new String[0], directory.resolve("source"), directory.resolve("resource"), directory.resolve("class"), + "com.example", "example"); + assertThatIllegalStateException().isThrownBy(processor::process) + .withMessageContaining("Does it run a SpringApplication?"); + assertThat(directory).isEmptyDirectory(); + } + + @Test + void invokeMainParseArgumentsAndInvokesRunMethod(@TempDir Path directory) throws Exception { + String[] mainArguments = new String[] { SampleApplication.class.getName(), + directory.resolve("source").toString(), directory.resolve("resource").toString(), + directory.resolve("class").toString(), "com.example", "example", "1", "2" }; + SpringApplicationAotProcessor.main(mainArguments); + assertThat(SampleApplication.argsHolder).containsExactly("1", "2"); + assertThat(SampleApplication.postRunInvoked).isFalse(); + } + + @Test + void invokeMainWithMissingArguments() { + assertThatIllegalArgumentException() + .isThrownBy(() -> SpringApplicationAotProcessor.main(new String[] { "Test" })) + .withMessageContaining("Usage:"); + } + + @Configuration(proxyBeanMethods = false) + public static class SampleApplication { + + public static String[] argsHolder; + + public static boolean postRunInvoked; + + public static void main(String[] args) { + argsHolder = args; + SpringApplication.run(SampleApplication.class, args); + postRunInvoked = true; + } + + } + + public static class BrokenApplication { + + public static void main(String[] args) { + // Does not run an application + } + + } + +}