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
pull/32419/head
Scott Frederick 2 years ago
parent 2b3ef96156
commit 1ed4d89466

@ -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>
}
}
Loading…
Cancel
Save