From 397d692d4be7dd0e098595a84179e2723a271129 Mon Sep 17 00:00:00 2001 From: academey Date: Thu, 24 Jul 2025 08:54:21 +0900 Subject: [PATCH] Exclude spring-boot-devtools from AOT processing in Maven Previously, spring-boot-devtools was only excluded from native images built with Gradle but not with Maven. This inconsistency meant that Maven builds would include devtools in the AOT processing classpath, causing the native image to fail or include unnecessary code. This commit applies the existing DEVTOOLS_EXCLUDE_FILTER to the classpath used during both main and test AOT processing in Maven, matching the behavior of the Gradle plugin which excludes the developmentOnly configuration. The exclusion happens automatically without requiring any user configuration, ensuring that devtools is properly excluded from native images while maintaining its functionality during development. Fixes gh-32853 Signed-off-by: academey --- .../springframework/boot/maven/AotTests.java | 35 +++++++++++ .../projects/aot-exclude-devtools/pom.xml | 48 +++++++++++++++ .../main/java/org/test/SampleApplication.java | 29 +++++++++ .../aot-test-exclude-devtools/pom.xml | 60 +++++++++++++++++++ .../main/java/org/test/SampleApplication.java | 29 +++++++++ .../java/org/test/SampleApplicationTests.java | 29 +++++++++ .../boot/maven/ProcessAotMojo.java | 2 +- .../boot/maven/ProcessTestAotMojo.java | 2 +- 8 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/pom.xml create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/src/main/java/org/test/SampleApplication.java create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/pom.xml create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/main/java/org/test/SampleApplication.java create mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/test/java/org/test/SampleApplicationTests.java diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotTests.java index a96ace27fc82..8fd4c68c252c 100644 --- a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotTests.java +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AotTests.java @@ -195,4 +195,39 @@ protected String buildLog(File project) { return contentOf(new File(project, "target/build.log")); } + @TestTemplate + void whenAotRunsWithDevtoolsInClasspathItIsExcluded(MavenBuild mavenBuild) { + mavenBuild.project("aot-exclude-devtools").goals("package").execute((project) -> { + // The test passes if the build completes successfully. + // If devtools were included, the AOT processing would fail because devtools + // uses features (like class proxies) that are not compatible with native + // images. + Path aotDirectory = project.toPath().resolve("target/spring-aot/main"); + assertThat(aotDirectory).exists(); + // Verify that source files were generated, indicating successful AOT + // processing + Path sourcesDirectory = aotDirectory.resolve("sources"); + assertThat(sourcesDirectory).exists(); + assertThat(collectRelativePaths(sourcesDirectory)).isNotEmpty(); + }); + } + + @TestTemplate + void whenTestAotRunsWithDevtoolsInClasspathItIsExcluded(MavenBuild mavenBuild) { + mavenBuild.project("aot-test-exclude-devtools").goals("process-test-classes").execute((project) -> { + // The test passes if the build completes successfully. + // If devtools were included, the test AOT processing would fail because + // devtools + // uses features (like class proxies) that are not compatible with native + // images. + Path aotDirectory = project.toPath().resolve("target/spring-aot/test"); + assertThat(aotDirectory).exists(); + // Verify that source files were generated, indicating successful AOT + // processing + Path sourcesDirectory = aotDirectory.resolve("sources"); + assertThat(sourcesDirectory).exists(); + assertThat(collectRelativePaths(sourcesDirectory)).isNotEmpty(); + }); + } + } diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/pom.xml b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/pom.xml new file mode 100644 index 000000000000..f8a155c6dadc --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + org.springframework.boot.maven.it + aot-exclude-devtools + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + process-aot + + + + + + + + + org.springframework.boot + spring-boot + @project.version@ + + + org.springframework.boot + spring-boot-devtools + @project.version@ + true + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + \ No newline at end of file diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/src/main/java/org/test/SampleApplication.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/src/main/java/org/test/SampleApplication.java new file mode 100644 index 000000000000..6620ac4a2d82 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-exclude-devtools/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-present 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.test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} \ No newline at end of file diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/pom.xml b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/pom.xml new file mode 100644 index 000000000000..6db8ade0eac2 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + org.springframework.boot.maven.it + aot-test-exclude-devtools + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + @java.version@ + @java.version@ + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + + + + process-test-aot + + + + + + + + + org.springframework.boot + spring-boot + @project.version@ + + + org.springframework.boot + spring-boot-devtools + @project.version@ + true + + + org.springframework.boot + spring-boot-test + @project.version@ + test + + + org.junit.jupiter + junit-jupiter + @junit-jupiter.version@ + test + + + jakarta.servlet + jakarta.servlet-api + @jakarta-servlet.version@ + provided + + + \ No newline at end of file diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/main/java/org/test/SampleApplication.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/main/java/org/test/SampleApplication.java new file mode 100644 index 000000000000..6620ac4a2d82 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/main/java/org/test/SampleApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-present 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.test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} \ No newline at end of file diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/test/java/org/test/SampleApplicationTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/test/java/org/test/SampleApplicationTests.java new file mode 100644 index 000000000000..cc23c3e14674 --- /dev/null +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/aot-test-exclude-devtools/src/test/java/org/test/SampleApplicationTests.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-present 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.test; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SampleApplicationTests { + + @Test + void contextLoads() { + } + +} \ No newline at end of file diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java index c6c940e4e2b7..524594db73ed 100644 --- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java +++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessAotMojo.java @@ -123,7 +123,7 @@ private String[] getAotArguments(String applicationClass) { private URL[] getClassPath() throws Exception { File[] directories = new File[] { this.classesDirectory, this.generatedClasses }; - return getClassPath(directories, new ExcludeTestScopeArtifactFilter()); + return getClassPath(directories, new ExcludeTestScopeArtifactFilter(), DEVTOOLS_EXCLUDE_FILTER); } private RunArguments resolveArguments() { diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java index 8cf523bdf114..6b3cf7929eb1 100644 --- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java +++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/ProcessTestAotMojo.java @@ -148,7 +148,7 @@ private String[] getAotArguments() { protected URL[] getClassPath(boolean includeJUnitPlatformLauncher) throws Exception { File[] directories = new File[] { this.testClassesDirectory, this.generatedTestClasses, this.classesDirectory, this.generatedClasses }; - URL[] classPath = getClassPath(directories); + URL[] classPath = getClassPath(directories, DEVTOOLS_EXCLUDE_FILTER); if (!includeJUnitPlatformLauncher || this.project.getArtifactMap() .containsKey(JUNIT_PLATFORM_GROUP_ID + ":" + JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID)) { return classPath;