mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Add Maven plugin support for processing test contexts Ahead-of-time
Refactor and update the Spring Boot Maven Plugin so that it can be used to perform AOT processing of test classes. Closes gh-32191
This commit is contained in:
parent
e599a70425
commit
09bd531fe5
@ -446,6 +446,8 @@ public class MavenPluginPlugin implements Plugin<Project> {
|
|||||||
effectiveBom.property("spring-framework.version", versions::setProperty);
|
effectiveBom.property("spring-framework.version", versions::setProperty);
|
||||||
effectiveBom.property("jakarta-servlet.version", versions::setProperty);
|
effectiveBom.property("jakarta-servlet.version", versions::setProperty);
|
||||||
effectiveBom.property("kotlin.version", versions::setProperty);
|
effectiveBom.property("kotlin.version", versions::setProperty);
|
||||||
|
effectiveBom.property("assertj.version", versions::setProperty);
|
||||||
|
effectiveBom.property("junit-jupiter.version", versions::setProperty);
|
||||||
return versions;
|
return versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +132,13 @@ publishing.publications.withType(MavenPublication) {
|
|||||||
delegate.useDefaultDelimiters('false')
|
delegate.useDefaultDelimiters('false')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
plugin {
|
||||||
|
delegate.groupId('org.graalvm.buildtools')
|
||||||
|
delegate.artifactId('native-maven-plugin')
|
||||||
|
configuration {
|
||||||
|
delegate.extensions('true')
|
||||||
|
}
|
||||||
|
}
|
||||||
plugin {
|
plugin {
|
||||||
delegate.groupId('io.github.git-commit-id')
|
delegate.groupId('io.github.git-commit-id')
|
||||||
delegate.artifactId('git-commit-id-maven-plugin')
|
delegate.artifactId('git-commit-id-maven-plugin')
|
||||||
@ -226,6 +233,13 @@ publishing.publications.withType(MavenPublication) {
|
|||||||
profiles {
|
profiles {
|
||||||
profile {
|
profile {
|
||||||
delegate.id("native")
|
delegate.id("native")
|
||||||
|
delegate.dependencies {
|
||||||
|
dependency {
|
||||||
|
delegate.groupId('org.junit.platform')
|
||||||
|
delegate.artifactId('junit-platform-launcher')
|
||||||
|
delegate.scope('test')
|
||||||
|
}
|
||||||
|
}
|
||||||
build {
|
build {
|
||||||
plugins {
|
plugins {
|
||||||
plugin {
|
plugin {
|
||||||
@ -238,7 +252,12 @@ publishing.publications.withType(MavenPublication) {
|
|||||||
delegate.goal('process-aot')
|
delegate.goal('process-aot')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
execution {
|
||||||
|
delegate.id('process-test-aot')
|
||||||
|
goals {
|
||||||
|
delegate.goal('process-test-aot')
|
||||||
}
|
}
|
||||||
|
} }
|
||||||
}
|
}
|
||||||
plugin {
|
plugin {
|
||||||
delegate.groupId('org.graalvm.buildtools')
|
delegate.groupId('org.graalvm.buildtools')
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
[[aot]]
|
[[aot]]
|
||||||
= Optimizing Your Application at Build-Time
|
= Ahead-of-Time Processing
|
||||||
|
Spring AOT is a process that analyzes your code at build-time in order to generate an optimized version of it.
|
||||||
|
It is most often used to help generate GraalVM native images.
|
||||||
|
|
||||||
Spring AOT inspects an application at build-time and generates an optimized version of it.
|
The Spring Boot Maven plugin offers goals that can be used to perform AOT processing on both application and test code.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== Processing Applications
|
||||||
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.
|
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.
|
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.
|
For instance, these are used to generate the necessary reflection configuration that GraalVM needs to initialize the context in a native image.
|
||||||
@ -18,5 +24,21 @@ This has an important difference compared to what a regular Spring Boot applicat
|
|||||||
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.
|
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 `process-aot` goal shares a number of properties with the <<run,run goal>> for that reason.
|
The `process-aot` goal shares a number of properties with the <<run,run goal>> for that reason.
|
||||||
|
|
||||||
|
|
||||||
include::goals/process-aot.adoc[leveloffset=+1]
|
include::goals/process-aot.adoc[leveloffset=+1]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
== Processing Tests
|
||||||
|
The AOT engine can be applied to JUnit 5 tests that use Spring's Test Context Framework.
|
||||||
|
Suitable tests are processed by the AOT engine in order to generate `ApplicationContextInitialzer` code.
|
||||||
|
|
||||||
|
To configure your application to use this feature, add an execution for the `process-test-aot` goal, as shown in the following example:
|
||||||
|
|
||||||
|
[source,xml,indent=0,subs="verbatim,attributes",tabsize=4]
|
||||||
|
----
|
||||||
|
include::../maven/aot-test/pom.xml[tags=aot]
|
||||||
|
----
|
||||||
|
|
||||||
|
As with application AOT processing, the `BeanFactory` is fully prepared at build-time.
|
||||||
|
|
||||||
|
include::goals/process-test-aot.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>process-test-aot</id>
|
||||||
|
<goals>
|
||||||
|
<goal>process-test-aot</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<!-- end::aot[] -->
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
*/
|
*/
|
||||||
@ExtendWith(MavenBuildExtension.class)
|
@ExtendWith(MavenBuildExtension.class)
|
||||||
public class AotGenerateTests {
|
public class AotTests {
|
||||||
|
|
||||||
@TestTemplate
|
@TestTemplate
|
||||||
void whenAotRunsSourcesAreGenerated(MavenBuild mavenBuild) {
|
void whenAotRunsSourcesAreGenerated(MavenBuild mavenBuild) {
|
||||||
@ -113,6 +113,20 @@ public class AotGenerateTests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
void whenAotTestRunsSourcesAndResourcesAreGenerated(MavenBuild mavenBuild) {
|
||||||
|
mavenBuild.project("aot-test").goals("test").execute((project) -> {
|
||||||
|
Path aotDirectory = project.toPath().resolve("target/spring-aot/test");
|
||||||
|
assertThat(collectRelativePaths(aotDirectory.resolve("sources"))).contains(Path.of("org", "test",
|
||||||
|
"SampleApplicationTests__TestContext001_ApplicationContextInitializer.java"));
|
||||||
|
Path testClassesDirectory = project.toPath().resolve("target/test-classes");
|
||||||
|
assertThat(collectRelativePaths(testClassesDirectory)).contains(Path.of("META-INF", "native-image",
|
||||||
|
"org.springframework.boot.maven.it", "aot-test", "reflect-config.json"));
|
||||||
|
assertThat(collectRelativePaths(testClassesDirectory)).contains(Path.of("org", "test",
|
||||||
|
"SampleApplicationTests__TestContext001_ApplicationContextInitializer.class"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Stream<Path> collectRelativePaths(Path sourceDirectory) {
|
Stream<Path> collectRelativePaths(Path sourceDirectory) {
|
||||||
try {
|
try {
|
||||||
return Files.walk(sourceDirectory).filter(Files::isRegularFile)
|
return Files.walk(sourceDirectory).filter(Files::isRegularFile)
|
@ -0,0 +1,66 @@
|
|||||||
|
<?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-test</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>process-test-aot</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>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-test</artifactId>
|
||||||
|
<version>@spring-framework.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-test</artifactId>
|
||||||
|
<version>@project.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>@assertj.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>@junit-jupiter.version@</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* 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.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,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.fail;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||||
|
|
||||||
|
@SpringJUnitConfig
|
||||||
|
class SampleApplicationTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MyBean myBean;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
assertThat(this.myBean).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class MyConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
MyBean myBean() {
|
||||||
|
return new MyBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyBean {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* 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.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.execution.MavenSession;
|
||||||
|
import org.apache.maven.plugin.MojoExecutionException;
|
||||||
|
import org.apache.maven.plugin.MojoFailureException;
|
||||||
|
import org.apache.maven.plugins.annotations.Component;
|
||||||
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
|
import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
|
||||||
|
import org.apache.maven.toolchain.ToolchainManager;
|
||||||
|
|
||||||
|
import org.springframework.boot.maven.CommandLineBuilder.ClasspathBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base class for AOT processing MOJOs.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractAotMojo extends AbstractDependencyFilterMojo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current Maven session. This is used for toolchain manager API calls.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${session}", readonly = true)
|
||||||
|
private MavenSession session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The toolchain manager to use to locate a custom JDK.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
private ToolchainManager toolchainManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip the execution.
|
||||||
|
*/
|
||||||
|
@Parameter(property = "spring-boot.aot.skip", defaultValue = "false")
|
||||||
|
private boolean skip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of JVM system properties to pass to the AOT process.
|
||||||
|
*/
|
||||||
|
@Parameter
|
||||||
|
private Map<String, String> systemPropertyVariables;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM arguments that should be associated with the AOT process. On command line, make
|
||||||
|
* sure to wrap multiple values between quotes.
|
||||||
|
*/
|
||||||
|
@Parameter(property = "spring-boot.aot.jvmArguments")
|
||||||
|
private String jvmArguments;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||||
|
if (this.skip) {
|
||||||
|
getLog().debug("Skipping AOT execution as per configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
executeAot();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new MojoExecutionException(ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void executeAot() throws Exception;
|
||||||
|
|
||||||
|
protected void generateAotAssets(URL[] classPath, String processorClassName, String... arguments) throws Exception {
|
||||||
|
List<String> command = CommandLineBuilder.forMainClass(processorClassName)
|
||||||
|
.withSystemProperties(this.systemPropertyVariables)
|
||||||
|
.withJvmArguments(new RunArguments(this.jvmArguments).asArray()).withClasspath(classPath)
|
||||||
|
.withArguments(arguments).build();
|
||||||
|
if (getLog().isDebugEnabled()) {
|
||||||
|
getLog().debug("Generating AOT assets using command: " + command);
|
||||||
|
}
|
||||||
|
JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager);
|
||||||
|
processExecutor.run(this.project.getBasedir(), command, Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void compileSourceFiles(URL[] classPath, File sourcesDirectory, File outputDirectory)
|
||||||
|
throws Exception {
|
||||||
|
List<Path> sourceFiles = Files.walk(sourcesDirectory.toPath()).filter(Files::isRegularFile).toList();
|
||||||
|
if (sourceFiles.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||||
|
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
options.add("-cp");
|
||||||
|
options.add(ClasspathBuilder.build(Arrays.asList(classPath)));
|
||||||
|
options.add("-d");
|
||||||
|
options.add(outputDirectory.toPath().toAbsolutePath().toString());
|
||||||
|
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromPaths(sourceFiles);
|
||||||
|
Errors errors = new Errors();
|
||||||
|
CompilationTask task = compiler.getTask(null, fileManager, errors, options, null, compilationUnits);
|
||||||
|
boolean result = task.call();
|
||||||
|
if (!result || errors.hasReportedErrors()) {
|
||||||
|
throw new IllegalStateException("Unable to compile generated source" + errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final URL[] getClassPath(File classesDirectory, ArtifactsFilter... artifactFilters)
|
||||||
|
throws MojoExecutionException {
|
||||||
|
List<URL> urls = new ArrayList<>();
|
||||||
|
urls.add(toURL(classesDirectory));
|
||||||
|
urls.addAll(getDependencyURLs(artifactFilters));
|
||||||
|
return urls.toArray(URL[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void copyAll(Path from, Path to) throws IOException {
|
||||||
|
List<Path> files = (Files.exists(from)) ? Files.walk(from).filter(Files::isRegularFile).toList()
|
||||||
|
: Collections.emptyList();
|
||||||
|
for (Path file : files) {
|
||||||
|
String relativeFileName = file.subpath(from.getNameCount(), file.getNameCount()).toString();
|
||||||
|
getLog().debug("Copying '" + relativeFileName + "' to " + to);
|
||||||
|
Path target = to.resolve(relativeFileName);
|
||||||
|
Files.createDirectories(target.getParent());
|
||||||
|
Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link DiagnosticListener} used to collect errors.
|
||||||
|
*/
|
||||||
|
protected 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,12 +20,14 @@ import java.io.File;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
import org.apache.maven.artifact.Artifact;
|
import org.apache.maven.artifact.Artifact;
|
||||||
|
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
|
||||||
import org.apache.maven.plugin.AbstractMojo;
|
import org.apache.maven.plugin.AbstractMojo;
|
||||||
import org.apache.maven.plugin.MojoExecutionException;
|
import org.apache.maven.plugin.MojoExecutionException;
|
||||||
import org.apache.maven.plugins.annotations.Parameter;
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
@ -157,9 +159,12 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
|
|||||||
return cleaned.toString();
|
return cleaned.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class TestScopeArtifactFilter extends AbstractArtifactFeatureFilter {
|
/**
|
||||||
|
* {@link ArtifactFilter} to exclude test scope dependencies.
|
||||||
|
*/
|
||||||
|
protected static class ExcludeTestScopeArtifactFilter extends AbstractArtifactFeatureFilter {
|
||||||
|
|
||||||
TestScopeArtifactFilter() {
|
ExcludeTestScopeArtifactFilter() {
|
||||||
super("", Artifact.SCOPE_TEST);
|
super("", Artifact.SCOPE_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,4 +175,20 @@ public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ArtifactFilter} that only include runtime scopes.
|
||||||
|
*/
|
||||||
|
protected static class RuntimeArtifactFilter implements ArtifactFilter {
|
||||||
|
|
||||||
|
private static final Collection<String> SCOPES = List.of(Artifact.SCOPE_COMPILE,
|
||||||
|
Artifact.SCOPE_COMPILE_PLUS_RUNTIME, Artifact.SCOPE_RUNTIME);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean include(Artifact artifact) {
|
||||||
|
String scope = artifact.getScope();
|
||||||
|
return !artifact.isOptional() && (scope == null || SCOPES.contains(scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -371,7 +371,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
|
|||||||
|
|
||||||
private void addDependencies(List<URL> urls) throws MalformedURLException, MojoExecutionException {
|
private void addDependencies(List<URL> urls) throws MalformedURLException, MojoExecutionException {
|
||||||
Set<Artifact> artifacts = (this.useTestClasspath) ? filterDependencies(this.project.getArtifacts())
|
Set<Artifact> artifacts = (this.useTestClasspath) ? filterDependencies(this.project.getArtifacts())
|
||||||
: filterDependencies(this.project.getArtifacts(), new TestScopeArtifactFilter());
|
: filterDependencies(this.project.getArtifacts(), new ExcludeTestScopeArtifactFilter());
|
||||||
for (Artifact artifact : artifacts) {
|
for (Artifact artifact : artifacts) {
|
||||||
if (artifact.getFile() != null) {
|
if (artifact.getFile() != null) {
|
||||||
urls.add(artifact.getFile().toURI().toURL());
|
urls.add(artifact.getFile().toURI().toURL());
|
||||||
|
@ -17,37 +17,15 @@
|
|||||||
package org.springframework.boot.maven;
|
package org.springframework.boot.maven;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
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.execution.MavenSession;
|
|
||||||
import org.apache.maven.plugin.MojoExecutionException;
|
|
||||||
import org.apache.maven.plugin.MojoFailureException;
|
|
||||||
import org.apache.maven.plugins.annotations.Component;
|
|
||||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||||
import org.apache.maven.plugins.annotations.Mojo;
|
import org.apache.maven.plugins.annotations.Mojo;
|
||||||
import org.apache.maven.plugins.annotations.Parameter;
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||||
import org.apache.maven.toolchain.ToolchainManager;
|
|
||||||
|
|
||||||
import org.springframework.boot.maven.CommandLineBuilder.ClasspathBuilder;
|
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,22 +38,10 @@ import org.springframework.util.ObjectUtils;
|
|||||||
@Mojo(name = "process-aot", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true,
|
@Mojo(name = "process-aot", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true,
|
||||||
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
|
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
|
||||||
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
|
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
|
||||||
public class ProcessAotMojo extends AbstractDependencyFilterMojo {
|
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.AotProcessor";
|
||||||
|
|
||||||
/**
|
|
||||||
* The current Maven session. This is used for toolchain manager API calls.
|
|
||||||
*/
|
|
||||||
@Parameter(defaultValue = "${session}", readonly = true)
|
|
||||||
private MavenSession session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The toolchain manager to use to locate a custom JDK.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
private ToolchainManager toolchainManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory containing the classes and resource files that should be packaged into
|
* Directory containing the classes and resource files that should be packaged into
|
||||||
* the archive.
|
* the archive.
|
||||||
@ -83,12 +49,6 @@ public class ProcessAotMojo extends AbstractDependencyFilterMojo {
|
|||||||
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
|
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
|
||||||
private File classesDirectory;
|
private File classesDirectory;
|
||||||
|
|
||||||
/**
|
|
||||||
* Skip the execution.
|
|
||||||
*/
|
|
||||||
@Parameter(property = "spring-boot.aot.skip", defaultValue = "false")
|
|
||||||
private boolean skip;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory containing the generated sources.
|
* Directory containing the generated sources.
|
||||||
*/
|
*/
|
||||||
@ -107,19 +67,6 @@ public class ProcessAotMojo extends AbstractDependencyFilterMojo {
|
|||||||
@Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true)
|
@Parameter(defaultValue = "${project.build.directory}/spring-aot/main/classes", required = true)
|
||||||
private File generatedClasses;
|
private File generatedClasses;
|
||||||
|
|
||||||
/**
|
|
||||||
* List of JVM system properties to pass to the AOT process.
|
|
||||||
*/
|
|
||||||
@Parameter
|
|
||||||
private Map<String, String> systemPropertyVariables;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JVM arguments that should be associated with the AOT process. On command line, make
|
|
||||||
* sure to wrap multiple values between quotes.
|
|
||||||
*/
|
|
||||||
@Parameter(property = "spring-boot.aot.jvmArguments")
|
|
||||||
private String jvmArguments;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the main class to use as the source for the AOT process. If not specified
|
* Name of the main class to use as the source for the AOT process. If not specified
|
||||||
* the first compiled class found that contains a 'main' method will be used.
|
* the first compiled class found that contains a 'main' method will be used.
|
||||||
@ -134,36 +81,16 @@ public class ProcessAotMojo extends AbstractDependencyFilterMojo {
|
|||||||
private String[] profiles;
|
private String[] profiles;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
protected void executeAot() throws Exception {
|
||||||
if (this.skip) {
|
String applicationClass = (this.mainClass != null) ? this.mainClass
|
||||||
getLog().debug("skipping execution as per configuration.");
|
: SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory);
|
||||||
return;
|
URL[] classPath = getClassPath();
|
||||||
}
|
generateAotAssets(classPath, AOT_PROCESSOR_CLASS_NAME, getAotArguments(applicationClass));
|
||||||
try {
|
compileSourceFiles(classPath, this.generatedSources, this.classesDirectory);
|
||||||
generateAotAssets();
|
|
||||||
compileSourceFiles();
|
|
||||||
copyAll(this.generatedResources.toPath().resolve("META-INF/native-image"),
|
copyAll(this.generatedResources.toPath().resolve("META-INF/native-image"),
|
||||||
this.classesDirectory.toPath().resolve("META-INF/native-image"));
|
this.classesDirectory.toPath().resolve("META-INF/native-image"));
|
||||||
copyAll(this.generatedClasses.toPath(), this.classesDirectory.toPath());
|
copyAll(this.generatedClasses.toPath(), this.classesDirectory.toPath());
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
|
||||||
throw new MojoExecutionException(ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void generateAotAssets() throws MojoExecutionException {
|
|
||||||
String applicationClass = (this.mainClass != null) ? this.mainClass
|
|
||||||
: SpringBootApplicationClassFinder.findSingleClass(this.classesDirectory);
|
|
||||||
List<String> command = CommandLineBuilder.forMainClass(AOT_PROCESSOR_CLASS_NAME)
|
|
||||||
.withSystemProperties(this.systemPropertyVariables)
|
|
||||||
.withJvmArguments(new RunArguments(this.jvmArguments).asArray()).withClasspath(getClassPathUrls())
|
|
||||||
.withArguments(getAotArguments(applicationClass)).build();
|
|
||||||
if (getLog().isDebugEnabled()) {
|
|
||||||
getLog().debug("Generating AOT assets using command: " + command);
|
|
||||||
}
|
|
||||||
JavaProcessExecutor processExecutor = new JavaProcessExecutor(this.session, this.toolchainManager);
|
|
||||||
processExecutor.run(this.project.getBasedir(), command, Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] getAotArguments(String applicationClass) {
|
private String[] getAotArguments(String applicationClass) {
|
||||||
List<String> aotArguments = new ArrayList<>();
|
List<String> aotArguments = new ArrayList<>();
|
||||||
@ -179,75 +106,8 @@ public class ProcessAotMojo extends AbstractDependencyFilterMojo {
|
|||||||
return aotArguments.toArray(String[]::new);
|
return aotArguments.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
private URL[] getClassPathUrls() throws MojoExecutionException {
|
protected URL[] getClassPath() throws Exception {
|
||||||
List<URL> urls = new ArrayList<>();
|
return getClassPath(this.classesDirectory, new ExcludeTestScopeArtifactFilter());
|
||||||
urls.add(toURL(this.classesDirectory));
|
|
||||||
urls.addAll(getDependencyURLs(new TestScopeArtifactFilter()));
|
|
||||||
return urls.toArray(URL[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void compileSourceFiles() throws IOException, MojoExecutionException {
|
|
||||||
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 = new ArrayList<>();
|
|
||||||
options.add("-cp");
|
|
||||||
options.add(ClasspathBuilder.build(Arrays.asList(getClassPathUrls())));
|
|
||||||
options.add("-d");
|
|
||||||
options.add(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 copyAll(Path from, Path to) throws IOException {
|
|
||||||
List<Path> files = (Files.exists(from)) ? Files.walk(from).filter(Files::isRegularFile).toList()
|
|
||||||
: Collections.emptyList();
|
|
||||||
for (Path file : files) {
|
|
||||||
String relativeFileName = file.subpath(from.getNameCount(), file.getNameCount()).toString();
|
|
||||||
getLog().debug("Copying '" + relativeFileName + "' to " + to);
|
|
||||||
Path target = to.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* 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.net.URL;
|
||||||
|
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.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.maven.artifact.Artifact;
|
||||||
|
import org.apache.maven.artifact.DefaultArtifact;
|
||||||
|
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
|
||||||
|
import org.apache.maven.artifact.repository.ArtifactRepository;
|
||||||
|
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
|
||||||
|
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
|
||||||
|
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
|
||||||
|
import org.apache.maven.plugin.MojoExecutionException;
|
||||||
|
import org.apache.maven.plugins.annotations.Component;
|
||||||
|
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.apache.maven.repository.RepositorySystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the AOT engine on tests.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 3.0.0
|
||||||
|
*/
|
||||||
|
@Mojo(name = "process-test-aot", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, threadSafe = true,
|
||||||
|
requiresDependencyResolution = ResolutionScope.TEST, requiresDependencyCollection = ResolutionScope.TEST)
|
||||||
|
public class ProcessTestAotMojo extends AbstractAotMojo {
|
||||||
|
|
||||||
|
private static final String JUNIT_PLATFORM_GROUP_ID = "org.junit.platform";
|
||||||
|
|
||||||
|
private static final String JUNIT_PLATFORM_COMMONS_ARTIFACT_ID = "junit-platform-commons";
|
||||||
|
|
||||||
|
private static final String JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID = "junit-platform-launcher";
|
||||||
|
|
||||||
|
private static final String AOT_PROCESSOR_CLASS_NAME = "org.springframework.test.context.aot.TestAotProcessor";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory containing the classes and resource files that should be packaged into
|
||||||
|
* the archive.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
|
||||||
|
private File classesDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory containing the generated sources.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${project.build.directory}/spring-aot/test/sources", required = true)
|
||||||
|
private File generatedSources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory containing the generated resources.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${project.build.directory}/spring-aot/test/resources", required = true)
|
||||||
|
private File generatedResources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory containing the generated classes.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${project.build.directory}/spring-aot/test/classes", required = true)
|
||||||
|
private File generatedClasses;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local artifact repository used to resolve JUnit platform launcher jars.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${localRepository}", required = true, readonly = true)
|
||||||
|
private ArtifactRepository localRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove artifact repositories used to resolve JUnit platform launcher jars.
|
||||||
|
*/
|
||||||
|
@Parameter(defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true)
|
||||||
|
private List<ArtifactRepository> remoteRepositories;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
private RepositorySystem repositorySystem;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
private ResolutionErrorHandler resolutionErrorHandler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeAot() throws Exception {
|
||||||
|
if (Boolean.getBoolean("skipTests") || Boolean.getBoolean("maven.test.skip")) {
|
||||||
|
getLog().info("Skipping AOT test processing since tests are skipped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path testOutputDirectory = Paths.get(this.project.getBuild().getTestOutputDirectory());
|
||||||
|
if (Files.notExists(testOutputDirectory)) {
|
||||||
|
getLog().info("Skipping AOT test processing since no tests have been detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
generateAotAssets(getClassPath(true), AOT_PROCESSOR_CLASS_NAME, getAotArguments());
|
||||||
|
compileSourceFiles(getClassPath(false), this.generatedSources, this.classesDirectory);
|
||||||
|
copyAll(this.generatedResources.toPath().resolve("META-INF/native-image"),
|
||||||
|
this.classesDirectory.toPath().resolve("META-INF/native-image"));
|
||||||
|
copyAll(this.generatedClasses.toPath(), this.classesDirectory.toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getAotArguments() {
|
||||||
|
List<String> aotArguments = new ArrayList<>();
|
||||||
|
aotArguments.add(this.classesDirectory.toPath().toAbsolutePath().normalize().toString());
|
||||||
|
aotArguments.add(this.generatedSources.toString());
|
||||||
|
aotArguments.add(this.generatedResources.toString());
|
||||||
|
aotArguments.add(this.generatedClasses.toString());
|
||||||
|
aotArguments.add(this.project.getGroupId());
|
||||||
|
aotArguments.add(this.project.getArtifactId());
|
||||||
|
return aotArguments.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URL[] getClassPath(boolean includeJUnitPlatformLauncher) throws Exception {
|
||||||
|
URL[] classPath = getClassPath(this.classesDirectory);
|
||||||
|
if (!includeJUnitPlatformLauncher || this.project.getArtifactMap()
|
||||||
|
.containsKey(JUNIT_PLATFORM_GROUP_ID + ":" + JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID)) {
|
||||||
|
return classPath;
|
||||||
|
}
|
||||||
|
return addJUnitPlatformLauncher(classPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL[] addJUnitPlatformLauncher(URL[] classPath) throws Exception {
|
||||||
|
String version = getJUnitPlatformVersion();
|
||||||
|
DefaultArtifactHandler handler = new DefaultArtifactHandler("jar");
|
||||||
|
handler.setIncludesDependencies(true);
|
||||||
|
ArtifactResolutionResult resolutionResult = resolveArtifact(new DefaultArtifact(JUNIT_PLATFORM_GROUP_ID,
|
||||||
|
JUNIT_PLATFORM_LAUNCHER_ARTIFACT_ID, version, null, "jar", null, handler));
|
||||||
|
Set<URL> fullClassPath = new LinkedHashSet<>(Arrays.asList(classPath));
|
||||||
|
for (Artifact artifact : resolutionResult.getArtifacts()) {
|
||||||
|
fullClassPath.add(artifact.getFile().toURI().toURL());
|
||||||
|
}
|
||||||
|
return fullClassPath.toArray(URL[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJUnitPlatformVersion() throws MojoExecutionException {
|
||||||
|
String id = JUNIT_PLATFORM_GROUP_ID + ":" + JUNIT_PLATFORM_COMMONS_ARTIFACT_ID;
|
||||||
|
Artifact platformCommonsArtifact = this.project.getArtifactMap().get(id);
|
||||||
|
String version = (platformCommonsArtifact != null) ? platformCommonsArtifact.getBaseVersion() : null;
|
||||||
|
if (version == null) {
|
||||||
|
throw new MojoExecutionException(
|
||||||
|
"Unable to find '%s' dependnecy. Please ensure JUnit is correctly configured.".formatted(id));
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArtifactResolutionResult resolveArtifact(Artifact artifact) throws Exception {
|
||||||
|
ArtifactResolutionRequest request = new ArtifactResolutionRequest();
|
||||||
|
request.setArtifact(artifact);
|
||||||
|
request.setLocalRepository(this.localRepository);
|
||||||
|
request.setResolveTransitively(true);
|
||||||
|
request.setCollectionFilter(new RuntimeArtifactFilter());
|
||||||
|
request.setRemoteRepositories(this.remoteRepositories);
|
||||||
|
ArtifactResolutionResult result = this.repositorySystem.resolve(request);
|
||||||
|
this.resolutionErrorHandler.throwErrors(request, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user