From c6d10387fdf6b76a0811efde884173f3c36137d9 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Tue, 21 Oct 2025 14:07:07 +0200 Subject: [PATCH 01/22] Add generateDynamicAccessMetadata gradle task to the native-gradle-plugin --- .../internal/FileSystemRepository.java | 4 + native-gradle-plugin/build.gradle | 1 + .../buildtools/gradle/NativeImagePlugin.java | 26 ++++ .../GraalVMReachabilityMetadataService.java | 7 + .../tasks/GenerateDynamicAccessMetadata.java | 135 ++++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java index e1d645a75..1fc00bba9 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java @@ -133,6 +133,10 @@ public Set findConfigurationsFor(Consumer .collect(Collectors.toSet()); } + public Path getRootDirectory() { + return rootDirectory; + } + /** * Allows getting insights about how configuration is picked. */ diff --git a/native-gradle-plugin/build.gradle b/native-gradle-plugin/build.gradle index d5ce50d35..a0aafdd8f 100644 --- a/native-gradle-plugin/build.gradle +++ b/native-gradle-plugin/build.gradle @@ -56,6 +56,7 @@ maven { } dependencies { + implementation libs.openjson implementation libs.utils implementation libs.jvmReachabilityMetadata testImplementation libs.test.spock diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 6d5a79011..bdf8567ed 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -58,6 +58,7 @@ import org.graalvm.buildtools.gradle.internal.agent.AgentConfigurationFactory; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata; +import org.graalvm.buildtools.gradle.tasks.GenerateDynamicAccessMetadata; import org.graalvm.buildtools.gradle.tasks.GenerateResourcesConfigFile; import org.graalvm.buildtools.gradle.tasks.MetadataCopyTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; @@ -389,6 +390,19 @@ private void configureAutomaticTaskCreation(Project project, options.getConfigurationFileDirectories().from(generateResourcesConfig.map(serializableTransformerOf(t -> t.getOutputFile().map(serializableTransformerOf(f -> f.getAsFile().getParentFile())) ))); + TaskProvider generateDynamicAccessMetadata = registerDynamicAccessMetadataTask( + project.getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME), + graalVMReachabilityMetadataService(project, reachabilityExtensionOn(graalExtension)), + project.getLayout().getBuildDirectory(), + tasks, + deriveTaskName(binaryName, "generate", "DynamicAccessMetadata")); + options.getBuildArgs().addAll( + generateDynamicAccessMetadata.flatMap(task -> + task.getOutputJson().map(file -> + List.of("-H:DynamicAccessMetadata=" + file.getAsFile().getAbsolutePath()) + ) + ) + ); configureJvmReachabilityConfigurationDirectories(project, graalExtension, options, sourceSet); configureJvmReachabilityExcludeConfigArgs(project, graalExtension, options, sourceSet); }); @@ -656,6 +670,18 @@ private TaskProvider registerResourcesConfigTask(Pr }); } + private TaskProvider registerDynamicAccessMetadataTask(Configuration classpathConfiguration, + Provider metadataService, + DirectoryProperty buildDir, + TaskContainer tasks, + String name) { + return tasks.register(name, GenerateDynamicAccessMetadata.class, task -> { + task.getRuntimeClasspath().set(classpathConfiguration); + task.getMetadataService().set(metadataService); + task.getOutputJson().set(buildDir.dir("generated").map(dir -> dir.file("dynamic-access-metadata.json"))); + }); + } + public void registerTestBinary(Project project, DefaultGraalVmExtension graalExtension, DefaultTestBinaryConfig config) { diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java index fd0537ef2..4e278ac73 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java @@ -235,4 +235,11 @@ public Set findConfigurationsFor(Set excludedMod query.useLatestConfigWhenVersionIsUntested(); }); } + + public Path getRepositoryDirectory() { + if (repository instanceof FileSystemRepository fsRepo) { + return fsRepo.getRootDirectory(); + } + return null; + } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java new file mode 100644 index 000000000..11e2677ae --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -0,0 +1,135 @@ +package org.graalvm.buildtools.gradle.tasks; + +import com.github.openjson.JSONArray; +import com.github.openjson.JSONObject; +import org.graalvm.buildtools.gradle.internal.GraalVMLogger; +import org.graalvm.buildtools.gradle.internal.GraalVMReachabilityMetadataService; +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image + * Build Report. This json file contains the mapping of all classpath entries that exist in the + * {@code library-and-framework-list.json} to their transitive dependencies. + *
+ * If {@code library-and-framework-list.json} doesn't exist in the used release of the + * {@code Graalvm Reachability Metadata} repository, this task does nothing. + */ +public abstract class GenerateDynamicAccessMetadata extends DefaultTask { + private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; + + @Internal + public abstract Property getRuntimeClasspath(); + + @Internal + public abstract Property getMetadataService(); + + @OutputFile + public abstract RegularFileProperty getOutputJson(); + + @TaskAction + public void generate() { + File jsonFile = getMetadataService().get().getRepositoryDirectory().resolve(LIBRARY_AND_FRAMEWORK_LIST).toFile(); + if (!jsonFile.exists()) { + GraalVMLogger.of(getLogger()).log("{} is not packaged with the provided reachability metadata repository.", LIBRARY_AND_FRAMEWORK_LIST); + return; + } + + try { + Set artifactsToInclude = readArtifacts(jsonFile); + + Configuration runtimeClasspathConfig = getRuntimeClasspath().get(); + Set classpathJars = runtimeClasspathConfig.getFiles(); + + Map> exportMap = buildExportMap( + runtimeClasspathConfig.getResolvedConfiguration().getFirstLevelModuleDependencies(), + artifactsToInclude, + classpathJars + ); + + writeMapToJson(getOutputJson().getAsFile().get(), exportMap); + } catch (Exception e) { + throw new RuntimeException("Failed to generate dynamic access metadata", e); + } + } + + private Set readArtifacts(File inputFile) throws Exception { + Set artifacts = new HashSet<>(); + String content = Files.readString(inputFile.toPath()); + JSONArray jsonArray = new JSONArray(content); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject entry = jsonArray.getJSONObject(i); + if (entry.has("artifact")) { + artifacts.add(entry.getString("artifact")); + } + } + return artifacts; + } + + private Map> buildExportMap(Set dependencies, + Set artifactsToInclude, + Set classpathJars) { + Map> exportMap = new HashMap<>(); + for (ResolvedDependency dep : dependencies) { + String depKey = dep.getModuleGroup() + ":" + dep.getModuleName(); + if (!artifactsToInclude.contains(depKey)) { + continue; + } + + for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { + File file = artifact.getFile(); + if (classpathJars.contains(file)) { + Set files = new HashSet<>(); + collectArtifacts(dep, files, classpathJars); + exportMap.put(file.getAbsolutePath(), files); + } + } + } + return exportMap; + } + + private void collectArtifacts(ResolvedDependency dep, Set collector, Set classpathJars) { + for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { + File file = artifact.getFile(); + if (classpathJars.contains(file)) { + collector.add(file.getAbsolutePath()); + } + } + + for (ResolvedDependency child : dep.getChildren()) { + collectArtifacts(child, collector, classpathJars); + } + } + + public static void writeMapToJson(File outputFile, Map> exportMap) { + try { + JSONObject root = new JSONObject(); + for (Map.Entry> entry : exportMap.entrySet()) { + JSONArray array = new JSONArray(); + entry.getValue().forEach(array::put); + root.put(entry.getKey(), array); + } + + try (FileWriter writer = new FileWriter(outputFile)) { + writer.write(root.toString(2)); + } + } catch (Exception e) { + throw new RuntimeException("Failed to write export map to JSON", e); + } + } +} \ No newline at end of file From 17d00f7e01f2a8d32bc5dc58f387c1aa5fe41d52 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 23 Oct 2025 16:28:33 +0200 Subject: [PATCH 02/22] Implement dynamic access generator on the native-maven-plugin --- .../tasks/GenerateDynamicAccessMetadata.java | 47 +++- .../maven/AbstractNativeImageMojo.java | 6 +- .../NativeBuildDynamicAccessMetadataMojo.java | 207 ++++++++++++++++++ .../maven/NativeCompileNoForkMojo.java | 1 + 4 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 11e2677ae..af9fe657a 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -1,3 +1,43 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package org.graalvm.buildtools.gradle.tasks; import com.github.openjson.JSONArray; @@ -16,6 +56,7 @@ import java.io.File; import java.io.FileWriter; +import java.io.IOException; import java.nio.file.Files; import java.util.HashMap; import java.util.HashSet; @@ -28,7 +69,7 @@ * {@code library-and-framework-list.json} to their transitive dependencies. *
* If {@code library-and-framework-list.json} doesn't exist in the used release of the - * {@code Graalvm Reachability Metadata} repository, this task does nothing. + * {@code GraalVM Reachability Metadata} repository, this task does nothing. */ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; @@ -63,12 +104,12 @@ public void generate() { ); writeMapToJson(getOutputJson().getAsFile().get(), exportMap); - } catch (Exception e) { + } catch (IOException e) { throw new RuntimeException("Failed to generate dynamic access metadata", e); } } - private Set readArtifacts(File inputFile) throws Exception { + private Set readArtifacts(File inputFile) throws IOException { Set artifacts = new HashSet<>(); String content = Files.readString(inputFile.toPath()); JSONArray jsonArray = new JSONArray(content); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java index ae27dd6a3..b9dabf35c 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -543,7 +543,11 @@ protected void maybeAddGeneratedResourcesConfig(List into) { } } - + protected void maybeAddDynamicAccessMetadata(List into) { + if (into.contains("--emit build-report") && Files.exists(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json"))) { + into.add("-H:DynamicAccessMetadata=" + outputDirectory + "/" + "dynamic-access-metadata.json"); + } + } protected void maybeAddReachabilityMetadata(List configDirs) { if (isMetadataRepositoryEnabled() && !metadataRepositoryConfigurations.isEmpty()) { diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java new file mode 100644 index 000000000..274d43a7e --- /dev/null +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.graalvm.buildtools.maven; + +import com.github.openjson.JSONArray; +import com.github.openjson.JSONObject; +import org.apache.maven.artifact.Artifact; +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.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; +import org.graalvm.reachability.internal.FileSystemRepository; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image + * Build Report. This json file contains the mapping of all classpath entries that exist in the + * {@code library-and-framework-list.json} to their transitive dependencies. + *
+ * If {@code library-and-framework-list.json} doesn't exist in the used release of the + * {@code GraalVM Reachability Metadata} repository, this task does nothing. + */ +@Mojo(name = "generateDynamicAccessMetadata", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) +public class NativeBuildDynamicAccessMetadataMojo extends AbstractNativeMojo { + private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; + + @Component + private RepositorySystem repoSystem; + + @Component + private DependencyGraphBuilder dependencyGraphBuilder; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true) + private List remoteRepos; + + @Parameter(defaultValue = "${project.build.directory}/dynamic-access-metadata.json", required = true) + private File outputJson; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + configureMetadataRepository(); + File jsonFile = ((FileSystemRepository) metadataRepository).getRootDirectory().resolve(LIBRARY_AND_FRAMEWORK_LIST).toFile(); + + if (!jsonFile.exists()) { + getLog().warn(LIBRARY_AND_FRAMEWORK_LIST + " is not packaged with the provided reachability metadata repository."); + return; + } + + try { + Set artifactsToInclude = readArtifacts(jsonFile); + + Map coordinateToPath = new HashMap<>(); + for (Artifact a : project.getArtifacts()) { + if (a.getFile() != null) { + String coords = a.getGroupId() + ":" + a.getArtifactId(); + coordinateToPath.put(coords, a.getFile().getAbsolutePath()); + } + } + + Map> exportMap = new HashMap<>(); + + for (Artifact artifact : project.getArtifacts()) { + String key = artifact.getGroupId() + ":" + artifact.getArtifactId(); + if (!artifactsToInclude.contains(key)) { + continue; + } + + if (artifact.getFile() == null) { + getLog().warn("Skipping artifact with null file: " + key); + continue; + } + + Set transitiveDeps = getTransitiveDependenciesForArtifact( + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(), + coordinateToPath + ); + + exportMap.put(artifact.getFile().getAbsolutePath(), transitiveDeps); + } + + writeMapToJson(outputJson, exportMap); + } catch (IOException e) { + throw new RuntimeException("Failed to generate dynamic access metadata", e); + } catch (DependencyCollectionException e) { + throw new RuntimeException(e); + } + } + + private Set readArtifacts(File inputFile) throws IOException { + Set artifacts = new HashSet<>(); + String content = Files.readString(inputFile.toPath()); + JSONArray jsonArray = new JSONArray(content); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject entry = jsonArray.getJSONObject(i); + if (entry.has("artifact")) { + artifacts.add(entry.getString("artifact")); + } + } + return artifacts; + } + + private Set getTransitiveDependenciesForArtifact(String coordinates, Map coordinateToPath) throws DependencyCollectionException { + org.eclipse.aether.artifact.Artifact artifact = new DefaultArtifact(coordinates); + + CollectRequest collectRequest = new CollectRequest(); + collectRequest.setRoot(new Dependency(artifact, "")); + collectRequest.setRepositories(remoteRepos); + + DependencyNode node = repoSystem.collectDependencies(repoSession, collectRequest).getRoot(); + + PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); + node.accept(nlg); + + Set deps = new HashSet<>(); + nlg.getNodes().forEach(n -> { + if (n.getDependency() != null) { + org.eclipse.aether.artifact.Artifact a = n.getDependency().getArtifact(); + String dependencyPath = coordinateToPath.get(a.getGroupId() + ":" + a.getArtifactId()); + if (dependencyPath != null) { + deps.add(dependencyPath); + } + } + }); + + return deps; + } + + public static void writeMapToJson(File outputFile, Map> exportMap) { + try { + JSONObject root = new JSONObject(); + for (Map.Entry> entry : exportMap.entrySet()) { + JSONArray array = new JSONArray(); + entry.getValue().forEach(array::put); + root.put(entry.getKey(), array); + } + + try (FileWriter writer = new FileWriter(outputFile)) { + writer.write(root.toString(2)); + } + } catch (Exception e) { + throw new RuntimeException("Failed to write export map to JSON", e); + } + } +} \ No newline at end of file diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java index 4ad126b3d..f40f75ad0 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java @@ -110,6 +110,7 @@ public void execute() throws MojoExecutionException { maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-assembly-plugin", "archive", "manifest", "mainClass"); maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass"); maybeAddGeneratedResourcesConfig(buildArgs); + maybeAddDynamicAccessMetadata(buildArgs); generateBaseSBOMIfNeeded(); From 7b37e440b3497d8fa37c306cbc483b203d01b971 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 24 Oct 2025 11:31:56 +0200 Subject: [PATCH 03/22] Touch-up maven plugin --- .../maven/AbstractNativeImageMojo.java | 2 +- .../NativeBuildDynamicAccessMetadataMojo.java | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java index b9dabf35c..114a4738f 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -545,7 +545,7 @@ protected void maybeAddGeneratedResourcesConfig(List into) { protected void maybeAddDynamicAccessMetadata(List into) { if (into.contains("--emit build-report") && Files.exists(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json"))) { - into.add("-H:DynamicAccessMetadata=" + outputDirectory + "/" + "dynamic-access-metadata.json"); + into.add("-H:DynamicAccessMetadata=" + outputDirectory + File.separator + "dynamic-access-metadata.json"); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 274d43a7e..13730296a 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -87,9 +87,6 @@ public class NativeBuildDynamicAccessMetadataMojo extends AbstractNativeMojo { @Component private RepositorySystem repoSystem; - @Component - private DependencyGraphBuilder dependencyGraphBuilder; - @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) private RepositorySystemSession repoSession; @@ -133,19 +130,19 @@ public void execute() throws MojoExecutionException, MojoFailureException { continue; } - Set transitiveDeps = getTransitiveDependenciesForArtifact( + Set transitiveDependencies = getTransitiveDependenciesForArtifact( artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(), coordinateToPath ); - exportMap.put(artifact.getFile().getAbsolutePath(), transitiveDeps); + exportMap.put(artifact.getFile().getAbsolutePath(), transitiveDependencies); } writeMapToJson(outputJson, exportMap); } catch (IOException e) { - throw new RuntimeException("Failed to generate dynamic access metadata", e); + throw new RuntimeException("Failed generating dynamic access metadata", e); } catch (DependencyCollectionException e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed collecting dependencies", e); } } @@ -174,18 +171,18 @@ private Set getTransitiveDependenciesForArtifact(String coordinates, Map PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); node.accept(nlg); - Set deps = new HashSet<>(); + Set dependencies = new HashSet<>(); nlg.getNodes().forEach(n -> { if (n.getDependency() != null) { org.eclipse.aether.artifact.Artifact a = n.getDependency().getArtifact(); String dependencyPath = coordinateToPath.get(a.getGroupId() + ":" + a.getArtifactId()); if (dependencyPath != null) { - deps.add(dependencyPath); + dependencies.add(dependencyPath); } } }); - return deps; + return dependencies; } public static void writeMapToJson(File outputFile, Map> exportMap) { From f17e3277286f5df9ade24e8ec76d1b141b0b6824 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 24 Oct 2025 12:19:55 +0200 Subject: [PATCH 04/22] Relax exception catches and be more specific with exceptions --- .../gradle/tasks/GenerateDynamicAccessMetadata.java | 6 +++--- .../maven/NativeBuildDynamicAccessMetadataMojo.java | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index af9fe657a..db9ae34ec 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -105,7 +105,7 @@ public void generate() { writeMapToJson(getOutputJson().getAsFile().get(), exportMap); } catch (IOException e) { - throw new RuntimeException("Failed to generate dynamic access metadata", e); + GraalVMLogger.of(getLogger()).log("Failed to generate dynamic access metadata: {}", e); } } @@ -157,7 +157,7 @@ private void collectArtifacts(ResolvedDependency dep, Set collector, Set } } - public static void writeMapToJson(File outputFile, Map> exportMap) { + public void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); for (Map.Entry> entry : exportMap.entrySet()) { @@ -170,7 +170,7 @@ public static void writeMapToJson(File outputFile, Map> expo writer.write(root.toString(2)); } } catch (Exception e) { - throw new RuntimeException("Failed to write export map to JSON", e); + GraalVMLogger.of(getLogger()).log("Failed to write export map to JSON: {}", e); } } } \ No newline at end of file diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 13730296a..0c4f2fe9b 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -50,7 +50,6 @@ 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.shared.dependency.graph.DependencyGraphBuilder; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; @@ -140,9 +139,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { writeMapToJson(outputJson, exportMap); } catch (IOException e) { - throw new RuntimeException("Failed generating dynamic access metadata", e); + getLog().warn("Failed generating dynamic access metadata: " + e); } catch (DependencyCollectionException e) { - throw new RuntimeException("Failed collecting dependencies", e); + getLog().warn("Failed collecting dependencies: " + e); } } @@ -185,7 +184,7 @@ private Set getTransitiveDependenciesForArtifact(String coordinates, Map return dependencies; } - public static void writeMapToJson(File outputFile, Map> exportMap) { + public void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); for (Map.Entry> entry : exportMap.entrySet()) { @@ -197,8 +196,8 @@ public static void writeMapToJson(File outputFile, Map> expo try (FileWriter writer = new FileWriter(outputFile)) { writer.write(root.toString(2)); } - } catch (Exception e) { - throw new RuntimeException("Failed to write export map to JSON", e); + } catch (IOException e) { + getLog().warn("Failed to write export map to JSON: " + e); } } } \ No newline at end of file From 1f4148e1ab6d24315db5c285be70162aaf8ce1a4 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 24 Oct 2025 14:07:05 +0200 Subject: [PATCH 05/22] Make GenerateDynamicAccessMetadata contingent on the presence of "--emit build-report" --- .../buildtools/gradle/NativeImagePlugin.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index bdf8567ed..11813f294 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -396,13 +396,17 @@ private void configureAutomaticTaskCreation(Project project, project.getLayout().getBuildDirectory(), tasks, deriveTaskName(binaryName, "generate", "DynamicAccessMetadata")); - options.getBuildArgs().addAll( - generateDynamicAccessMetadata.flatMap(task -> - task.getOutputJson().map(file -> - List.of("-H:DynamicAccessMetadata=" + file.getAsFile().getAbsolutePath()) - ) - ) - ); + imageBuilder.configure(buildImageTask -> { + if (buildImageTask.getOptions().get().getBuildArgs().get().contains("--emit build-report")) { + options.getBuildArgs().addAll( + generateDynamicAccessMetadata.flatMap(generateDynamicAccessMetadataTask -> + generateDynamicAccessMetadataTask.getOutputJson().map(file -> + List.of("-H:DynamicAccessMetadata=" + file.getAsFile().getAbsolutePath()) + ) + ) + ); + } + }); configureJvmReachabilityConfigurationDirectories(project, graalExtension, options, sourceSet); configureJvmReachabilityExcludeConfigArgs(project, graalExtension, options, sourceSet); }); From ea322e50b0abd0bbfd40e2d4a49b06c8805fdc9a Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 24 Oct 2025 15:30:59 +0200 Subject: [PATCH 06/22] Cleanup both plugins and make them more consistent with eachother --- .../tasks/GenerateDynamicAccessMetadata.java | 18 +++--- .../NativeBuildDynamicAccessMetadataMojo.java | 57 +++++++++---------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index db9ae34ec..90a8bea1a 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -122,21 +122,19 @@ private Set readArtifacts(File inputFile) throws IOException { return artifacts; } - private Map> buildExportMap(Set dependencies, - Set artifactsToInclude, - Set classpathJars) { + private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathJars) { Map> exportMap = new HashMap<>(); - for (ResolvedDependency dep : dependencies) { - String depKey = dep.getModuleGroup() + ":" + dep.getModuleName(); - if (!artifactsToInclude.contains(depKey)) { + for (ResolvedDependency dependency : dependencies) { + String dependencyCoordinates = dependency.getModuleGroup() + ":" + dependency.getModuleName(); + if (!artifactsToInclude.contains(dependencyCoordinates)) { continue; } - for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { + for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) { File file = artifact.getFile(); if (classpathJars.contains(file)) { Set files = new HashSet<>(); - collectArtifacts(dep, files, classpathJars); + collectDependencies(dependency, files, classpathJars); exportMap.put(file.getAbsolutePath(), files); } } @@ -144,7 +142,7 @@ private Map> buildExportMap(Set dependen return exportMap; } - private void collectArtifacts(ResolvedDependency dep, Set collector, Set classpathJars) { + private void collectDependencies(ResolvedDependency dep, Set collector, Set classpathJars) { for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { File file = artifact.getFile(); if (classpathJars.contains(file)) { @@ -153,7 +151,7 @@ private void collectArtifacts(ResolvedDependency dep, Set collector, Set } for (ResolvedDependency child : dep.getChildren()) { - collectArtifacts(child, collector, classpathJars); + collectDependencies(child, collector, classpathJars); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 0c4f2fe9b..bec202497 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -108,34 +108,15 @@ public void execute() throws MojoExecutionException, MojoFailureException { try { Set artifactsToInclude = readArtifacts(jsonFile); - Map coordinateToPath = new HashMap<>(); + Map coordinatesToPath = new HashMap<>(); for (Artifact a : project.getArtifacts()) { if (a.getFile() != null) { - String coords = a.getGroupId() + ":" + a.getArtifactId(); - coordinateToPath.put(coords, a.getFile().getAbsolutePath()); + String coordinates = a.getGroupId() + ":" + a.getArtifactId(); + coordinatesToPath.put(coordinates, a.getFile().getAbsolutePath()); } } - Map> exportMap = new HashMap<>(); - - for (Artifact artifact : project.getArtifacts()) { - String key = artifact.getGroupId() + ":" + artifact.getArtifactId(); - if (!artifactsToInclude.contains(key)) { - continue; - } - - if (artifact.getFile() == null) { - getLog().warn("Skipping artifact with null file: " + key); - continue; - } - - Set transitiveDependencies = getTransitiveDependenciesForArtifact( - artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(), - coordinateToPath - ); - - exportMap.put(artifact.getFile().getAbsolutePath(), transitiveDependencies); - } + Map> exportMap = buildExportMap(artifactsToInclude, coordinatesToPath); writeMapToJson(outputJson, exportMap); } catch (IOException e) { @@ -158,8 +139,26 @@ private Set readArtifacts(File inputFile) throws IOException { return artifacts; } - private Set getTransitiveDependenciesForArtifact(String coordinates, Map coordinateToPath) throws DependencyCollectionException { - org.eclipse.aether.artifact.Artifact artifact = new DefaultArtifact(coordinates); + private Map> buildExportMap(Set artifactsToInclude, Map coordinatesToPath) throws DependencyCollectionException { + Map> exportMap = new HashMap<>(); + + for (Artifact artifact : project.getArtifacts()) { + String coordinates = artifact.getGroupId() + ":" + artifact.getArtifactId(); + if (!artifactsToInclude.contains(coordinates) || artifact.getFile() == null) { + continue; + } + + Set transitiveDependencies = collectDependencies( + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(), + coordinatesToPath); + + exportMap.put(artifact.getFile().getAbsolutePath(), transitiveDependencies); + } + return exportMap; + } + + private Set collectDependencies(String coordinates, Map coordinatesToPath) throws DependencyCollectionException { + DefaultArtifact artifact = new DefaultArtifact(coordinates); CollectRequest collectRequest = new CollectRequest(); collectRequest.setRoot(new Dependency(artifact, "")); @@ -171,10 +170,10 @@ private Set getTransitiveDependenciesForArtifact(String coordinates, Map node.accept(nlg); Set dependencies = new HashSet<>(); - nlg.getNodes().forEach(n -> { - if (n.getDependency() != null) { - org.eclipse.aether.artifact.Artifact a = n.getDependency().getArtifact(); - String dependencyPath = coordinateToPath.get(a.getGroupId() + ":" + a.getArtifactId()); + nlg.getNodes().forEach(dependencyNode -> { + if (dependencyNode.getDependency() != null) { + DefaultArtifact dependencyArtifact = (DefaultArtifact) dependencyNode.getDependency().getArtifact(); + String dependencyPath = coordinatesToPath.get(dependencyArtifact.getGroupId() + ":" + dependencyArtifact.getArtifactId()); if (dependencyPath != null) { dependencies.add(dependencyPath); } From 0c739316b42e2f02b0646ca36a28953891d745a4 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 27 Oct 2025 10:01:46 +0100 Subject: [PATCH 07/22] Hotfix private method being public --- .../buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java | 2 +- .../buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 90a8bea1a..2ccc1c842 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -155,7 +155,7 @@ private void collectDependencies(ResolvedDependency dep, Set collector, } } - public void writeMapToJson(File outputFile, Map> exportMap) { + private void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); for (Map.Entry> entry : exportMap.entrySet()) { diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index bec202497..d4bafa4d3 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -183,7 +183,7 @@ private Set collectDependencies(String coordinates, Map return dependencies; } - public void writeMapToJson(File outputFile, Map> exportMap) { + private void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); for (Map.Entry> entry : exportMap.entrySet()) { From f033acfbf59bff6c58cea5ffd5126aec062cca83 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 27 Oct 2025 11:01:08 +0100 Subject: [PATCH 08/22] Make the generateDynamicAccessMetadata mojo execute when executing compile-no-fork while emitting a Build Report --- .../maven/AbstractNativeImageMojo.java | 2 +- .../maven/NativeCompileNoForkMojo.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java index 114a4738f..655f8b145 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -544,7 +544,7 @@ protected void maybeAddGeneratedResourcesConfig(List into) { } protected void maybeAddDynamicAccessMetadata(List into) { - if (into.contains("--emit build-report") && Files.exists(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json"))) { + if (Files.exists(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json"))) { into.add("-H:DynamicAccessMetadata=" + outputDirectory + File.separator + "dynamic-access-metadata.json"); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java index f40f75ad0..9732006b8 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java @@ -55,6 +55,7 @@ import org.codehaus.plexus.util.xml.Xpp3Dom; import org.graalvm.buildtools.maven.sbom.SBOMGenerator; import org.graalvm.buildtools.utils.NativeImageUtils; +import org.twdata.maven.mojoexecutor.MojoExecutor; import java.util.Arrays; import java.util.List; @@ -110,6 +111,8 @@ public void execute() throws MojoExecutionException { maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-assembly-plugin", "archive", "manifest", "mainClass"); maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass"); maybeAddGeneratedResourcesConfig(buildArgs); + + generateDynamicAccessMetadataIfNeeded(buildArgs); maybeAddDynamicAccessMetadata(buildArgs); generateBaseSBOMIfNeeded(); @@ -117,6 +120,30 @@ public void execute() throws MojoExecutionException { buildImage(); } + /** + * Executes the {@code generateDynamicAccessMetadata} goal of the + * {@code native-maven-plugin} if the build arguments indicate that + * a build report should be emitted. + * + * @throws MojoExecutionException if executing the {@code generateDynamicAccessMetadata} + * Mojo fails + */ + private void generateDynamicAccessMetadataIfNeeded(List buildArgs) throws MojoExecutionException { + if (buildArgs.contains("--emit build-report")) { + MojoExecutor.executeMojo( + MojoExecutor.plugin( + MojoExecutor.groupId(project.getPlugin("org.graalvm.buildtools:native-maven-plugin").getGroupId()), + MojoExecutor.artifactId(project.getPlugin("org.graalvm.buildtools:native-maven-plugin").getArtifactId()), + MojoExecutor.version(project.getPlugin("org.graalvm.buildtools:native-maven-plugin").getVersion()) + ), + MojoExecutor.goal("generateDynamicAccessMetadata"), + MojoExecutor.configuration( + MojoExecutor.element("outputJson", "${project.build.directory}/dynamic-access-metadata.json") + ), + MojoExecutor.executionEnvironment(project, session, pluginManager)); + } + } + /** * Invokes {@link SBOMGenerator#generateIfSupportedAndEnabled} if {@link NativeCompileNoForkMojo#skipBaseSBOM} * is false. From fc0acfbb54e9ce727e654669bf4b363753efbe82 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 30 Oct 2025 15:26:43 +0100 Subject: [PATCH 09/22] Add EOF newlines --- .../buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java | 2 +- .../buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 2ccc1c842..3934f7a4c 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -171,4 +171,4 @@ private void writeMapToJson(File outputFile, Map> exportMap) GraalVMLogger.of(getLogger()).log("Failed to write export map to JSON: {}", e); } } -} \ No newline at end of file +} diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index d4bafa4d3..7aab07f3d 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -199,4 +199,4 @@ private void writeMapToJson(File outputFile, Map> exportMap) getLog().warn("Failed to write export map to JSON: " + e); } } -} \ No newline at end of file +} From fe588bc6ceed8c66cd5623fcc9b0c4cb30014c2b Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 31 Oct 2025 14:32:42 +0100 Subject: [PATCH 10/22] Make the dynamic access metadata generation parts of the maven and gradle plugins add the generated file to the classpath instead of a new option --- .../org/graalvm/buildtools/gradle/NativeImagePlugin.java | 9 ++++----- .../buildtools/maven/AbstractNativeImageMojo.java | 5 +++-- .../buildtools/maven/NativeCompileNoForkMojo.java | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 11813f294..7d828eabe 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -93,6 +93,7 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileSystemLocation; import org.gradle.api.file.FileSystemOperations; +import org.gradle.api.file.RegularFile; import org.gradle.api.logging.LogLevel; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.JavaApplication; @@ -398,12 +399,10 @@ private void configureAutomaticTaskCreation(Project project, deriveTaskName(binaryName, "generate", "DynamicAccessMetadata")); imageBuilder.configure(buildImageTask -> { if (buildImageTask.getOptions().get().getBuildArgs().get().contains("--emit build-report")) { - options.getBuildArgs().addAll( - generateDynamicAccessMetadata.flatMap(generateDynamicAccessMetadataTask -> - generateDynamicAccessMetadataTask.getOutputJson().map(file -> - List.of("-H:DynamicAccessMetadata=" + file.getAsFile().getAbsolutePath()) + options.getClasspath().from( + generateDynamicAccessMetadata.flatMap(task -> + task.getOutputJson().map(RegularFile::getAsFile) ) - ) ); } }); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java index 655f8b145..d1034f9d6 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -437,6 +437,7 @@ protected void populateClasspath() throws MojoExecutionException { addDependenciesToClasspath(); } addInferredDependenciesToClasspath(); + maybeAddDynamicAccessMetadataToClasspath(); imageClasspath.removeIf(entry -> !entry.toFile().exists()); } @@ -543,9 +544,9 @@ protected void maybeAddGeneratedResourcesConfig(List into) { } } - protected void maybeAddDynamicAccessMetadata(List into) { + protected void maybeAddDynamicAccessMetadataToClasspath() { if (Files.exists(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json"))) { - into.add("-H:DynamicAccessMetadata=" + outputDirectory + File.separator + "dynamic-access-metadata.json"); + imageClasspath.add(Path.of(outputDirectory.getPath() ,"dynamic-access-metadata.json")); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java index 9732006b8..639d890b4 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java @@ -113,7 +113,6 @@ public void execute() throws MojoExecutionException { maybeAddGeneratedResourcesConfig(buildArgs); generateDynamicAccessMetadataIfNeeded(buildArgs); - maybeAddDynamicAccessMetadata(buildArgs); generateBaseSBOMIfNeeded(); From 3ba7d3349495377c697db10263b9ece28d257451 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 31 Oct 2025 15:14:15 +0100 Subject: [PATCH 11/22] Add documentation for the dynamic access metadata generation parts of the maven and gradle plugins --- .../tasks/GenerateDynamicAccessMetadata.java | 21 ++++++++++++++++++- .../NativeBuildDynamicAccessMetadataMojo.java | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 3934f7a4c..230510484 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -67,9 +67,12 @@ * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image * Build Report. This json file contains the mapping of all classpath entries that exist in the * {@code library-and-framework-list.json} to their transitive dependencies. - *
+ *

