Skip to content

Commit a4592a7

Browse files
authored
Include buildtool support for bundling reachability metadata into jar files (#322)
* Add DirectoryConfiguration.copy method Update `DirectoryConfiguration` with a new `copy` method that can be used to copy matadata files into `META-INF/native-image` directories. The `DirectoryConfiguration` class now has group, artifact and version information so that it can write files into the appropriate folder. The `copy` method will also write a `reachability-metadata.properties` file that indicates if the 'override' property was set on the metadata. * Add `add-metadata-hints` goal to Maven Plugin Update the maven plugin with a new `add-metadata-hints` goal that can be used to bundle hints obtained from the metadata repository into the jar. * Add `ReachabilityMetadataCopyTask` to Gradle Plugin Update the gradle plugin with a new `ReachabilityMetadataCopyTask` class that can be used to copy hints obtained from the metadata repository. * Refactor common code in NativeImagePlugin Refactor the `configureJvmReachabilityConfigurationDirectories` and `configureJvmReachabilityExcludeConfigArgs` to use a common method since they share the same logic.
1 parent 82dd663 commit a4592a7

File tree

22 files changed

+1191
-475
lines changed

22 files changed

+1191
-475
lines changed

common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/DirectoryConfiguration.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,34 @@
4040
*/
4141
package org.graalvm.reachability;
4242

43+
import java.io.IOException;
44+
import java.nio.charset.StandardCharsets;
45+
import java.nio.file.FileVisitResult;
46+
import java.nio.file.Files;
4347
import java.nio.file.Path;
48+
import java.nio.file.SimpleFileVisitor;
49+
import java.nio.file.StandardCopyOption;
50+
import java.nio.file.attribute.BasicFileAttributes;
51+
import java.util.Collection;
4452

4553
public class DirectoryConfiguration {
4654

55+
private static final String PROPERTIES = "reachability-metadata.properties";
56+
57+
private final String groupId;
58+
59+
private final String artifactId;
60+
61+
private final String version;
62+
4763
private final Path directory;
4864

4965
private final boolean override;
5066

51-
public DirectoryConfiguration(Path directory, boolean override) {
67+
public DirectoryConfiguration(String groupId, String artifactId, String version, Path directory, boolean override) {
68+
this.groupId = groupId;
69+
this.artifactId = artifactId;
70+
this.version = version;
5271
this.directory = directory;
5372
this.override = override;
5473
}
@@ -60,4 +79,49 @@ public Path getDirectory() {
6079
public boolean isOverride() {
6180
return override;
6281
}
82+
83+
public static void copy(Collection<DirectoryConfiguration> configurations, Path destinationDirectory) throws IOException {
84+
Path nativeImageDestination = destinationDirectory.resolve("META-INF").resolve("native-image");
85+
for (DirectoryConfiguration configuration : configurations) {
86+
Path target = nativeImageDestination
87+
.resolve(configuration.groupId)
88+
.resolve(configuration.artifactId)
89+
.resolve((configuration.version != null) ? configuration.version :
90+
configuration.getDirectory().getFileName().toString());
91+
copyFileTree(configuration.directory, target);
92+
writeConfigurationProperties(configuration, target);
93+
}
94+
}
95+
96+
private static void copyFileTree(Path source, Path target) throws IOException {
97+
Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
98+
99+
@Override
100+
public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException {
101+
Files.createDirectories(target.resolve(source.relativize(directory)));
102+
return FileVisitResult.CONTINUE;
103+
}
104+
105+
@Override
106+
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
107+
if (!"index.json".equalsIgnoreCase(file.getFileName().toString())) {
108+
Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
109+
}
110+
return FileVisitResult.CONTINUE;
111+
}
112+
});
113+
}
114+
115+
private static void writeConfigurationProperties(DirectoryConfiguration configuration, Path target)
116+
throws IOException {
117+
StringBuilder content = new StringBuilder();
118+
if (configuration.isOverride()) {
119+
content.append("override=true\n");
120+
}
121+
if (content.length() > 0) {
122+
Files.write(target.resolve(PROPERTIES), content.toString().getBytes(StandardCharsets.ISO_8859_1));
123+
}
124+
}
63125
}
126+
127+

