Copy native reachability metadata to jar file in Gradle plugin
When the Spring Boot Gradle plugin builds a fat jar and the Native Build Tools Gradle plugin is applied to the build, any configuration files from the GraalVM reachability metadata repository that match project dependencies are copied to a `META-INF/native-image` directory in the fat jar. Closes gh-32408
This commit is contained in:
@@ -16,24 +16,32 @@
|
||||
|
||||
package org.springframework.boot.gradle.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.graalvm.buildtools.gradle.NativeImagePlugin;
|
||||
import org.graalvm.buildtools.gradle.dsl.GraalVMExtension;
|
||||
import org.graalvm.buildtools.gradle.dsl.GraalVMReachabilityMetadataRepositoryExtension;
|
||||
import org.graalvm.buildtools.gradle.dsl.NativeImageOptions;
|
||||
import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask;
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.FileCopyDetails;
|
||||
import org.gradle.api.plugins.ExtensionAware;
|
||||
import org.gradle.api.plugins.JavaPlugin;
|
||||
import org.gradle.api.plugins.JavaPluginExtension;
|
||||
import org.gradle.api.tasks.SourceSet;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
|
||||
import org.springframework.boot.gradle.tasks.bundling.BootJar;
|
||||
|
||||
/**
|
||||
* {@link Action} that is executed in response to the {@link NativeImagePlugin} being
|
||||
* applied.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
class NativeImagePluginAction implements PluginApplicationAction {
|
||||
|
||||
@@ -49,23 +57,53 @@ class NativeImagePluginAction implements PluginApplicationAction {
|
||||
project.getPlugins().withType(JavaPlugin.class).all((plugin) -> {
|
||||
JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class);
|
||||
SourceSetContainer sourceSets = javaPluginExtension.getSourceSets();
|
||||
SourceSet aotSourceSet = sourceSets.getByName(SpringBootAotPlugin.AOT_SOURCE_SET_NAME);
|
||||
project.getTasks().named(NativeImagePlugin.NATIVE_COMPILE_TASK_NAME, BuildNativeImageTask.class,
|
||||
(nativeCompile) -> nativeCompile.getOptions().get().classpath(aotSourceSet.getOutput()));
|
||||
SourceSet aotTestSourceSet = sourceSets.getByName(SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME);
|
||||
project.getTasks().named("nativeTestCompile", BuildNativeImageTask.class,
|
||||
(nativeTestCompile) -> nativeTestCompile.getOptions().get()
|
||||
.classpath(aotTestSourceSet.getOutput()));
|
||||
configureTaskClasspath(project, NativeImagePlugin.NATIVE_COMPILE_TASK_NAME,
|
||||
sourceSets.getByName(SpringBootAotPlugin.AOT_SOURCE_SET_NAME));
|
||||
configureTaskClasspath(project, NativeImagePlugin.NATIVE_TEST_COMPILE_TASK_NAME,
|
||||
sourceSets.getByName(SpringBootAotPlugin.AOT_TEST_SOURCE_SET_NAME));
|
||||
GraalVMExtension graalVmExtension = configureGraalVmExtension(project);
|
||||
configureGraalVmReachabilityExtension(graalVmExtension);
|
||||
copyReachabilityMetadataToBootJar(project, graalVmExtension);
|
||||
});
|
||||
GraalVMExtension graalVmExtension = project.getExtensions().getByType(GraalVMExtension.class);
|
||||
graalVmExtension.getToolchainDetection().set(false);
|
||||
reachabilityExtensionOn(graalVmExtension).getEnabled().set(true);
|
||||
}
|
||||
|
||||
private static GraalVMReachabilityMetadataRepositoryExtension reachabilityExtensionOn(
|
||||
GraalVMExtension graalVmExtension) {
|
||||
return ((ExtensionAware) graalVmExtension).getExtensions()
|
||||
private void configureTaskClasspath(Project project, String taskName, SourceSet sourceSet) {
|
||||
project.getTasks().named(taskName, BuildNativeImageTask.class,
|
||||
(nativeCompile) -> nativeCompile.getOptions().get().classpath(sourceSet.getOutput()));
|
||||
}
|
||||
|
||||
private GraalVMExtension configureGraalVmExtension(Project project) {
|
||||
GraalVMExtension extension = project.getExtensions().getByType(GraalVMExtension.class);
|
||||
extension.getToolchainDetection().set(false);
|
||||
return extension;
|
||||
}
|
||||
|
||||
private void configureGraalVmReachabilityExtension(GraalVMExtension graalVmExtension) {
|
||||
GraalVMReachabilityMetadataRepositoryExtension extension = ((ExtensionAware) graalVmExtension).getExtensions()
|
||||
.getByType(GraalVMReachabilityMetadataRepositoryExtension.class);
|
||||
extension.getEnabled().set(true);
|
||||
}
|
||||
|
||||
private void copyReachabilityMetadataToBootJar(Project project, GraalVMExtension graalVmExtension) {
|
||||
Path repositoryCacheDir = new File(project.getGradle().getGradleUserHomeDir(),
|
||||
"native-build-tools/repositories").toPath();
|
||||
project.getTasks().named(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class).configure((bootJar) -> {
|
||||
NativeImageOptions options = graalVmExtension.getBinaries().named(NativeImagePlugin.NATIVE_MAIN_EXTENSION)
|
||||
.get();
|
||||
bootJar.from(options.getConfigurationFileDirectories())
|
||||
.eachFile((file) -> normalizePathIfNecessary(repositoryCacheDir, file));
|
||||
});
|
||||
}
|
||||
|
||||
private void normalizePathIfNecessary(Path repositoryCacheDir, FileCopyDetails configurationFile) {
|
||||
Path configurationFilePath = configurationFile.getFile().toPath();
|
||||
if (configurationFilePath.startsWith(repositoryCacheDir)) {
|
||||
Path versionDir = configurationFilePath.getParent();
|
||||
Path artifactDir = versionDir.getParent();
|
||||
Path groupDir = artifactDir.getParent();
|
||||
Path gavParentDir = groupDir.getParent();
|
||||
configurationFile.setPath("/META-INF/native-image/" + gavParentDir.relativize(configurationFilePath));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
|
||||
package org.springframework.boot.gradle.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.junit.jupiter.api.TestTemplate;
|
||||
|
||||
import org.springframework.boot.gradle.junit.GradleCompatibility;
|
||||
@@ -27,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* Integration tests for {@link NativeImagePluginAction}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@GradleCompatibility(configurationCache = true)
|
||||
class NativeImagePluginActionIntegrationTests {
|
||||
@@ -39,4 +52,52 @@ class NativeImagePluginActionIntegrationTests {
|
||||
.contains("org.springframework.boot.aot applied = true");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void reachabilityMetadataConfigurationFilesAreCopiedToJar() throws IOException {
|
||||
writeDummyAotProcessorMainClass();
|
||||
BuildResult result = this.gradleBuild.build("bootJar");
|
||||
assertThat(result.task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
File buildLibs = new File(this.gradleBuild.getProjectDir(), "build/libs");
|
||||
File jarFile = new File(buildLibs, this.gradleBuild.getProjectDir().getName() + ".jar");
|
||||
assertThat(buildLibs.listFiles()).contains(jarFile);
|
||||
assertThat(getEntryNames(jarFile)).contains(
|
||||
"META-INF/native-image/ch.qos.logback/logback-classic/1.2.11/reflect-config.json",
|
||||
"META-INF/native-image/org.jline/jline/3.21.0/jni-config.json",
|
||||
"META-INF/native-image/org.jline/jline/3.21.0/proxy-config.json",
|
||||
"META-INF/native-image/org.jline/jline/3.21.0/reflect-config.json",
|
||||
"META-INF/native-image/org.jline/jline/3.21.0/resource-config.json");
|
||||
}
|
||||
|
||||
private void writeDummyAotProcessorMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/org/springframework/boot");
|
||||
examplePackage.mkdirs();
|
||||
File main = new File(examplePackage, "AotProcessor.java");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||
writer.println("package org.springframework.boot;");
|
||||
writer.println();
|
||||
writer.println("import java.io.IOException;");
|
||||
writer.println();
|
||||
writer.println("public class AotProcessor {");
|
||||
writer.println();
|
||||
writer.println(" public static void main(String[] args) {");
|
||||
writer.println(" }");
|
||||
writer.println();
|
||||
writer.println("}");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> getEntryNames(File file) throws IOException {
|
||||
List<String> entryNames = new ArrayList<>();
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
entryNames.add(entries.nextElement().getName());
|
||||
}
|
||||
}
|
||||
return entryNames;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot'
|
||||
id 'org.springframework.boot.aot'
|
||||
}
|
||||
|
||||
apply plugin: 'org.graalvm.buildtools.native'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "ch.qos.logback:logback-classic:1.2.11"
|
||||
implementation "org.jline:jline:3.21.0"
|
||||
}
|
||||
|
||||
// see https://github.com/graalvm/native-build-tools/issues/302
|
||||
graalvmNative {
|
||||
agent {
|
||||
tasksToInstrumentPredicate = { t -> false } as java.util.function.Predicate<Test>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user