* If {@code library-and-framework-list.json} doesn't exist in the used release of the * {@code GraalVM Reachability Metadata} repository, this task does nothing. + *

+ * The format of the generated JSON file conforms the following + * schema. */ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; @@ -109,6 +112,10 @@ public void generate() { } } + /** + * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each + * entry in the {@code library-and-framework.json} file. + */ private Set readArtifacts(File inputFile) throws IOException { Set artifacts = new HashSet<>(); String content = Files.readString(inputFile.toPath()); @@ -122,6 +129,11 @@ private Set readArtifacts(File inputFile) throws IOException { return artifacts; } + /** + * Builds a mapping from each JAR path in the classpath, whose corresponding artifact + * exists in the {@code library-and-framework.json} file, to the set of all of its + * transitive dependency JAR paths. + */ private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathJars) { Map> exportMap = new HashMap<>(); for (ResolvedDependency dependency : dependencies) { @@ -142,6 +154,9 @@ private Map> buildExportMap(Set dependen return exportMap; } + /** + * Recursively collects all classpath JARs for the given dependency and its transitive dependencies. + */ private void collectDependencies(ResolvedDependency dep, Set collector, Set classpathJars) { for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { File file = artifact.getFile(); @@ -155,6 +170,10 @@ private void collectDependencies(ResolvedDependency dep, Set collector, } } + /** + * Writes the export map to a JSON file. Each key (a JAR path) maps to + * a JSON array of JAR paths of its dependencies. + */ private void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 7aab07f3d..b2f19f083 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -75,9 +75,12 @@ * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image * Build Report. This json file contains the mapping of all classpath entries that exist in the * {@code library-and-framework-list.json} to their transitive dependencies. - *
+ *

* If {@code library-and-framework-list.json} doesn't exist in the used release of the * {@code GraalVM Reachability Metadata} repository, this task does nothing. + *

+ * The format of the generated JSON file conforms the following + * schema. */ @Mojo(name = "generateDynamicAccessMetadata", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) public class NativeBuildDynamicAccessMetadataMojo extends AbstractNativeMojo { @@ -126,6 +129,10 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + /** + * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each + * entry in the {@code library-and-framework.json} file. + */ private Set readArtifacts(File inputFile) throws IOException { Set artifacts = new HashSet<>(); String content = Files.readString(inputFile.toPath()); @@ -139,6 +146,11 @@ private Set readArtifacts(File inputFile) throws IOException { return artifacts; } + /** + * Builds a mapping from each JAR path in the classpath, whose corresponding artifact + * exists in the {@code library-and-framework.json} file, to the set of all of its + * transitive dependency JAR paths. + */ private Map> buildExportMap(Set artifactsToInclude, Map coordinatesToPath) throws DependencyCollectionException { Map> exportMap = new HashMap<>(); @@ -157,6 +169,9 @@ private Map> buildExportMap(Set artifactsToInclude, return exportMap; } + /** + * Collects the transitive dependency JAR paths for the artifact identified by the given coordinates. + */ private Set collectDependencies(String coordinates, Map coordinatesToPath) throws DependencyCollectionException { DefaultArtifact artifact = new DefaultArtifact(coordinates); @@ -183,6 +198,10 @@ private Set collectDependencies(String coordinates, Map return dependencies; } + /** + * Writes the export map to a JSON file. Each key (a JAR path) maps to + * a JSON array of JAR paths of its dependencies. + */ private void writeMapToJson(File outputFile, Map> exportMap) { try { JSONObject root = new JSONObject(); From 5f0b4c593873b75d06195d55de4691a019f4237f Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 31 Oct 2025 15:26:31 +0100 Subject: [PATCH 12/22] Make --emit build-report detection cover cases where the build report is given a path --- .../java/org/graalvm/buildtools/gradle/NativeImagePlugin.java | 3 ++- .../org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 7d828eabe..cce2b14bb 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -398,7 +398,8 @@ private void configureAutomaticTaskCreation(Project project, tasks, deriveTaskName(binaryName, "generate", "DynamicAccessMetadata")); imageBuilder.configure(buildImageTask -> { - if (buildImageTask.getOptions().get().getBuildArgs().get().contains("--emit build-report")) { + if (buildImageTask.getOptions().get().getBuildArgs().get().stream() + .anyMatch(arg -> arg.startsWith("--emit build-report"))) { options.getClasspath().from( generateDynamicAccessMetadata.flatMap(task -> task.getOutputJson().map(RegularFile::getAsFile) diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java index 639d890b4..a9baff345 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeCompileNoForkMojo.java @@ -128,7 +128,7 @@ public void execute() throws MojoExecutionException { * Mojo fails */ private void generateDynamicAccessMetadataIfNeeded(List buildArgs) throws MojoExecutionException { - if (buildArgs.contains("--emit build-report")) { + if (buildArgs.stream().anyMatch(arg -> arg.startsWith("--emit build-report"))) { MojoExecutor.executeMojo( MojoExecutor.plugin( MojoExecutor.groupId(project.getPlugin("org.graalvm.buildtools:native-maven-plugin").getGroupId()), From 05a2271e3aed4b70e19ed8c34a0d6cc144c0dcf9 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 3 Nov 2025 08:48:33 +0100 Subject: [PATCH 13/22] Move away from "JAR" usage, as different formats can be also found on the classpath --- .../tasks/GenerateDynamicAccessMetadata.java | 26 +++++++++---------- .../NativeBuildDynamicAccessMetadataMojo.java | 10 +++---- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 230510484..8b47e9fa7 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -98,12 +98,12 @@ public void generate() { Set artifactsToInclude = readArtifacts(jsonFile); Configuration runtimeClasspathConfig = getRuntimeClasspath().get(); - Set classpathJars = runtimeClasspathConfig.getFiles(); + Set classpathEntries = runtimeClasspathConfig.getFiles(); Map> exportMap = buildExportMap( runtimeClasspathConfig.getResolvedConfiguration().getFirstLevelModuleDependencies(), artifactsToInclude, - classpathJars + classpathEntries ); writeMapToJson(getOutputJson().getAsFile().get(), exportMap); @@ -130,11 +130,11 @@ private Set readArtifacts(File inputFile) throws IOException { } /** - * Builds a mapping from each JAR path in the classpath, whose corresponding artifact + * Builds a mapping from each entry in the classpath, whose corresponding artifact * exists in the {@code library-and-framework.json} file, to the set of all of its - * transitive dependency JAR paths. + * transitive dependency entry paths. */ - private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathJars) { + private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathEntries) { Map> exportMap = new HashMap<>(); for (ResolvedDependency dependency : dependencies) { String dependencyCoordinates = dependency.getModuleGroup() + ":" + dependency.getModuleName(); @@ -144,9 +144,9 @@ private Map> buildExportMap(Set dependen for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) { File file = artifact.getFile(); - if (classpathJars.contains(file)) { + if (classpathEntries.contains(file)) { Set files = new HashSet<>(); - collectDependencies(dependency, files, classpathJars); + collectDependencies(dependency, files, classpathEntries); exportMap.put(file.getAbsolutePath(), files); } } @@ -155,24 +155,24 @@ private Map> buildExportMap(Set dependen } /** - * Recursively collects all classpath JARs for the given dependency and its transitive dependencies. + * Recursively collects all classpath entry paths for the given dependency and its transitive dependencies. */ - private void collectDependencies(ResolvedDependency dep, Set collector, Set classpathJars) { + private void collectDependencies(ResolvedDependency dep, Set collector, Set classpathEntries) { for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { File file = artifact.getFile(); - if (classpathJars.contains(file)) { + if (classpathEntries.contains(file)) { collector.add(file.getAbsolutePath()); } } for (ResolvedDependency child : dep.getChildren()) { - collectDependencies(child, collector, classpathJars); + collectDependencies(child, collector, classpathEntries); } } /** - * Writes the export map to a JSON file. Each key (a JAR path) maps to - * a JSON array of JAR paths of its dependencies. + * Writes the export map to a JSON file. Each key (a classpath entry) maps to + * a JSON array of classpath entry paths of its dependencies. */ private void writeMapToJson(File outputFile, Map> exportMap) { try { diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index b2f19f083..19b7bd15b 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -147,9 +147,9 @@ private Set readArtifacts(File inputFile) throws IOException { } /** - * Builds a mapping from each JAR path in the classpath, whose corresponding artifact + * Builds a mapping from each entry in the classpath, whose corresponding artifact * exists in the {@code library-and-framework.json} file, to the set of all of its - * transitive dependency JAR paths. + * transitive dependency entry paths. */ private Map> buildExportMap(Set artifactsToInclude, Map coordinatesToPath) throws DependencyCollectionException { Map> exportMap = new HashMap<>(); @@ -170,7 +170,7 @@ private Map> buildExportMap(Set artifactsToInclude, } /** - * Collects the transitive dependency JAR paths for the artifact identified by the given coordinates. + * Recursively collects all classpath entry paths for the given dependency and its transitive dependencies. */ private Set collectDependencies(String coordinates, Map coordinatesToPath) throws DependencyCollectionException { DefaultArtifact artifact = new DefaultArtifact(coordinates); @@ -199,8 +199,8 @@ private Set collectDependencies(String coordinates, Map } /** - * Writes the export map to a JSON file. Each key (a JAR path) maps to - * a JSON array of JAR paths of its dependencies. + * Writes the export map to a JSON file. Each key (a classpath entry) maps to + * a JSON array of classpath entry paths of its dependencies. */ private void writeMapToJson(File outputFile, Map> exportMap) { try { From 564ef468e50d70a033ef214a0a70923ab1fa49fa Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 7 Nov 2025 13:04:22 +0100 Subject: [PATCH 14/22] Make the JSON output format more verbose for the native-gradle-plugin --- .../tasks/GenerateDynamicAccessMetadata.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 8b47e9fa7..e3200431a 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -176,18 +176,24 @@ private void collectDependencies(ResolvedDependency dep, Set collector, */ private void writeMapToJson(File outputFile, Map> exportMap) { try { - JSONObject root = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (Map.Entry> entry : exportMap.entrySet()) { - JSONArray array = new JSONArray(); - entry.getValue().forEach(array::put); - root.put(entry.getKey(), array); + JSONObject obj = new JSONObject(); + obj.put("metadataProvider", entry.getKey()); + + JSONArray providedArray = new JSONArray(); + entry.getValue().forEach(providedArray::put); + obj.put("providesFor", providedArray); + + jsonArray.put(obj); } try (FileWriter writer = new FileWriter(outputFile)) { - writer.write(root.toString(2)); + writer.write(jsonArray.toString(2)); } } catch (Exception e) { - GraalVMLogger.of(getLogger()).log("Failed to write export map to JSON: {}", e); + GraalVMLogger.of(getLogger()).log("Failed to write dynamic access metadata JSON: {}", e); } } } From da047df752d80867b5e0e4cde037288dc27b799c Mon Sep 17 00:00:00 2001 From: jvukicev Date: Fri, 7 Nov 2025 14:37:39 +0100 Subject: [PATCH 15/22] Make the JSON output format more verbose for the native-maven-plugin --- .../tasks/GenerateDynamicAccessMetadata.java | 2 +- .../NativeBuildDynamicAccessMetadataMojo.java | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index e3200431a..996af357d 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -192,7 +192,7 @@ private void writeMapToJson(File outputFile, Map> exportMap) try (FileWriter writer = new FileWriter(outputFile)) { writer.write(jsonArray.toString(2)); } - } catch (Exception e) { + } catch (IOException e) { GraalVMLogger.of(getLogger()).log("Failed to write dynamic access metadata JSON: {}", e); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 19b7bd15b..d851c9156 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -204,15 +204,21 @@ private Set collectDependencies(String coordinates, Map */ private void writeMapToJson(File outputFile, Map> exportMap) { try { - JSONObject root = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (Map.Entry> entry : exportMap.entrySet()) { - JSONArray array = new JSONArray(); - entry.getValue().forEach(array::put); - root.put(entry.getKey(), array); + JSONObject obj = new JSONObject(); + obj.put("metadataProvider", entry.getKey()); + + JSONArray providedArray = new JSONArray(); + entry.getValue().forEach(providedArray::put); + obj.put("providesFor", providedArray); + + jsonArray.put(obj); } try (FileWriter writer = new FileWriter(outputFile)) { - writer.write(root.toString(2)); + writer.write(jsonArray.toString(2)); } } catch (IOException e) { getLog().warn("Failed to write export map to JSON: " + e); From 6b9ccb89db527dba4af3320f44a770751fa0c363 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 10 Nov 2025 11:12:11 +0100 Subject: [PATCH 16/22] Update docs --- .../tasks/GenerateDynamicAccessMetadata.java | 19 +++++++++++-------- .../NativeBuildDynamicAccessMetadataMojo.java | 19 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 996af357d..ad3254fd2 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -66,9 +66,9 @@ /** * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image * Build Report. This json file contains the mapping of all classpath entries that exist in the - * {@code library-and-framework-list.json} to their transitive dependencies. + * {@value #LIBRARY_AND_FRAMEWORK_LIST} to their transitive dependencies. *

- * If {@code library-and-framework-list.json} doesn't exist in the used release of the + * If {@value #LIBRARY_AND_FRAMEWORK_LIST} doesn't exist in the used release of the * {@code GraalVM Reachability Metadata} repository, this task does nothing. *

* The format of the generated JSON file conforms the following @@ -76,6 +76,9 @@ */ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; + private static final String ARTIFACT = "artifact"; + private static final String METADATA_PROVIDER = "metadataProvider"; + private static final String PROVIDES_FOR = "providesFor"; @Internal public abstract Property getRuntimeClasspath(); @@ -114,7 +117,7 @@ public void generate() { /** * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each - * entry in the {@code library-and-framework.json} file. + * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. */ private Set readArtifacts(File inputFile) throws IOException { Set artifacts = new HashSet<>(); @@ -122,8 +125,8 @@ private Set readArtifacts(File inputFile) throws IOException { JSONArray jsonArray = new JSONArray(content); for (int i = 0; i < jsonArray.length(); i++) { JSONObject entry = jsonArray.getJSONObject(i); - if (entry.has("artifact")) { - artifacts.add(entry.getString("artifact")); + if (entry.has(ARTIFACT)) { + artifacts.add(entry.getString(ARTIFACT)); } } return artifacts; @@ -131,7 +134,7 @@ private Set readArtifacts(File inputFile) throws IOException { /** * Builds a mapping from each entry in the classpath, whose corresponding artifact - * exists in the {@code library-and-framework.json} file, to the set of all of its + * exists in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file, to the set of all of its * transitive dependency entry paths. */ private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathEntries) { @@ -180,11 +183,11 @@ private void writeMapToJson(File outputFile, Map> exportMap) for (Map.Entry> entry : exportMap.entrySet()) { JSONObject obj = new JSONObject(); - obj.put("metadataProvider", entry.getKey()); + obj.put(METADATA_PROVIDER, entry.getKey()); JSONArray providedArray = new JSONArray(); entry.getValue().forEach(providedArray::put); - obj.put("providesFor", providedArray); + obj.put(PROVIDES_FOR, providedArray); jsonArray.put(obj); } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index d851c9156..7ccd639c7 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -74,9 +74,9 @@ /** * Generates a {@code dynamic-access-metadata.json} file used by the dynamic access tab of the native image * Build Report. This json file contains the mapping of all classpath entries that exist in the - * {@code library-and-framework-list.json} to their transitive dependencies. + * {@value #LIBRARY_AND_FRAMEWORK_LIST} to their transitive dependencies. *

- * If {@code library-and-framework-list.json} doesn't exist in the used release of the + * If {@value #LIBRARY_AND_FRAMEWORK_LIST} doesn't exist in the used release of the * {@code GraalVM Reachability Metadata} repository, this task does nothing. *

* The format of the generated JSON file conforms the following @@ -85,6 +85,9 @@ @Mojo(name = "generateDynamicAccessMetadata", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) public class NativeBuildDynamicAccessMetadataMojo extends AbstractNativeMojo { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; + private static final String ARTIFACT = "artifact"; + private static final String METADATA_PROVIDER = "metadataProvider"; + private static final String PROVIDES_FOR = "providesFor"; @Component private RepositorySystem repoSystem; @@ -131,7 +134,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { /** * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each - * entry in the {@code library-and-framework.json} file. + * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. */ private Set readArtifacts(File inputFile) throws IOException { Set artifacts = new HashSet<>(); @@ -139,8 +142,8 @@ private Set readArtifacts(File inputFile) throws IOException { JSONArray jsonArray = new JSONArray(content); for (int i = 0; i < jsonArray.length(); i++) { JSONObject entry = jsonArray.getJSONObject(i); - if (entry.has("artifact")) { - artifacts.add(entry.getString("artifact")); + if (entry.has(ARTIFACT)) { + artifacts.add(entry.getString(ARTIFACT)); } } return artifacts; @@ -148,7 +151,7 @@ private Set readArtifacts(File inputFile) throws IOException { /** * Builds a mapping from each entry in the classpath, whose corresponding artifact - * exists in the {@code library-and-framework.json} file, to the set of all of its + * exists in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file, to the set of all of its * transitive dependency entry paths. */ private Map> buildExportMap(Set artifactsToInclude, Map coordinatesToPath) throws DependencyCollectionException { @@ -208,11 +211,11 @@ private void writeMapToJson(File outputFile, Map> exportMap) for (Map.Entry> entry : exportMap.entrySet()) { JSONObject obj = new JSONObject(); - obj.put("metadataProvider", entry.getKey()); + obj.put(METADATA_PROVIDER, entry.getKey()); JSONArray providedArray = new JSONArray(); entry.getValue().forEach(providedArray::put); - obj.put("providesFor", providedArray); + obj.put(PROVIDES_FOR, providedArray); jsonArray.put(obj); } From 7655aca6d98c0459b25ca243c1e6ab7c11697828 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 10 Nov 2025 12:16:06 +0100 Subject: [PATCH 17/22] Make GraalVMReachabilityMetadataService#getRepositoryDirectory optional --- .../GraalVMReachabilityMetadataService.java | 7 ++++--- .../gradle/tasks/GenerateDynamicAccessMetadata.java | 13 +++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java index 4e278ac73..07e9518fd 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/GraalVMReachabilityMetadataService.java @@ -71,6 +71,7 @@ import java.util.Collection; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; @@ -236,10 +237,10 @@ public Set findConfigurationsFor(Set excludedMod }); } - public Path getRepositoryDirectory() { + public Optional getRepositoryDirectory() { if (repository instanceof FileSystemRepository fsRepo) { - return fsRepo.getRootDirectory(); + return Optional.of(fsRepo.getRootDirectory()); } - return null; + return Optional.empty(); } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index ad3254fd2..d98b51427 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -58,9 +58,11 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -91,9 +93,16 @@ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { @TaskAction public void generate() { - File jsonFile = getMetadataService().get().getRepositoryDirectory().resolve(LIBRARY_AND_FRAMEWORK_LIST).toFile(); + Optional repositoryDirectory = getMetadataService().get().getRepositoryDirectory(); + if (repositoryDirectory.isEmpty()) { + GraalVMLogger.of(getLogger()) + .log("No reachability metadata repository is configured or available."); + return; + } + File jsonFile = repositoryDirectory.get().resolve(LIBRARY_AND_FRAMEWORK_LIST).toFile(); if (!jsonFile.exists()) { - GraalVMLogger.of(getLogger()).log("{} is not packaged with the provided reachability metadata repository.", LIBRARY_AND_FRAMEWORK_LIST); + GraalVMLogger.of(getLogger()) + .log("{} is not packaged with the provided reachability metadata repository.", LIBRARY_AND_FRAMEWORK_LIST); return; } From 1c497ea0dba4428082b121350c977e9946315b7a Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 10 Nov 2025 15:28:40 +0100 Subject: [PATCH 18/22] Migrate to the use of LinkedHashSet to have consistent results across invocations --- .../gradle/tasks/GenerateDynamicAccessMetadata.java | 5 +++-- .../maven/NativeBuildDynamicAccessMetadataMojo.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index d98b51427..131b6f367 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -61,6 +61,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -129,7 +130,7 @@ public void generate() { * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. */ private Set readArtifacts(File inputFile) throws IOException { - Set artifacts = new HashSet<>(); + Set artifacts = new LinkedHashSet<>(); String content = Files.readString(inputFile.toPath()); JSONArray jsonArray = new JSONArray(content); for (int i = 0; i < jsonArray.length(); i++) { @@ -157,7 +158,7 @@ private Map> buildExportMap(Set dependen for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) { File file = artifact.getFile(); if (classpathEntries.contains(file)) { - Set files = new HashSet<>(); + Set files = new LinkedHashSet<>(); collectDependencies(dependency, files, classpathEntries); exportMap.put(file.getAbsolutePath(), files); } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 7ccd639c7..4833eec2d 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -66,7 +66,7 @@ import java.io.IOException; import java.nio.file.Files; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -137,7 +137,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. */ private Set readArtifacts(File inputFile) throws IOException { - Set artifacts = new HashSet<>(); + Set artifacts = new LinkedHashSet<>(); String content = Files.readString(inputFile.toPath()); JSONArray jsonArray = new JSONArray(content); for (int i = 0; i < jsonArray.length(); i++) { @@ -187,7 +187,7 @@ private Set collectDependencies(String coordinates, Map PreorderNodeListGenerator nlg = new PreorderNodeListGenerator(); node.accept(nlg); - Set dependencies = new HashSet<>(); + Set dependencies = new LinkedHashSet<>(); nlg.getNodes().forEach(dependencyNode -> { if (dependencyNode.getDependency() != null) { DefaultArtifact dependencyArtifact = (DefaultArtifact) dependencyNode.getDependency().getArtifact(); From e948593fcdeb72b97af1497b6459630c949b8ce6 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Mon, 10 Nov 2025 15:53:33 +0100 Subject: [PATCH 19/22] Use non-eager transformation when checking if a build report is being emitted --- .../buildtools/gradle/NativeImagePlugin.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index cce2b14bb..7dfa0bee3 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -398,15 +398,20 @@ private void configureAutomaticTaskCreation(Project project, tasks, deriveTaskName(binaryName, "generate", "DynamicAccessMetadata")); imageBuilder.configure(buildImageTask -> { - if (buildImageTask.getOptions().get().getBuildArgs().get().stream() - .anyMatch(arg -> arg.startsWith("--emit build-report"))) { - options.getClasspath().from( - generateDynamicAccessMetadata.flatMap(task -> - task.getOutputJson().map(RegularFile::getAsFile) - ) - ); - } + Provider emittingBuildReport = + buildImageTask.getOptions() + .flatMap(o -> o.getBuildArgs() + .map(args -> args.stream() + .anyMatch(arg -> arg.startsWith("--emit build-report")))); + options.getClasspath().from( + emittingBuildReport.flatMap(enabled -> + enabled + ? generateDynamicAccessMetadata.flatMap(task -> + task.getOutputJson().map(RegularFile::getAsFile)) + : buildImageTask.getProject().provider(Collections::emptyList)) + ); }); + configureJvmReachabilityConfigurationDirectories(project, graalExtension, options, sourceSet); configureJvmReachabilityExcludeConfigArgs(project, graalExtension, options, sourceSet); }); From d001a47f5ef32d8f49ffb99a1b00688519b7833e Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 12 Nov 2025 15:08:57 +0100 Subject: [PATCH 20/22] Use Property and PropertySet instead of relying on Property --- .../buildtools/gradle/NativeImagePlugin.java | 2 +- .../tasks/GenerateDynamicAccessMetadata.java | 99 +++++++++++++------ .../NativeBuildDynamicAccessMetadataMojo.java | 9 +- 3 files changed, 74 insertions(+), 36 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index 7dfa0bee3..a92a4c321 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java @@ -685,7 +685,7 @@ private TaskProvider registerDynamicAccessMetadat TaskContainer tasks, String name) { return tasks.register(name, GenerateDynamicAccessMetadata.class, task -> { - task.getRuntimeClasspath().set(classpathConfiguration); + task.setClasspath(classpathConfiguration); task.getMetadataService().set(metadataService); task.getOutputJson().set(buildDir.dir("generated").map(dir -> dir.file("dynamic-access-metadata.json"))); }); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index 131b6f367..fe501c1a7 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -46,10 +46,14 @@ import org.graalvm.buildtools.gradle.internal.GraalVMReachabilityMetadataService; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.result.DependencyResult; +import org.gradle.api.artifacts.result.ResolvedArtifactResult; +import org.gradle.api.artifacts.result.ResolvedComponentResult; +import org.gradle.api.artifacts.result.ResolvedDependencyResult; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; @@ -60,7 +64,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; @@ -83,8 +86,16 @@ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { private static final String METADATA_PROVIDER = "metadataProvider"; private static final String PROVIDES_FOR = "providesFor"; + public void setClasspath(Configuration classpath) { + getRuntimeClasspathGraph().set(classpath.getIncoming().getResolutionResult().getRootComponent()); + getRuntimeClasspathArtifacts().set(classpath.getIncoming().getArtifacts().getResolvedArtifacts()); + } + + @Internal + public abstract Property getRuntimeClasspathGraph(); + @Internal - public abstract Property getRuntimeClasspath(); + public abstract SetProperty getRuntimeClasspathArtifacts(); @Internal public abstract Property getMetadataService(); @@ -110,16 +121,21 @@ public void generate() { try { Set artifactsToInclude = readArtifacts(jsonFile); - Configuration runtimeClasspathConfig = getRuntimeClasspath().get(); - Set classpathEntries = runtimeClasspathConfig.getFiles(); + Map coordinatesToPath = new HashMap<>(); + for (ResolvedArtifactResult artifact : getRuntimeClasspathArtifacts().get()) { + if (artifact.getId().getComponentIdentifier() instanceof ModuleComponentIdentifier mci) { + String coordinates = mci.getGroup() + ":" + mci.getModule(); + coordinatesToPath.put(coordinates, artifact.getFile().getAbsolutePath()); + } + } - Map> exportMap = buildExportMap( - runtimeClasspathConfig.getResolvedConfiguration().getFirstLevelModuleDependencies(), - artifactsToInclude, - classpathEntries - ); + ResolvedComponentResult root = getRuntimeClasspathGraph().get(); + + Map> exportMap = buildExportMap(root, artifactsToInclude, coordinatesToPath); writeMapToJson(getOutputJson().getAsFile().get(), exportMap); + + GraalVMLogger.of(getLogger()).log("Dynamic Access Metadata written into " + getOutputJson().get()); } catch (IOException e) { GraalVMLogger.of(getLogger()).log("Failed to generate dynamic access metadata: {}", e); } @@ -147,40 +163,61 @@ private Set readArtifacts(File inputFile) throws IOException { * exists in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file, to the set of all of its * transitive dependency entry paths. */ - private Map> buildExportMap(Set dependencies, Set artifactsToInclude, Set classpathEntries) { + private Map> buildExportMap(ResolvedComponentResult root, Set artifactsToInclude, Map coordinatesToPath) { Map> exportMap = new HashMap<>(); - for (ResolvedDependency dependency : dependencies) { - String dependencyCoordinates = dependency.getModuleGroup() + ":" + dependency.getModuleName(); - if (!artifactsToInclude.contains(dependencyCoordinates)) { - continue; - } + Map> dependencyMap = new HashMap<>(); - for (ResolvedArtifact artifact : dependency.getModuleArtifacts()) { - File file = artifact.getFile(); - if (classpathEntries.contains(file)) { - Set files = new LinkedHashSet<>(); - collectDependencies(dependency, files, classpathEntries); - exportMap.put(file.getAbsolutePath(), files); + collectDependencies(root, dependencyMap, new LinkedHashSet<>(), coordinatesToPath); + + for (Map.Entry> entry : dependencyMap.entrySet()) { + String coordinates = entry.getKey(); + if (artifactsToInclude.contains(coordinates)) { + String absolutePath = coordinatesToPath.get(coordinates); + if (absolutePath != null) { + exportMap.put(absolutePath, entry.getValue()); } } } + return exportMap; } /** * Recursively collects all classpath entry paths for the given dependency and its transitive dependencies. */ - private void collectDependencies(ResolvedDependency dep, Set collector, Set classpathEntries) { - for (ResolvedArtifact artifact : dep.getModuleArtifacts()) { - File file = artifact.getFile(); - if (classpathEntries.contains(file)) { - collector.add(file.getAbsolutePath()); - } + private void collectDependencies(ResolvedComponentResult node, Map> dependencyMap, Set visited, Map coordinatesToPath) { + String coordinates = null; + if (node.getId() instanceof ModuleComponentIdentifier mci) { + coordinates = mci.getGroup() + ":" + mci.getModule(); } - for (ResolvedDependency child : dep.getChildren()) { - collectDependencies(child, collector, classpathEntries); + if (coordinates != null && !visited.add(coordinates)) { + return; + } + + Set dependencies = new LinkedHashSet<>(); + for (DependencyResult dep : node.getDependencies()) { + if (dep instanceof ResolvedDependencyResult resolved) { + ResolvedComponentResult target = resolved.getSelected(); + + if (target.getId() instanceof ModuleComponentIdentifier targetMci) { + String dependencyCoordinates = targetMci.getGroup() + ":" + targetMci.getModule(); + String dependencyPath = coordinatesToPath.get(dependencyCoordinates); + + if (dependencyPath != null) { + dependencies.add(dependencyPath); + } + + collectDependencies(target, dependencyMap, visited, coordinatesToPath); + + Set transitiveDependencies = dependencyMap.get(dependencyCoordinates); + if (transitiveDependencies != null) { + dependencies.addAll(transitiveDependencies); + } + } + } } + dependencyMap.put(coordinates, dependencies); } /** diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 4833eec2d..878de2779 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -115,10 +115,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { Set artifactsToInclude = readArtifacts(jsonFile); Map coordinatesToPath = new HashMap<>(); - for (Artifact a : project.getArtifacts()) { - if (a.getFile() != null) { - String coordinates = a.getGroupId() + ":" + a.getArtifactId(); - coordinatesToPath.put(coordinates, a.getFile().getAbsolutePath()); + for (Artifact artifact : project.getArtifacts()) { + File file = artifact.getFile(); + if (file != null) { + String coordinates = artifact.getGroupId() + ":" + artifact.getArtifactId(); + coordinatesToPath.put(coordinates, file.getAbsolutePath()); } } From f04d7e276660fe168e3bd1f7f7d62c802096cd47 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 12 Nov 2025 15:55:42 +0100 Subject: [PATCH 21/22] Move JSON generation and parsing to a new DynamicAccessMetadataUtils class, that is used by both the Maven and Gradle plugin --- .../utils/DynamicAccessMetadataUtils.java | 50 +++++++++++++++++ native-gradle-plugin/build.gradle | 1 - .../tasks/GenerateDynamicAccessMetadata.java | 55 ++----------------- .../NativeBuildDynamicAccessMetadataMojo.java | 53 ++---------------- 4 files changed, 62 insertions(+), 97 deletions(-) create mode 100644 common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java diff --git a/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java b/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java new file mode 100644 index 000000000..41195586c --- /dev/null +++ b/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java @@ -0,0 +1,50 @@ +package org.graalvm.buildtools.utils; + +import com.github.openjson.JSONArray; +import com.github.openjson.JSONObject; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +public final class DynamicAccessMetadataUtils { + /** + * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each + * entry in the {@code library-and-framework-list.json} file. + */ + public static Set readArtifacts(File inputFile) throws IOException { + Set artifacts = new LinkedHashSet<>(); + String content = Files.readString(inputFile.toPath()); + JSONArray jsonArray = new JSONArray(content); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject entry = jsonArray.getJSONObject(i); + if (entry.has("artifact")) { + artifacts.add(entry.getString("artifact")); + } + } + return artifacts; + } + + public static void serialize(File outputFile, Map> exportMap) throws IOException { + JSONArray jsonArray = new JSONArray(); + + for (Map.Entry> entry : exportMap.entrySet()) { + JSONObject obj = new JSONObject(); + obj.put("metadataProvider", entry.getKey()); + + JSONArray providedArray = new JSONArray(); + entry.getValue().forEach(providedArray::put); + obj.put("providesFor", providedArray); + + jsonArray.put(obj); + } + + try (FileWriter writer = new FileWriter(outputFile)) { + writer.write(jsonArray.toString(2)); + } + } +} diff --git a/native-gradle-plugin/build.gradle b/native-gradle-plugin/build.gradle index a0aafdd8f..d5ce50d35 100644 --- a/native-gradle-plugin/build.gradle +++ b/native-gradle-plugin/build.gradle @@ -56,7 +56,6 @@ maven { } dependencies { - implementation libs.openjson implementation libs.utils implementation libs.jvmReachabilityMetadata testImplementation libs.test.spock diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index fe501c1a7..aa07b4281 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -40,10 +40,9 @@ */ package org.graalvm.buildtools.gradle.tasks; -import com.github.openjson.JSONArray; -import com.github.openjson.JSONObject; import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GraalVMReachabilityMetadataService; +import org.graalvm.buildtools.utils.DynamicAccessMetadataUtils; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; @@ -59,9 +58,7 @@ import org.gradle.api.tasks.TaskAction; import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashSet; @@ -82,9 +79,6 @@ */ public abstract class GenerateDynamicAccessMetadata extends DefaultTask { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; - private static final String ARTIFACT = "artifact"; - private static final String METADATA_PROVIDER = "metadataProvider"; - private static final String PROVIDES_FOR = "providesFor"; public void setClasspath(Configuration classpath) { getRuntimeClasspathGraph().set(classpath.getIncoming().getResolutionResult().getRootComponent()); @@ -119,7 +113,7 @@ public void generate() { } try { - Set artifactsToInclude = readArtifacts(jsonFile); + Set artifactsToInclude = DynamicAccessMetadataUtils.readArtifacts(jsonFile); Map coordinatesToPath = new HashMap<>(); for (ResolvedArtifactResult artifact : getRuntimeClasspathArtifacts().get()) { @@ -133,31 +127,12 @@ public void generate() { Map> exportMap = buildExportMap(root, artifactsToInclude, coordinatesToPath); - writeMapToJson(getOutputJson().getAsFile().get(), exportMap); - - GraalVMLogger.of(getLogger()).log("Dynamic Access Metadata written into " + getOutputJson().get()); + serializeExportMap(getOutputJson().getAsFile().get(), exportMap); } catch (IOException e) { GraalVMLogger.of(getLogger()).log("Failed to generate dynamic access metadata: {}", e); } } - /** - * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each - * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. - */ - private Set readArtifacts(File inputFile) throws IOException { - Set artifacts = new LinkedHashSet<>(); - String content = Files.readString(inputFile.toPath()); - JSONArray jsonArray = new JSONArray(content); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject entry = jsonArray.getJSONObject(i); - if (entry.has(ARTIFACT)) { - artifacts.add(entry.getString(ARTIFACT)); - } - } - return artifacts; - } - /** * Builds a mapping from each entry in the classpath, whose corresponding artifact * exists in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file, to the set of all of its @@ -224,26 +199,8 @@ private void collectDependencies(ResolvedComponentResult node, Map> exportMap) { - try { - JSONArray jsonArray = new JSONArray(); - - for (Map.Entry> entry : exportMap.entrySet()) { - JSONObject obj = new JSONObject(); - obj.put(METADATA_PROVIDER, entry.getKey()); - - JSONArray providedArray = new JSONArray(); - entry.getValue().forEach(providedArray::put); - obj.put(PROVIDES_FOR, providedArray); - - jsonArray.put(obj); - } - - try (FileWriter writer = new FileWriter(outputFile)) { - writer.write(jsonArray.toString(2)); - } - } catch (IOException e) { - GraalVMLogger.of(getLogger()).log("Failed to write dynamic access metadata JSON: {}", e); - } + private void serializeExportMap(File outputFile, Map> exportMap) throws IOException { + DynamicAccessMetadataUtils.serialize(outputFile, exportMap); + GraalVMLogger.of(getLogger()).lifecycle("Dynamic Access Metadata written into " + outputFile); } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index 878de2779..a3d1520db 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -40,8 +40,6 @@ */ package org.graalvm.buildtools.maven; -import com.github.openjson.JSONArray; -import com.github.openjson.JSONObject; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -59,12 +57,11 @@ import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator; +import org.graalvm.buildtools.utils.DynamicAccessMetadataUtils; import org.graalvm.reachability.internal.FileSystemRepository; import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.nio.file.Files; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -85,9 +82,6 @@ @Mojo(name = "generateDynamicAccessMetadata", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) public class NativeBuildDynamicAccessMetadataMojo extends AbstractNativeMojo { private static final String LIBRARY_AND_FRAMEWORK_LIST = "library-and-framework-list.json"; - private static final String ARTIFACT = "artifact"; - private static final String METADATA_PROVIDER = "metadataProvider"; - private static final String PROVIDES_FOR = "providesFor"; @Component private RepositorySystem repoSystem; @@ -112,7 +106,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { } try { - Set artifactsToInclude = readArtifacts(jsonFile); + Set artifactsToInclude = DynamicAccessMetadataUtils.readArtifacts(jsonFile); Map coordinatesToPath = new HashMap<>(); for (Artifact artifact : project.getArtifacts()) { @@ -125,7 +119,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { Map> exportMap = buildExportMap(artifactsToInclude, coordinatesToPath); - writeMapToJson(outputJson, exportMap); + serializeExportMap(outputJson, exportMap); } catch (IOException e) { getLog().warn("Failed generating dynamic access metadata: " + e); } catch (DependencyCollectionException e) { @@ -133,23 +127,6 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } - /** - * Collects all versionless artifact coordinates ({@code groupId:artifactId}) from each - * entry in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file. - */ - private Set readArtifacts(File inputFile) throws IOException { - Set artifacts = new LinkedHashSet<>(); - String content = Files.readString(inputFile.toPath()); - JSONArray jsonArray = new JSONArray(content); - for (int i = 0; i < jsonArray.length(); i++) { - JSONObject entry = jsonArray.getJSONObject(i); - if (entry.has(ARTIFACT)) { - artifacts.add(entry.getString(ARTIFACT)); - } - } - return artifacts; - } - /** * Builds a mapping from each entry in the classpath, whose corresponding artifact * exists in the {@value #LIBRARY_AND_FRAMEWORK_LIST} file, to the set of all of its @@ -206,26 +183,8 @@ private Set collectDependencies(String coordinates, Map * Writes the export map to a JSON file. Each key (a classpath entry) maps to * a JSON array of classpath entry paths of its dependencies. */ - private void writeMapToJson(File outputFile, Map> exportMap) { - try { - JSONArray jsonArray = new JSONArray(); - - for (Map.Entry> entry : exportMap.entrySet()) { - JSONObject obj = new JSONObject(); - obj.put(METADATA_PROVIDER, entry.getKey()); - - JSONArray providedArray = new JSONArray(); - entry.getValue().forEach(providedArray::put); - obj.put(PROVIDES_FOR, providedArray); - - jsonArray.put(obj); - } - - try (FileWriter writer = new FileWriter(outputFile)) { - writer.write(jsonArray.toString(2)); - } - } catch (IOException e) { - getLog().warn("Failed to write export map to JSON: " + e); - } + private void serializeExportMap(File outputFile, Map> exportMap) throws IOException { + DynamicAccessMetadataUtils.serialize(outputFile, exportMap); + getLog().info("Dynamic Access Metadata written into " + outputFile); } } From e2d7aeb11f0053bfc63d8d16ca0a30da41f6db8c Mon Sep 17 00:00:00 2001 From: jvukicev Date: Wed, 12 Nov 2025 16:02:09 +0100 Subject: [PATCH 22/22] Update and move serialization documentation to the util class --- .../buildtools/utils/DynamicAccessMetadataUtils.java | 8 ++++++++ .../gradle/tasks/GenerateDynamicAccessMetadata.java | 4 ---- .../maven/NativeBuildDynamicAccessMetadataMojo.java | 4 ---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java b/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java index 41195586c..654b680c3 100644 --- a/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java +++ b/common/utils/src/main/java/org/graalvm/buildtools/utils/DynamicAccessMetadataUtils.java @@ -29,6 +29,14 @@ public static Set readArtifacts(File inputFile) throws IOException { return artifacts; } + /** + * Serializes dynamic access metadata to JSON. + *

+ * The output follows the schema defined at: + * + * dynamic-access-metadata-schema-v1.0.0.json + * + */ public static void serialize(File outputFile, Map> exportMap) throws IOException { JSONArray jsonArray = new JSONArray(); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java index aa07b4281..2fc05396c 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/GenerateDynamicAccessMetadata.java @@ -195,10 +195,6 @@ private void collectDependencies(ResolvedComponentResult node, Map> exportMap) throws IOException { DynamicAccessMetadataUtils.serialize(outputFile, exportMap); GraalVMLogger.of(getLogger()).lifecycle("Dynamic Access Metadata written into " + outputFile); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java index a3d1520db..0c9aef719 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildDynamicAccessMetadataMojo.java @@ -179,10 +179,6 @@ private Set collectDependencies(String coordinates, Map return dependencies; } - /** - * Writes the export map to a JSON file. Each key (a classpath entry) maps to - * a JSON array of classpath entry paths of its dependencies. - */ private void serializeExportMap(File outputFile, Map> exportMap) throws IOException { DynamicAccessMetadataUtils.serialize(outputFile, exportMap); getLog().info("Dynamic Access Metadata written into " + outputFile);