common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/FileSystemRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public Set<DirectoryConfiguration> findConfigurationsFor(Consumer<? super Query>
112112
Optional<DirectoryConfiguration> configuration = index.findConfiguration(groupId, artifactId, version);
113113
if (!configuration.isPresent() && artifactQuery.isUseLatestVersion()) {
114114
logger.log(groupId, artifactId, version, "Configuration directory not found. Trying latest version.");
115-
configuration = index.findLatestConfigurationFor(groupId, artifactId);
115+
configuration = index.findLatestConfigurationFor(groupId, artifactId, version);
116116
if (!configuration.isPresent()) {
117117
logger.log(groupId, artifactId, version, "Latest version not found!");
118118
}

common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,13 @@ private Map<String, List<Artifact>> parseIndexFile(Path rootPath) {
9191
*/
9292
@Override
9393
public Optional<DirectoryConfiguration> findConfiguration(String groupId, String artifactId, String version) {
94-
return findConfigurationFor(groupId, artifactId, artifact -> artifact.getVersions().contains(version));
94+
return findConfigurationFor(groupId, artifactId, version, artifact -> artifact.getVersions().contains(version));
95+
}
96+
97+
@Override
98+
@Deprecated
99+
public Optional<DirectoryConfiguration> findLatestConfigurationFor(String groupId, String artifactId) {
100+
return findConfigurationFor(groupId, artifactId, null, Artifact::isLatest);
95101
}
96102

97103
/**
@@ -102,11 +108,12 @@ public Optional<DirectoryConfiguration> findConfiguration(String groupId, String
102108
* @return a configuration directory, or empty if no configuration directory is available
103109
*/
104110
@Override
105-
public Optional<DirectoryConfiguration> findLatestConfigurationFor(String groupId, String artifactId) {
106-
return findConfigurationFor(groupId, artifactId, Artifact::isLatest);
111+
public Optional<DirectoryConfiguration> findLatestConfigurationFor(String groupId, String artifactId, String version) {
112+
return findConfigurationFor(groupId, artifactId, version, Artifact::isLatest);
107113
}
108114

109-
private Optional<DirectoryConfiguration> findConfigurationFor(String groupId, String artifactId, Predicate<? super Artifact> predicate) {
115+
private Optional<DirectoryConfiguration> findConfigurationFor(String groupId, String artifactId, String version,
116+
Predicate<? super Artifact> predicate) {
110117
String module = groupId + ":" + artifactId;
111118
List<Artifact> artifacts = index.get(module);
112119
if (artifacts == null) {
@@ -116,7 +123,8 @@ private Optional<DirectoryConfiguration> findConfigurationFor(String groupId, St
116123
.filter(artifact -> artifact.getModule().equals(module))
117124
.filter(predicate)
118125
.findFirst()
119-
.map(artifact -> new DirectoryConfiguration(moduleRoot.resolve(artifact.getDirectory()), artifact.isOverride()));
126+
.map(artifact -> new DirectoryConfiguration(groupId, artifactId, version,
127+
moduleRoot.resolve(artifact.getDirectory()), artifact.isOverride()));
120128
}
121129

122130
}

common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ public interface VersionToConfigDirectoryIndex {
6060
* @param groupId the group ID of the artifact
6161
* @param artifactId the artifact ID of the artifact
6262
* @return a configuration, or empty if no configuration directory is available
63+
* @deprecated in favor of {@link #findLatestConfigurationFor(String, String, String)}
6364
*/
65+
@Deprecated
6466
Optional<DirectoryConfiguration> findLatestConfigurationFor(String groupId, String artifactId);
67+
68+
/**
69+
* Returns the latest configuration for the requested artifact.
70+
* @param groupId the group ID of the artifact
71+
* @param artifactId the artifact ID of the artifact
72+
* @param version the version of the artifact
73+
* @return a configuration, or empty if no configuration directory is available
74+
*/
75+
Optional<DirectoryConfiguration> findLatestConfigurationFor(String groupId, String artifactId, String version);
6576
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2020, 2022 Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
42+
package org.graalvm.reachability;
43+
44+
import static org.junit.jupiter.api.Assertions.assertEquals;
45+
import static org.junit.jupiter.api.Assertions.assertFalse;
46+
import static org.junit.jupiter.api.Assertions.assertTrue;
47+
48+
import java.io.ByteArrayInputStream;
49+
import java.io.FileInputStream;
50+
import java.io.IOException;
51+
import java.nio.file.Files;
52+
import java.nio.file.Path;
53+
import java.util.Arrays;
54+
import java.util.Properties;
55+
56+
import org.junit.jupiter.api.Test;
57+
import org.junit.jupiter.api.io.TempDir;
58+
59+
class DirectoryConfigurationTest {
60+
61+
@TempDir
62+
Path temp;
63+
64+
@Test
65+
void copyCopiesFiles() throws IOException {
66+
Path directory = temp.resolve("source/com.example.group/artifact/123");
67+
Path target = temp.resolve("target");
68+
createJsonFiles(directory);
69+
DirectoryConfiguration configuration = new DirectoryConfiguration("com.example.group", "artifact", "123", directory, false);
70+
DirectoryConfiguration.copy(Arrays.asList(configuration), target);
71+
assertTrue(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/reflect-config.json")));
72+
assertTrue(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/other.json")));
73+
assertFalse(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/index.json")));
74+
assertFalse(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/reachability-metadata.properties")));
75+
}
76+
77+
@Test
78+
void copyWhenHasOverrideGeneratesMetadataRepositoryProperties() throws IOException {
79+
Path directory = temp.resolve("source/com.example.group/artifact/123");
80+
Path target = temp.resolve("target");
81+
createJsonFiles(directory);
82+
DirectoryConfiguration configuration = new DirectoryConfiguration("com.example.group", "artifact", "123", directory, true);
83+
DirectoryConfiguration.copy(Arrays.asList(configuration), target);
84+
Path propertiesFile = target.resolve("META-INF/native-image/com.example.group/artifact/123/reachability-metadata.properties");
85+
Properties properties = new Properties();
86+
properties.load(new FileInputStream(propertiesFile.toFile()));
87+
assertEquals("true", properties.getProperty("override"));
88+
}
89+
90+
private void createJsonFiles(Path directory) throws IOException {
91+
Files.createDirectories(directory);
92+
byte[] json = "{}".getBytes();
93+
Files.copy(new ByteArrayInputStream(json), directory.resolve("index.json"));
94+
Files.copy(new ByteArrayInputStream(json), directory.resolve("reflect-config.json"));
95+
Files.copy(new ByteArrayInputStream(json), directory.resolve("other.json"));
96+
}
97+
98+
}

common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void checkIndex() throws URISyntaxException {
8888
config = index.findConfiguration("com.foo", "nope", "1.0");
8989
assertFalse(config.isPresent());
9090

91-
Optional<DirectoryConfiguration> latest = index.findLatestConfigurationFor("com.foo", "bar");
91+
Optional<DirectoryConfiguration> latest = index.findLatestConfigurationFor("com.foo", "bar", "123");
9292
assertTrue(latest.isPresent());
9393
assertEquals(repoPath.resolve("2.0"), latest.get().getDirectory());
9494
assertTrue(latest.get().isOverride());

docs/src/docs/asciidoc/gradle-plugin.adoc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,32 @@ include::../snippets/gradle/groovy/build.gradle[tags=specify-metadata-version-fo
411411
include::../snippets/gradle/kotlin/build.gradle.kts[tags=specify-metadata-version-for-library]
412412
----
413413

414+
=== Including metadata repository files
415+
416+
By default, reachability metadata will be used only when your native image is generated.
417+
In some situations, you may want a copy of the reachability metadata to use directly.
418+
419+
For example, copying the reachability metadata into your jar can be useful when some other process is responsible for converting your jar into a native image.
420+
You might be generating a shaded jar and using a https://paketo.io/[Paketo buildpack] to convert it to a native image.
421+
422+
To download a copy of the metadata into the `build/native-reachability-metadata` directory you can the `collectReachabilityMetadata` task.
423+
Files will be downloaded into `META-INF/native-image/<groupId>/<versionId>` subdirectories.
424+
425+
To include metadata repository inside your jar you can link to the task using the `jar` DSL `from` directive:
426+
427+
.Including metadata repository files
428+
[source, groovy, role="multi-language-sample"]
429+
----
430+
include::../snippets/gradle/groovy/build.gradle[tags=include-metadata]
431+
----
432+
433+
[source, kotlin, role="multi-language-sample"]
434+
----
435+
include::../snippets/gradle/kotlin/build.gradle.kts[tags=include-metadata]
436+
----
437+
438+
For more advanced configurations you can declare a `org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata` task and set the appropriate properties.
439+
414440
[[plugin-configurations]]
415441
== Configurations defined by the plugin
416442

docs/src/docs/asciidoc/maven-plugin.adoc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,23 @@ This may be interesting if there's no specific metadata available for the partic
660660
include::../../../../samples/native-config-integration/pom.xml[tag=metadata-force-version]
661661
----
662662

663+
=== Adding metadata respoistory files
664+
665+
By default, repository metadata will be used only when your native image is generated.
666+
In some situations, you may want to include the metadata directly inside your jar.
667+
668+
Adding metadata to your jar can be useful when some other process is responsible for converting your jar into a native image.
669+
For example, you might be generating a shaded jar and using a https://paketo.io/[Paketo buildpack] to convert it to a native image.
670+
671+
To include metadata repository inside your jar you can use the `add-reachability-metadata` goal.
672+
Typically the goal will be included in an execution step where by default it will be bound to the `generate-resources` phase:
673+
674+
.Configuring the `add-reachability-metadata` goal to execute with the `generate-resources` phase
675+
[source,xml,indent=0]
676+
----
677+
include::../../../../samples/native-config-integration/pom.xml[tag=add-reachability-metadata-execution]
678+
----
679+
663680
[[javadocs]]
664681
== Javadocs
665682

docs/src/docs/snippets/gradle/groovy/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,9 @@ graalvmNative {
207207
}
208208
}
209209
// end::specify-metadata-version-for-library[]
210+
211+
// tag::include-metadata[]
212+
tasks.named("jar") {
213+
from collectReachabilityMetadata
214+
}
215+
// end::include-metadata[]

docs/src/docs/snippets/gradle/kotlin/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,9 @@ graalvmNative {
221221
}
222222
}
223223
// end::specify-metadata-version-for-library[]
224+
225+
// tag::include-metadata[]
226+
tasks.named("jar", Jar) {
227+
from(collectReachabilityMetadata)
228+
}
229+
// end::include-metadata[]

0 commit comments

Comments
 (0)