Add support for invoking AOT to the Maven Plugin
This commit adds an `aot-generate` goal to the Maven Plugin that triggers AOT generation on the application. The new goal shares a number of properties with the existing `run` goal and uses the same algorithm to detect the main class to use. Closes gh-30525
This commit is contained in:
parent
096420cc4c
commit
e81c6337c6
|
@ -34,6 +34,7 @@ dependencies {
|
|||
exclude(group: "javax.inject", module: "javax.inject")
|
||||
}
|
||||
|
||||
implementation("org.springframework:spring-context")
|
||||
implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform"))
|
||||
implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-loader-tools"))
|
||||
|
||||
|
@ -53,6 +54,8 @@ dependencies {
|
|||
exclude(group: "javax.inject", module: "javax.inject")
|
||||
}
|
||||
|
||||
mavenRepository(project(path: ":spring-boot-project:spring-boot", configuration: "mavenRepository"))
|
||||
|
||||
runtimeOnly("org.sonatype.plexus:plexus-build-api")
|
||||
|
||||
testImplementation("org.assertj:assertj-core")
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[[aot]]
|
||||
= Optimizing Your Application at Build-Time
|
||||
|
||||
Spring AOT inspects an application at build-time and generates an optimized version of it.
|
||||
Based on your `@SpringBootApplication`-annotated main class, the AOT engine generates a persistent view of the beans that are going to be contributed at runtime in a way that bean instantiation is as straightforward as possible.
|
||||
Additional post-processing of the factory is possible using callbacks.
|
||||
For instance, these are used to generate the necessary reflection configuration that GraalVM needs to initialize the context in a native image.
|
||||
|
||||
To configure your application to use this feature, add an execution for the `aot-generate` goal, as shown in the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,attributes",tabsize=4]
|
||||
----
|
||||
include::../maven/aot/pom.xml[tags=aot]
|
||||
----
|
||||
|
||||
As the `BeanFactory` is fully prepared at build-time, conditions are also evaluated.
|
||||
This has an important difference compared to what a regular Spring Boot application does at runtime.
|
||||
For instance, if you want to opt-in or opt-out for certain features, you need to configure the environment used at build time to do so.
|
||||
The `aot-generate` goal shares a number of properties with the <<run,run goal>> for that reason.
|
||||
|
||||
|
||||
include::goals/aot-generate.adoc[leveloffset=+1]
|
|
@ -36,6 +36,8 @@ include::packaging-oci-image.adoc[leveloffset=+1]
|
|||
|
||||
include::running.adoc[leveloffset=+1]
|
||||
|
||||
include::aot.adoc[leveloffset=+1]
|
||||
|
||||
include::integration-tests.adoc[leveloffset=+1]
|
||||
|
||||
include::build-info.adoc[leveloffset=+1]
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>aot</artifactId>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- tag::aot[] -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>aot-generate</id>
|
||||
<goals>
|
||||
<goal>aot-generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<!-- end::aot[] -->
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.maven;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.TestTemplate;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for the Maven plugin's AOT support.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ExtendWith(MavenBuildExtension.class)
|
||||
public class AotGenerateTests {
|
||||
|
||||
@TestTemplate
|
||||
void whenAotRunsSourcesAreGenerated(MavenBuild mavenBuild) {
|
||||
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");
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenAotRunsSourcesAreCompiled(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("aot").goals("package").execute((project) -> {
|
||||
Path classesDirectory = project.toPath().resolve("target/classes");
|
||||
assertThat(collectRelativeFileNames(classesDirectory))
|
||||
.contains("org/test/SampleApplication__ApplicationContextInitializer.class");
|
||||
});
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void whenAotRunsResourcesAreCopiedToTargetClasses(MavenBuild mavenBuild) {
|
||||
mavenBuild.project("aot").goals("package").execute((project) -> {
|
||||
Path classesDirectory = project.toPath().resolve("target/classes/META-INF/native-image");
|
||||
assertThat(collectRelativeFileNames(classesDirectory)).contains(
|
||||
"org.springframework.boot.maven.it/aot/reflect-config.json",
|
||||
"org.springframework.boot.maven.it/aot/native-image.properties");
|
||||
});
|
||||
}
|
||||
|
||||
Stream<String> collectRelativeFileNames(Path sourceDirectory) {
|
||||
try {
|
||||
return Files.walk(sourceDirectory).filter(Files::isRegularFile)
|
||||
.map((path) -> path.subpath(sourceDirectory.getNameCount(), path.getNameCount()).toString());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.boot.maven.it</groupId>
|
||||
<artifactId>aot</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>@java.version@</maven.compiler.source>
|
||||
<maven.compiler.target>@java.version@</maven.compiler.target>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>@project.groupId@</groupId>
|
||||
<artifactId>@project.artifactId@</artifactId>
|
||||
<version>@project.version@</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>aot-generate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot</artifactId>
|
||||
<version>@project.version@</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<version>@jakarta-servlet.version@</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class SampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SampleApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.maven;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.maven.model.Resource;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
|
||||
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
|
||||
/**
|
||||
* Base class to run a spring application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
* @author David Liu
|
||||
* @author Daniel Young
|
||||
* @author Dmytro Nosan
|
||||
* @since 3.0.0
|
||||
* @see RunMojo
|
||||
* @see StartMojo
|
||||
*/
|
||||
public abstract class AbstractApplicationRunMojo extends AbstractRunMojo {
|
||||
|
||||
/**
|
||||
* Add maven resources to the classpath directly, this allows live in-place editing of
|
||||
* resources. Duplicate resources are removed from {@code target/classes} to prevent
|
||||
* them to appear twice if {@code ClassLoader.getResources()} is called. Please
|
||||
* consider adding {@code spring-boot-devtools} to your project instead as it provides
|
||||
* this feature and many more.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.addResources", defaultValue = "false")
|
||||
private boolean addResources = false;
|
||||
|
||||
/**
|
||||
* Path to agent jars. NOTE: a forked process is required to use this feature.
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.agents")
|
||||
private File[] agents;
|
||||
|
||||
/**
|
||||
* Flag to say that the agent requires -noverify.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.noverify")
|
||||
private boolean noverify = false;
|
||||
|
||||
/**
|
||||
* Flag to include the test classpath when running.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
|
||||
private Boolean useTestClasspath;
|
||||
|
||||
@Override
|
||||
protected void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException, MojoFailureException {
|
||||
List<String> args = new ArrayList<>();
|
||||
addAgents(args);
|
||||
addJvmArgs(args);
|
||||
addClasspath(args);
|
||||
args.add(startClassName);
|
||||
addArgs(args);
|
||||
run(workingDirectory, args, environmentVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run with a forked VM, using the specified command line arguments.
|
||||
* @param workingDirectory the working directory of the forked JVM
|
||||
* @param args the arguments (JVM arguments and application arguments)
|
||||
* @param environmentVariables the environment variables
|
||||
* @throws MojoExecutionException in case of MOJO execution errors
|
||||
* @throws MojoFailureException in case of MOJO failures
|
||||
*/
|
||||
protected abstract void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException, MojoFailureException;
|
||||
|
||||
@Override
|
||||
protected URL[] getClassPathUrls() throws MojoExecutionException {
|
||||
try {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
addUserDefinedDirectories(urls);
|
||||
addResources(urls);
|
||||
addProjectClasses(urls);
|
||||
FilterArtifacts filters = (this.useTestClasspath ? getFilters() : getFilters(new TestArtifactFilter()));
|
||||
addDependencies(urls, filters);
|
||||
return urls.toArray(new URL[0]);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MojoExecutionException("Unable to build classpath", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAgents(List<String> args) {
|
||||
if (this.agents != null) {
|
||||
if (getLog().isInfoEnabled()) {
|
||||
getLog().info("Attaching agents: " + Arrays.asList(this.agents));
|
||||
}
|
||||
for (File agent : this.agents) {
|
||||
args.add("-javaagent:" + agent);
|
||||
}
|
||||
}
|
||||
if (this.noverify) {
|
||||
args.add("-noverify");
|
||||
}
|
||||
}
|
||||
|
||||
private void addResources(List<URL> urls) throws IOException {
|
||||
if (this.addResources) {
|
||||
for (Resource resource : this.project.getResources()) {
|
||||
File directory = new File(resource.getDirectory());
|
||||
urls.add(directory.toURI().toURL());
|
||||
FileUtils.removeDuplicatesFromOutputDirectory(this.classesDirectory, directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,6 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -30,7 +29,6 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.model.Resource;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.Component;
|
||||
|
@ -41,12 +39,11 @@ import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
|
|||
import org.apache.maven.toolchain.Toolchain;
|
||||
import org.apache.maven.toolchain.ToolchainManager;
|
||||
|
||||
import org.springframework.boot.loader.tools.FileUtils;
|
||||
import org.springframework.boot.loader.tools.JavaExecutable;
|
||||
import org.springframework.boot.loader.tools.MainClassFinder;
|
||||
|
||||
/**
|
||||
* Base class to run a spring application.
|
||||
* Base class to support running a process that deals with a Spring application.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Stephane Nicoll
|
||||
|
@ -54,19 +51,19 @@ import org.springframework.boot.loader.tools.MainClassFinder;
|
|||
* @author Daniel Young
|
||||
* @author Dmytro Nosan
|
||||
* @since 1.3.0
|
||||
* @see RunMojo
|
||||
* @see StartMojo
|
||||
*/
|
||||
public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
||||
|
||||
private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication";
|
||||
|
||||
private static final int EXIT_CODE_SIGINT = 130;
|
||||
|
||||
/**
|
||||
* The Maven project.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(defaultValue = "${project}", readonly = true, required = true)
|
||||
private MavenProject project;
|
||||
protected MavenProject project;
|
||||
|
||||
/**
|
||||
* The current Maven session. This is used for toolchain manager API calls.
|
||||
|
@ -82,31 +79,6 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
@Component
|
||||
private ToolchainManager toolchainManager;
|
||||
|
||||
/**
|
||||
* Add maven resources to the classpath directly, this allows live in-place editing of
|
||||
* resources. Duplicate resources are removed from {@code target/classes} to prevent
|
||||
* them to appear twice if {@code ClassLoader.getResources()} is called. Please
|
||||
* consider adding {@code spring-boot-devtools} to your project instead as it provides
|
||||
* this feature and many more.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.addResources", defaultValue = "false")
|
||||
private boolean addResources = false;
|
||||
|
||||
/**
|
||||
* Path to agent jars.
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.agents")
|
||||
private File[] agents;
|
||||
|
||||
/**
|
||||
* Flag to say that the agent requires -noverify.
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.noverify")
|
||||
private boolean noverify = false;
|
||||
|
||||
/**
|
||||
* Current working directory to use for the application. If not specified, basedir
|
||||
* will be used.
|
||||
|
@ -185,14 +157,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
* @since 1.0.0
|
||||
*/
|
||||
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
|
||||
private File classesDirectory;
|
||||
|
||||
/**
|
||||
* Flag to include the test classpath when running.
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@Parameter(property = "spring-boot.run.useTestClasspath", defaultValue = "false")
|
||||
private Boolean useTestClasspath;
|
||||
protected File classesDirectory;
|
||||
|
||||
/**
|
||||
* Skip the execution.
|
||||
|
@ -207,31 +172,30 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
getLog().debug("skipping run as per configuration.");
|
||||
return;
|
||||
}
|
||||
run(getStartClass());
|
||||
}
|
||||
|
||||
private void run(String startClassName) throws MojoExecutionException, MojoFailureException {
|
||||
List<String> args = new ArrayList<>();
|
||||
addAgents(args);
|
||||
addJvmArgs(args);
|
||||
addClasspath(args);
|
||||
args.add(startClassName);
|
||||
addArgs(args);
|
||||
run((this.workingDirectory != null) ? this.workingDirectory : this.project.getBasedir(), args,
|
||||
run((this.workingDirectory != null) ? this.workingDirectory : this.project.getBasedir(), getStartClass(),
|
||||
determineEnvironmentVariables());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run with a forked VM, using the specified command line arguments.
|
||||
* Run with a forked VM, using the specified class name.
|
||||
* @param workingDirectory the working directory of the forked JVM
|
||||
* @param args the arguments (JVM arguments and application arguments)
|
||||
* @param startClassName the name of the class to execute
|
||||
* @param environmentVariables the environment variables
|
||||
* @throws MojoExecutionException in case of MOJO execution errors
|
||||
* @throws MojoFailureException in case of MOJO failures
|
||||
*/
|
||||
protected abstract void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
|
||||
protected abstract void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException, MojoFailureException;
|
||||
|
||||
/**
|
||||
* Specify if the forked process has terminated successfully, based on its exit code.
|
||||
* @param exitCode the exit code of the process
|
||||
* @return {@code true} if the process has terminated successfully
|
||||
*/
|
||||
protected boolean hasTerminatedSuccessfully(int exitCode) {
|
||||
return (exitCode == 0 || exitCode == EXIT_CODE_SIGINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the application arguments to use.
|
||||
* @return a {@link RunArguments} defining the application arguments
|
||||
|
@ -261,7 +225,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
return new EnvVariables(this.environmentVariables);
|
||||
}
|
||||
|
||||
private void addArgs(List<String> args) {
|
||||
protected void addArgs(List<String> args) {
|
||||
RunArguments applicationArguments = resolveApplicationArguments();
|
||||
Collections.addAll(args, applicationArguments.asArray());
|
||||
logArguments("Application argument(s): ", applicationArguments.asArray());
|
||||
|
@ -290,26 +254,12 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
return new RunArguments(stringBuilder.toString());
|
||||
}
|
||||
|
||||
private void addJvmArgs(List<String> args) {
|
||||
protected void addJvmArgs(List<String> args) {
|
||||
RunArguments jvmArguments = resolveJvmArguments();
|
||||
Collections.addAll(args, jvmArguments.asArray());
|
||||
logArguments("JVM argument(s): ", jvmArguments.asArray());
|
||||
}
|
||||
|
||||
private void addAgents(List<String> args) {
|
||||
if (this.agents != null) {
|
||||
if (getLog().isInfoEnabled()) {
|
||||
getLog().info("Attaching agents: " + Arrays.asList(this.agents));
|
||||
}
|
||||
for (File agent : this.agents) {
|
||||
args.add("-javaagent:" + agent);
|
||||
}
|
||||
}
|
||||
if (this.noverify) {
|
||||
args.add("-noverify");
|
||||
}
|
||||
}
|
||||
|
||||
private void addActiveProfileArgument(RunArguments arguments) {
|
||||
if (this.profiles.length > 0) {
|
||||
StringBuilder arg = new StringBuilder("--spring.profiles.active=");
|
||||
|
@ -324,7 +274,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
}
|
||||
}
|
||||
|
||||
private void addClasspath(List<String> args) throws MojoExecutionException {
|
||||
protected void addClasspath(List<String> args) throws MojoExecutionException {
|
||||
try {
|
||||
StringBuilder classpath = new StringBuilder();
|
||||
for (URL ele : getClassPathUrls()) {
|
||||
|
@ -344,7 +294,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
}
|
||||
}
|
||||
|
||||
private String getStartClass() throws MojoExecutionException {
|
||||
protected String getStartClass() throws MojoExecutionException {
|
||||
String mainClass = this.mainClass;
|
||||
if (mainClass == null) {
|
||||
try {
|
||||
|
@ -361,21 +311,9 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
return mainClass;
|
||||
}
|
||||
|
||||
protected URL[] getClassPathUrls() throws MojoExecutionException {
|
||||
try {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
addUserDefinedDirectories(urls);
|
||||
addResources(urls);
|
||||
addProjectClasses(urls);
|
||||
addDependencies(urls);
|
||||
return urls.toArray(new URL[0]);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MojoExecutionException("Unable to build classpath", ex);
|
||||
}
|
||||
}
|
||||
protected abstract URL[] getClassPathUrls() throws MojoExecutionException;
|
||||
|
||||
private void addUserDefinedDirectories(List<URL> urls) throws MalformedURLException {
|
||||
protected void addUserDefinedDirectories(List<URL> urls) throws MalformedURLException {
|
||||
if (this.directories != null) {
|
||||
for (String directory : this.directories) {
|
||||
urls.add(new File(directory).toURI().toURL());
|
||||
|
@ -383,22 +321,12 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
}
|
||||
}
|
||||
|
||||
private void addResources(List<URL> urls) throws IOException {
|
||||
if (this.addResources) {
|
||||
for (Resource resource : this.project.getResources()) {
|
||||
File directory = new File(resource.getDirectory());
|
||||
urls.add(directory.toURI().toURL());
|
||||
FileUtils.removeDuplicatesFromOutputDirectory(this.classesDirectory, directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addProjectClasses(List<URL> urls) throws MalformedURLException {
|
||||
protected void addProjectClasses(List<URL> urls) throws MalformedURLException {
|
||||
urls.add(this.classesDirectory.toURI().toURL());
|
||||
}
|
||||
|
||||
private void addDependencies(List<URL> urls) throws MalformedURLException, MojoExecutionException {
|
||||
FilterArtifacts filters = (this.useTestClasspath ? getFilters() : getFilters(new TestArtifactFilter()));
|
||||
protected void addDependencies(List<URL> urls, FilterArtifacts filters)
|
||||
throws MalformedURLException, MojoExecutionException {
|
||||
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), filters);
|
||||
for (Artifact artifact : artifacts) {
|
||||
if (artifact.getFile() != null) {
|
||||
|
@ -413,7 +341,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||
}
|
||||
}
|
||||
|
||||
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
|
||||
static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
|
||||
|
||||
TestArtifactFilter() {
|
||||
super("", Artifact.SCOPE_TEST);
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* 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.maven;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
|
||||
import org.springframework.boot.loader.tools.RunProcess;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
/**
|
||||
* Invoke the AOT engine on the application.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Mojo(name = "aot-generate", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true,
|
||||
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
|
||||
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
|
||||
public class AotGenerateMojo extends AbstractRunMojo {
|
||||
|
||||
private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.boot.AotProcessor";
|
||||
|
||||
/**
|
||||
* Directory containing the generated sources.
|
||||
*/
|
||||
@Parameter(defaultValue = "${project.build.directory}/spring-aot/main/sources", required = true)
|
||||
private File generatedSources;
|
||||
|
||||
/**
|
||||
* Directory containing the generated resources.
|
||||
*/
|
||||
@Parameter(defaultValue = "${project.build.directory}/spring-aot/main/resources", required = true)
|
||||
private File generatedResources;
|
||||
|
||||
@Override
|
||||
protected void run(File workingDirectory, String startClassName, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException, MojoFailureException {
|
||||
try {
|
||||
deletePreviousAotAssets();
|
||||
generateAotAssets(workingDirectory, startClassName, environmentVariables);
|
||||
compileSourceFiles(getClassPathUrls());
|
||||
copyNativeConfiguration(this.generatedResources.toPath());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MojoExecutionException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePreviousAotAssets() {
|
||||
FileSystemUtils.deleteRecursively(this.generatedSources);
|
||||
FileSystemUtils.deleteRecursively(this.generatedResources);
|
||||
}
|
||||
|
||||
private void generateAotAssets(File workingDirectory, String startClassName,
|
||||
Map<String, String> environmentVariables) throws MojoExecutionException {
|
||||
List<String> args = new ArrayList<>();
|
||||
addJvmArgs(args);
|
||||
addClasspath(args);
|
||||
args.add(AOT_PROCESSOR_CLASS_NAME);
|
||||
// Adding arguments that are necessary for generation
|
||||
args.add(startClassName);
|
||||
args.add(this.generatedSources.toString());
|
||||
args.add(this.generatedResources.toString());
|
||||
args.add(this.project.getGroupId());
|
||||
args.add(this.project.getArtifactId());
|
||||
addArgs(args);
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug("Generating AOT assets using command: " + args);
|
||||
}
|
||||
int exitCode = forkJvm(workingDirectory, args, environmentVariables);
|
||||
if (!hasTerminatedSuccessfully(exitCode)) {
|
||||
throw new MojoExecutionException("AOT generation process finished with exit code: " + exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private int forkJvm(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException {
|
||||
try {
|
||||
RunProcess runProcess = new RunProcess(workingDirectory, getJavaExecutable());
|
||||
return runProcess.run(true, args, environmentVariables);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MojoExecutionException("Could not exec java", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL[] getClassPathUrls() throws MojoExecutionException {
|
||||
try {
|
||||
List<URL> urls = new ArrayList<>();
|
||||
addUserDefinedDirectories(urls);
|
||||
addProjectClasses(urls);
|
||||
addDependencies(urls, getFilters(new TestArtifactFilter()));
|
||||
return urls.toArray(new URL[0]);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MojoExecutionException("Unable to build classpath", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void compileSourceFiles(URL[] classpathUrls) throws IOException {
|
||||
List<Path> sourceFiles = Files.walk(this.generatedSources.toPath()).filter(Files::isRegularFile).toList();
|
||||
if (sourceFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
try (StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null)) {
|
||||
List<String> options = List.of("-cp",
|
||||
Arrays.stream(classpathUrls).map(URL::toString).collect(Collectors.joining(":")), "-d",
|
||||
this.classesDirectory.toPath().toAbsolutePath().toString());
|
||||
Iterable<? extends JavaFileObject> compilationUnits = fm.getJavaFileObjectsFromPaths(sourceFiles);
|
||||
Errors errors = new Errors();
|
||||
CompilationTask task = compiler.getTask(null, fm, errors, options, null, compilationUnits);
|
||||
boolean result = task.call();
|
||||
if (!result || errors.hasReportedErrors()) {
|
||||
throw new IllegalStateException("Unable to compile generated source" + errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copyNativeConfiguration(Path generatedResources) throws IOException {
|
||||
Path targetDirectory = this.classesDirectory.toPath().resolve("META-INF/native-image");
|
||||
Path sourceDirectory = generatedResources.resolve("META-INF/native-image");
|
||||
List<Path> files = Files.walk(sourceDirectory).filter(Files::isRegularFile).toList();
|
||||
for (Path file : files) {
|
||||
String relativeFileName = file.subpath(sourceDirectory.getNameCount(), file.getNameCount()).toString();
|
||||
getLog().debug("Copying '" + relativeFileName + "' to " + targetDirectory);
|
||||
Path target = targetDirectory.resolve(relativeFileName);
|
||||
Files.createDirectories(target.getParent());
|
||||
Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link DiagnosticListener} used to collect errors.
|
||||
*/
|
||||
static class Errors implements DiagnosticListener<JavaFileObject> {
|
||||
|
||||
private final StringBuilder message = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
|
||||
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
|
||||
this.message.append("\n");
|
||||
this.message.append(diagnostic.getMessage(Locale.getDefault()));
|
||||
this.message.append(" ");
|
||||
this.message.append(diagnostic.getSource().getName());
|
||||
this.message.append(" ");
|
||||
this.message.append(diagnostic.getLineNumber()).append(":").append(diagnostic.getColumnNumber());
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasReportedErrors() {
|
||||
return this.message.length() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.message.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -42,9 +42,7 @@ import org.springframework.boot.loader.tools.RunProcess;
|
|||
@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE,
|
||||
requiresDependencyResolution = ResolutionScope.TEST)
|
||||
@Execute(phase = LifecyclePhase.TEST_COMPILE)
|
||||
public class RunMojo extends AbstractRunMojo {
|
||||
|
||||
private static final int EXIT_CODE_SIGINT = 130;
|
||||
public class RunMojo extends AbstractApplicationRunMojo {
|
||||
|
||||
/**
|
||||
* Whether the JVM's launch should be optimized.
|
||||
|
@ -78,7 +76,7 @@ public class RunMojo extends AbstractRunMojo {
|
|||
protected void run(File workingDirectory, List<String> args, Map<String, String> environmentVariables)
|
||||
throws MojoExecutionException {
|
||||
int exitCode = forkJvm(workingDirectory, args, environmentVariables);
|
||||
if (exitCode == 0 || exitCode == EXIT_CODE_SIGINT) {
|
||||
if (hasTerminatedSuccessfully(exitCode)) {
|
||||
return;
|
||||
}
|
||||
throw new MojoExecutionException("Application finished with exit code: " + exitCode);
|
||||
|
|
|
@ -49,7 +49,7 @@ import org.springframework.boot.loader.tools.RunProcess;
|
|||
*/
|
||||
@Mojo(name = "start", requiresProject = true, defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST,
|
||||
requiresDependencyResolution = ResolutionScope.TEST)
|
||||
public class StartMojo extends AbstractRunMojo {
|
||||
public class StartMojo extends AbstractApplicationRunMojo {
|
||||
|
||||
private static final String ENABLE_MBEAN_PROPERTY = "--spring.application.admin.enabled=true";
|
||||
|
||||
|
|
Loading…
Reference in New Issue