diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/DirectoryConfiguration.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/DirectoryConfiguration.java index db3f824f0..f463dd72a 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/DirectoryConfiguration.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/DirectoryConfiguration.java @@ -40,15 +40,34 @@ */ package org.graalvm.reachability; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; public class DirectoryConfiguration { + private static final String PROPERTIES = "reachability-metadata.properties"; + + private final String groupId; + + private final String artifactId; + + private final String version; + private final Path directory; private final boolean override; - public DirectoryConfiguration(Path directory, boolean override) { + public DirectoryConfiguration(String groupId, String artifactId, String version, Path directory, boolean override) { + this.groupId = groupId; + this.artifactId = artifactId; + this.version = version; this.directory = directory; this.override = override; } @@ -60,4 +79,49 @@ public Path getDirectory() { public boolean isOverride() { return override; } + + public static void copy(Collection configurations, Path destinationDirectory) throws IOException { + Path nativeImageDestination = destinationDirectory.resolve("META-INF").resolve("native-image"); + for (DirectoryConfiguration configuration : configurations) { + Path target = nativeImageDestination + .resolve(configuration.groupId) + .resolve(configuration.artifactId) + .resolve((configuration.version != null) ? configuration.version : + configuration.getDirectory().getFileName().toString()); + copyFileTree(configuration.directory, target); + writeConfigurationProperties(configuration, target); + } + } + + private static void copyFileTree(Path source, Path target) throws IOException { + Files.walkFileTree(source, new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { + Files.createDirectories(target.resolve(source.relativize(directory))); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (!"index.json".equalsIgnoreCase(file.getFileName().toString())) { + Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } + }); + } + + private static void writeConfigurationProperties(DirectoryConfiguration configuration, Path target) + throws IOException { + StringBuilder content = new StringBuilder(); + if (configuration.isOverride()) { + content.append("override=true\n"); + } + if (content.length() > 0) { + Files.write(target.resolve(PROPERTIES), content.toString().getBytes(StandardCharsets.ISO_8859_1)); + } + } } + + 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 51b04d051..e1d645a75 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 @@ -112,7 +112,7 @@ public Set findConfigurationsFor(Consumer Optional configuration = index.findConfiguration(groupId, artifactId, version); if (!configuration.isPresent() && artifactQuery.isUseLatestVersion()) { logger.log(groupId, artifactId, version, "Configuration directory not found. Trying latest version."); - configuration = index.findLatestConfigurationFor(groupId, artifactId); + configuration = index.findLatestConfigurationFor(groupId, artifactId, version); if (!configuration.isPresent()) { logger.log(groupId, artifactId, version, "Latest version not found!"); } diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java index 5a27bd3d9..0f1edbb89 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndex.java @@ -91,7 +91,13 @@ private Map> parseIndexFile(Path rootPath) { */ @Override public Optional findConfiguration(String groupId, String artifactId, String version) { - return findConfigurationFor(groupId, artifactId, artifact -> artifact.getVersions().contains(version)); + return findConfigurationFor(groupId, artifactId, version, artifact -> artifact.getVersions().contains(version)); + } + + @Override + @Deprecated + public Optional findLatestConfigurationFor(String groupId, String artifactId) { + return findConfigurationFor(groupId, artifactId, null, Artifact::isLatest); } /** @@ -102,11 +108,12 @@ public Optional findConfiguration(String groupId, String * @return a configuration directory, or empty if no configuration directory is available */ @Override - public Optional findLatestConfigurationFor(String groupId, String artifactId) { - return findConfigurationFor(groupId, artifactId, Artifact::isLatest); + public Optional findLatestConfigurationFor(String groupId, String artifactId, String version) { + return findConfigurationFor(groupId, artifactId, version, Artifact::isLatest); } - private Optional findConfigurationFor(String groupId, String artifactId, Predicate predicate) { + private Optional findConfigurationFor(String groupId, String artifactId, String version, + Predicate predicate) { String module = groupId + ":" + artifactId; List artifacts = index.get(module); if (artifacts == null) { @@ -116,7 +123,8 @@ private Optional findConfigurationFor(String groupId, St .filter(artifact -> artifact.getModule().equals(module)) .filter(predicate) .findFirst() - .map(artifact -> new DirectoryConfiguration(moduleRoot.resolve(artifact.getDirectory()), artifact.isOverride())); + .map(artifact -> new DirectoryConfiguration(groupId, artifactId, version, + moduleRoot.resolve(artifact.getDirectory()), artifact.isOverride())); } } diff --git a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java index c93512d1e..6f7d96113 100644 --- a/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java +++ b/common/graalvm-reachability-metadata/src/main/java/org/graalvm/reachability/internal/index/artifacts/VersionToConfigDirectoryIndex.java @@ -60,6 +60,17 @@ public interface VersionToConfigDirectoryIndex { * @param groupId the group ID of the artifact * @param artifactId the artifact ID of the artifact * @return a configuration, or empty if no configuration directory is available + * @deprecated in favor of {@link #findLatestConfigurationFor(String, String, String)} */ + @Deprecated Optional findLatestConfigurationFor(String groupId, String artifactId); + + /** + * Returns the latest configuration for the requested artifact. + * @param groupId the group ID of the artifact + * @param artifactId the artifact ID of the artifact + * @param version the version of the artifact + * @return a configuration, or empty if no configuration directory is available + */ + Optional findLatestConfigurationFor(String groupId, String artifactId, String version); } diff --git a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/DirectoryConfigurationTest.java b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/DirectoryConfigurationTest.java new file mode 100644 index 000000000..2b5c4d0cc --- /dev/null +++ b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/DirectoryConfigurationTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, 2022 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.reachability; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Properties; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class DirectoryConfigurationTest { + + @TempDir + Path temp; + + @Test + void copyCopiesFiles() throws IOException { + Path directory = temp.resolve("source/com.example.group/artifact/123"); + Path target = temp.resolve("target"); + createJsonFiles(directory); + DirectoryConfiguration configuration = new DirectoryConfiguration("com.example.group", "artifact", "123", directory, false); + DirectoryConfiguration.copy(Arrays.asList(configuration), target); + assertTrue(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/reflect-config.json"))); + assertTrue(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/other.json"))); + assertFalse(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/index.json"))); + assertFalse(Files.exists(target.resolve("META-INF/native-image/com.example.group/artifact/123/reachability-metadata.properties"))); + } + + @Test + void copyWhenHasOverrideGeneratesMetadataRepositoryProperties() throws IOException { + Path directory = temp.resolve("source/com.example.group/artifact/123"); + Path target = temp.resolve("target"); + createJsonFiles(directory); + DirectoryConfiguration configuration = new DirectoryConfiguration("com.example.group", "artifact", "123", directory, true); + DirectoryConfiguration.copy(Arrays.asList(configuration), target); + Path propertiesFile = target.resolve("META-INF/native-image/com.example.group/artifact/123/reachability-metadata.properties"); + Properties properties = new Properties(); + properties.load(new FileInputStream(propertiesFile.toFile())); + assertEquals("true", properties.getProperty("override")); + } + + private void createJsonFiles(Path directory) throws IOException { + Files.createDirectories(directory); + byte[] json = "{}".getBytes(); + Files.copy(new ByteArrayInputStream(json), directory.resolve("index.json")); + Files.copy(new ByteArrayInputStream(json), directory.resolve("reflect-config.json")); + Files.copy(new ByteArrayInputStream(json), directory.resolve("other.json")); + } + +} diff --git a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java index 9198f5c33..42a046d46 100644 --- a/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java +++ b/common/graalvm-reachability-metadata/src/test/java/org/graalvm/reachability/internal/index/artifacts/SingleModuleJsonVersionToConfigDirectoryIndexTest.java @@ -88,7 +88,7 @@ void checkIndex() throws URISyntaxException { config = index.findConfiguration("com.foo", "nope", "1.0"); assertFalse(config.isPresent()); - Optional latest = index.findLatestConfigurationFor("com.foo", "bar"); + Optional latest = index.findLatestConfigurationFor("com.foo", "bar", "123"); assertTrue(latest.isPresent()); assertEquals(repoPath.resolve("2.0"), latest.get().getDirectory()); assertTrue(latest.get().isOverride()); diff --git a/docs/src/docs/asciidoc/gradle-plugin.adoc b/docs/src/docs/asciidoc/gradle-plugin.adoc index 53edacef1..2b0f7cfec 100644 --- a/docs/src/docs/asciidoc/gradle-plugin.adoc +++ b/docs/src/docs/asciidoc/gradle-plugin.adoc @@ -411,6 +411,32 @@ include::../snippets/gradle/groovy/build.gradle[tags=specify-metadata-version-fo include::../snippets/gradle/kotlin/build.gradle.kts[tags=specify-metadata-version-for-library] ---- +=== Including metadata repository files + +By default, reachability metadata will be used only when your native image is generated. +In some situations, you may want a copy of the reachability metadata to use directly. + +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. +You might be generating a shaded jar and using a https://paketo.io/[Paketo buildpack] to convert it to a native image. + +To download a copy of the metadata into the `build/native-reachability-metadata` directory you can the `collectReachabilityMetadata` task. +Files will be downloaded into `META-INF/native-image//` subdirectories. + +To include metadata repository inside your jar you can link to the task using the `jar` DSL `from` directive: + +.Including metadata repository files +[source, groovy, role="multi-language-sample"] +---- +include::../snippets/gradle/groovy/build.gradle[tags=include-metadata] +---- + +[source, kotlin, role="multi-language-sample"] +---- +include::../snippets/gradle/kotlin/build.gradle.kts[tags=include-metadata] +---- + +For more advanced configurations you can declare a `org.graalvm.buildtools.gradle.tasks.CollectReachabilityMetadata` task and set the appropriate properties. + [[plugin-configurations]] == Configurations defined by the plugin diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc index 590073c7f..fe4b361be 100644 --- a/docs/src/docs/asciidoc/maven-plugin.adoc +++ b/docs/src/docs/asciidoc/maven-plugin.adoc @@ -660,6 +660,23 @@ This may be interesting if there's no specific metadata available for the partic include::../../../../samples/native-config-integration/pom.xml[tag=metadata-force-version] ---- +=== Adding metadata respoistory files + +By default, repository metadata will be used only when your native image is generated. +In some situations, you may want to include the metadata directly inside your jar. + +Adding metadata to your jar can be useful when some other process is responsible for converting your jar into a native image. +For example, you might be generating a shaded jar and using a https://paketo.io/[Paketo buildpack] to convert it to a native image. + +To include metadata repository inside your jar you can use the `add-reachability-metadata` goal. +Typically the goal will be included in an execution step where by default it will be bound to the `generate-resources` phase: + +.Configuring the `add-reachability-metadata` goal to execute with the `generate-resources` phase +[source,xml,indent=0] +---- +include::../../../../samples/native-config-integration/pom.xml[tag=add-reachability-metadata-execution] +---- + [[javadocs]] == Javadocs diff --git a/docs/src/docs/snippets/gradle/groovy/build.gradle b/docs/src/docs/snippets/gradle/groovy/build.gradle index 009e07f68..1ef7fc997 100644 --- a/docs/src/docs/snippets/gradle/groovy/build.gradle +++ b/docs/src/docs/snippets/gradle/groovy/build.gradle @@ -207,3 +207,9 @@ graalvmNative { } } // end::specify-metadata-version-for-library[] + +// tag::include-metadata[] +tasks.named("jar") { + from collectReachabilityMetadata +} +// end::include-metadata[] diff --git a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts index a51b49fa1..eda5129ea 100644 --- a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts +++ b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts @@ -221,3 +221,9 @@ graalvmNative { } } // end::specify-metadata-version-for-library[] + +// tag::include-metadata[] +tasks.named("jar", Jar) { + from(collectReachabilityMetadata) +} +// end::include-metadata[] diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/ReachabilityMetadataFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/ReachabilityMetadataFunctionalTest.groovy new file mode 100644 index 000000000..a8e76ab44 --- /dev/null +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/ReachabilityMetadataFunctionalTest.groovy @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022, 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 + +import org.graalvm.buildtools.gradle.fixtures.AbstractFunctionalTest +import org.gradle.api.logging.LogLevel +import spock.lang.Unroll + +class ReachabilityMetadataFunctionalTest extends AbstractFunctionalTest { + + def "the application runs when using the official metadata repository"() { + given: + withSample("metadata-repo-integration") + + when: + run 'jar', "-D${NativeImagePlugin.CONFIG_REPO_LOGLEVEL}=${LogLevel.LIFECYCLE}" + + then: + tasks { + succeeded ':jar', ':collectReachabilityMetadata' + } + + and: "has copied metadata file" + file("build/native-reachability-metadata/META-INF/native-image/com.h2database/h2/2.1.210/resource-config.json").text.trim() == '''{ + "bundles": [], + "resources": { + "includes": [ + { + "condition": { + "typeReachable": "org.h2.util.Utils" + }, + "pattern": "\\\\Qorg/h2/util/data.zip\\\\E" + } + ] + } +}''' + } + +} 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 27a6a9f4a..4e5c07a55 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 @@ -59,6 +59,7 @@ import org.graalvm.buildtools.gradle.internal.NativeConfigurations; 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.GenerateResourcesConfigFile; import org.graalvm.buildtools.gradle.tasks.MetadataCopyTask; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; @@ -76,6 +77,7 @@ import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.file.ArchiveOperations; @@ -99,6 +101,7 @@ import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.TaskCollection; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.AbstractArchiveTask; @@ -126,10 +129,13 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collector; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.graalvm.buildtools.gradle.internal.ConfigurationCacheSupport.serializablePredicateOf; import static org.graalvm.buildtools.gradle.internal.ConfigurationCacheSupport.serializableSupplierOf; @@ -271,6 +277,26 @@ private void configureJavaProject(Project project, Provider task.getMergeWithExisting().set(graalExtension.getAgent().getMetadataCopy().getMergeWithExisting()); task.getToolchainDetection().set(graalExtension.getToolchainDetection()); }); + + project.getTasks().register("collectReachabilityMetadata", CollectReachabilityMetadata.class, task -> { + task.setGroup(LifecycleBasePlugin.BUILD_GROUP); + task.setDescription("Obtains native reachability metdata for the runtime classpath configuration"); + }); + + GraalVMReachabilityMetadataRepositoryExtension metadataRepositoryExtension = reachabilityExtensionOn(graalExtension); + TaskCollection reachabilityMetadataCopyTasks = project.getTasks() + .withType(CollectReachabilityMetadata.class); + reachabilityMetadataCopyTasks.configureEach(task -> { + Provider reachabilityMetadataService = graalVMReachabilityMetadataService( + project, metadataRepositoryExtension); + task.getMetadataService().set(reachabilityMetadataService); + task.usesService(reachabilityMetadataService); + task.getUri().convention(task.getVersion().map(this::getReachabilityMetadataRepositoryUrlForVersion) + .orElse(metadataRepositoryExtension.getUri())); + task.getExcludedModules().convention(metadataRepositoryExtension.getExcludedModules()); + task.getModuleToConfigVersion().convention(metadataRepositoryExtension.getModuleToConfigVersion()); + task.getInto().convention(project.getLayout().getBuildDirectory().dir("native-reachability-metadata")); + }); } private void configureAutomaticTaskCreation(Project project, @@ -318,83 +344,65 @@ private void configureAutomaticTaskCreation(Project project, }); } - private void configureJvmReachabilityConfigurationDirectories(Project project, GraalVMExtension graalExtension, NativeImageOptions options, SourceSet sourceSet) { - GraalVMReachabilityMetadataRepositoryExtension repositoryExtension = reachabilityExtensionOn(graalExtension); - Provider serviceProvider = project.getGradle() - .getSharedServices() - .registerIfAbsent("nativeConfigurationService", GraalVMReachabilityMetadataService.class, spec -> { - LogLevel logLevel = determineLogLevel(); - spec.getParameters().getLogLevel().set(logLevel); - spec.getParameters().getUri().set(repositoryExtension.getUri()); - spec.getParameters().getCacheDir().set(new File(project.getGradle().getGradleUserHomeDir(), "native-build-tools/repositories")); - }); - options.getConfigurationFileDirectories().from(repositoryExtension.getEnabled().flatMap(enabled -> { - if (enabled) { - if (repositoryExtension.getUri().isPresent()) { - Configuration classpath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); - Set excludedModules = repositoryExtension.getExcludedModules().getOrElse(Collections.emptySet()); - Map forcedVersions = repositoryExtension.getModuleToConfigVersion().getOrElse(Collections.emptyMap()); - return serviceProvider.map(repo -> repo.findConfigurationsFor(query -> classpath.getIncoming().getResolutionResult().allComponents(component -> { + private void configureJvmReachabilityConfigurationDirectories(Project project, GraalVMExtension graalExtension, + NativeImageOptions options, SourceSet sourceSet) { + options.getConfigurationFileDirectories().from(graalVMReachabilityQuery(project, graalExtension, sourceSet, + configuration -> true, this::getConfigurationDirectory, + Collectors.toList())); + } + + private File getConfigurationDirectory(ModuleVersionIdentifier moduleVersion, + DirectoryConfiguration configuration) { + return configuration.getDirectory().toAbsolutePath().toFile(); + } + + private void configureJvmReachabilityExcludeConfigArgs(Project project, GraalVMExtension graalExtension, + NativeImageOptions options, SourceSet sourceSet) { + options.getExcludeConfig().putAll(graalVMReachabilityQuery(project, graalExtension, sourceSet, + DirectoryConfiguration::isOverride, this::getExclusionConfig, + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + private Map.Entry> getExclusionConfig(ModuleVersionIdentifier moduleVersion, + DirectoryConfiguration configuration) { + String gav = moduleVersion.getGroup() + ":" + moduleVersion.getName() + ":" + moduleVersion.getVersion(); + return new AbstractMap.SimpleEntry<>(gav, Arrays.asList("^/META-INF/native-image/.*")); + } + + private Provider graalVMReachabilityQuery(Project project, GraalVMExtension graalExtension, + SourceSet sourceSet, Predicate filter, + BiFunction mapper, Collector collector) { + GraalVMReachabilityMetadataRepositoryExtension extension = reachabilityExtensionOn(graalExtension); + return extension.getEnabled().flatMap(enabled -> { + if (enabled && extension.getUri().isPresent()) { + Configuration classpath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); + Set excludedModules = extension.getExcludedModules().getOrElse(Collections.emptySet()); + Map forcedVersions = extension.getModuleToConfigVersion().getOrElse(Collections.emptyMap()); + return graalVMReachabilityMetadataService(project, extension).map(service -> { + Set components = classpath.getIncoming().getResolutionResult().getAllComponents(); + Stream mapped = components.stream().flatMap(component -> { ModuleVersionIdentifier moduleVersion = component.getModuleVersion(); - String module = Objects.requireNonNull(moduleVersion).getGroup() + ":" + moduleVersion.getName(); - if (!excludedModules.contains(module)) { - query.forArtifact(artifact -> { - artifact.gav(module + ":" + moduleVersion.getVersion()); - if (forcedVersions.containsKey(module)) { - artifact.forceConfigVersion(forcedVersions.get(module)); - } - }); - } - query.useLatestConfigWhenVersionIsUntested(); - })).stream() - .map(configuration -> configuration.getDirectory().toAbsolutePath()) - .map(Path::toFile) - .collect(Collectors.toList())); - } + Set configurations = service.findConfigurationsFor(excludedModules, forcedVersions, moduleVersion); + return configurations.stream().filter(filter).map(configuration -> mapper.apply(moduleVersion, configuration)); + }); + return mapped.collect(collector); + }); } - return project.getProviders().provider(Collections::emptySet); - })); + return project.getProviders().provider(() -> Stream.empty().collect(collector)); + }); } - private void configureJvmReachabilityExcludeConfigArgs(Project project, GraalVMExtension graalExtension, NativeImageOptions options, SourceSet sourceSet) { - GraalVMReachabilityMetadataRepositoryExtension repositoryExtension = reachabilityExtensionOn(graalExtension); - Provider serviceProvider = project.getGradle() + private Provider graalVMReachabilityMetadataService(Project project, + GraalVMReachabilityMetadataRepositoryExtension repositoryExtension) { + return project.getGradle() .getSharedServices() .registerIfAbsent("nativeConfigurationService", GraalVMReachabilityMetadataService.class, spec -> { LogLevel logLevel = determineLogLevel(); spec.getParameters().getLogLevel().set(logLevel); spec.getParameters().getUri().set(repositoryExtension.getUri()); - spec.getParameters().getCacheDir().set(new File(project.getGradle().getGradleUserHomeDir(), "native-build-tools/repositories")); + spec.getParameters().getCacheDir().set( + new File(project.getGradle().getGradleUserHomeDir(), "native-build-tools/repositories")); }); - options.getExcludeConfig().putAll(repositoryExtension.getEnabled().flatMap(enabled -> { - if (enabled) { - if (repositoryExtension.getUri().isPresent()) { - Configuration classpath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); - Set excludedModules = repositoryExtension.getExcludedModules().getOrElse(Collections.emptySet()); - Map forcedVersions = repositoryExtension.getModuleToConfigVersion().getOrElse(Collections.emptyMap()); - return serviceProvider.map(repo -> classpath.getIncoming().getResolutionResult().getAllComponents().stream().flatMap(component -> { - ModuleVersionIdentifier moduleVersion = component.getModuleVersion(); - return repo.findConfigurationsFor(query -> { - String module = Objects.requireNonNull(moduleVersion).getGroup() + ":" + moduleVersion.getName(); - if (!excludedModules.contains(module)) { - query.forArtifact(artifact -> { - artifact.gav(module + ":" + moduleVersion.getVersion()); - if (forcedVersions.containsKey(module)) { - artifact.forceConfigVersion(forcedVersions.get(module)); - } - }); - } - query.useLatestConfigWhenVersionIsUntested(); - }).stream() - .filter(DirectoryConfiguration::isOverride) - .map(configuration -> new AbstractMap.SimpleEntry<>( - moduleVersion.getGroup() + ":" + moduleVersion.getName() + ":" + moduleVersion.getVersion(), - Arrays.asList("^/META-INF/native-image/.*"))); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - } - } - return project.getProviders().provider(Collections::emptyMap); - })); } private static LogLevel determineLogLevel() { @@ -475,17 +483,19 @@ private void configureNativeConfigurationRepo(ExtensionAware graalvmNative) { GraalVMReachabilityMetadataRepositoryExtension configurationRepository = graalvmNative.getExtensions().create("metadataRepository", GraalVMReachabilityMetadataRepositoryExtension.class); configurationRepository.getEnabled().convention(false); configurationRepository.getVersion().convention(SharedConstants.METADATA_REPO_DEFAULT_VERSION); - configurationRepository.getUri().convention(configurationRepository.getVersion().map(v -> { - try { - return new URI(String.format(METADATA_REPO_URL_TEMPLATE, v)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - })); + configurationRepository.getUri().convention(configurationRepository.getVersion().map(this::getReachabilityMetadataRepositoryUrlForVersion)); configurationRepository.getExcludedModules().convention(Collections.emptySet()); configurationRepository.getModuleToConfigVersion().convention(Collections.emptyMap()); } + private URI getReachabilityMetadataRepositoryUrlForVersion(String version) { + try { + return new URI(String.format(METADATA_REPO_URL_TEMPLATE, version)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + private TaskProvider registerResourcesConfigTask(Provider generatedDir, NativeImageOptions options, TaskContainer tasks, 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 9a9d77d66..e648712b8 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 @@ -45,6 +45,7 @@ import org.graalvm.reachability.GraalVMReachabilityMetadataRepository; import org.graalvm.reachability.Query; import org.graalvm.reachability.internal.FileSystemRepository; +import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileSystemOperations; @@ -66,6 +67,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; @@ -208,4 +211,20 @@ public Set findConfigurationsFor(String gavCoordinates) public Set findConfigurationsFor(Collection modules) { return repository.findConfigurationsFor(modules); } + + public Set findConfigurationsFor(Set excludedModules, Map forcedVersions, ModuleVersionIdentifier moduleVersion) { + Objects.requireNonNull(moduleVersion); + String groupAndArtifact = moduleVersion.getGroup() + ":" + moduleVersion.getName(); + return findConfigurationsFor(query -> { + if (!excludedModules.contains(groupAndArtifact)) { + query.forArtifact(artifact -> { + artifact.gav(groupAndArtifact + ":" + moduleVersion.getVersion()); + if (forcedVersions.containsKey(groupAndArtifact)) { + artifact.forceConfigVersion(forcedVersions.get(groupAndArtifact)); + } + }); + } + query.useLatestConfigWhenVersionIsUntested(); + }); + } } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/CollectReachabilityMetadata.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/CollectReachabilityMetadata.java new file mode 100644 index 000000000..1a251ff64 --- /dev/null +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/CollectReachabilityMetadata.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2022, 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 java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.graalvm.buildtools.gradle.internal.GraalVMReachabilityMetadataService; +import org.graalvm.reachability.DirectoryConfiguration; +import org.gradle.api.DefaultTask; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.result.ResolvedComponentResult; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +public abstract class CollectReachabilityMetadata extends DefaultTask { + + private Configuration classpath; + + /** + * The classpath for which the metadata should be copied. + * @return the classpath to use + */ + @Classpath + @Optional + public Configuration getClasspath() { + return classpath; + } + + public void setClasspath(Configuration classpath) { + this.classpath = classpath; + } + + @Internal + public abstract Property getMetadataService(); + + /** + * A URI pointing to a GraalVM reachability metadata repository. This must + * either be a local file or a remote URI. In case of remote + * files, only zip or tarballs are supported. + * @return the uri property + */ + @Input + @Optional + public abstract Property getUri(); + + /** + * An optional version of the remote repository: if specified, + * and that no URI is provided, it will automatically use a + * published repository from the official GraalVM reachability + * metadata repository. + * + * @return the version of the repository to use + */ + @Input + @Optional + public abstract Property getVersion(); + + /** + * The set of modules for which we don't want to use the + * configuration found in the repository. Modules must be + * declared with the `groupId:artifactId` syntax. + * + * @return the set of excluded modules + */ + @Input + @Optional + public abstract SetProperty getExcludedModules(); + + /** + * A map from a module (org.group:artifact) to configuration + * repository config version. + * + * @return the map of modules to forced configuration versions + */ + @Input + @Optional + public abstract MapProperty getModuleToConfigVersion(); + + @OutputDirectory + @Optional + public abstract DirectoryProperty getInto(); + + @TaskAction + void copyReachabilityMetadata() throws IOException { + GraalVMReachabilityMetadataService service = getMetadataService().get(); + Configuration classpath = (this.classpath != null) ? this.classpath + : getProject().getConfigurations().getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + Objects.requireNonNull(classpath); + Set excludedModules = getExcludedModules().getOrElse(Collections.emptySet()); + Map forcedVersions = getModuleToConfigVersion().getOrElse(Collections.emptyMap()); + for (ResolvedComponentResult component : classpath.getIncoming().getResolutionResult().getAllComponents()) { + ModuleVersionIdentifier moduleVersion = component.getModuleVersion(); + Set configurations = service.findConfigurationsFor(excludedModules, forcedVersions, moduleVersion); + DirectoryConfiguration.copy(configurations, getInto().get().getAsFile().toPath()); + } + } + +} diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/MetadataRepositoryFunctionalTest.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/MetadataRepositoryFunctionalTest.groovy index 6d387b4b4..77677cb24 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/MetadataRepositoryFunctionalTest.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/MetadataRepositoryFunctionalTest.groovy @@ -175,4 +175,23 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes outputContains "Failed to download from https://httpstat.us/404: 404 Not Found" } + void "it can include hints in jar"() { + given: + withSample("native-config-integration") + + when: + mvn '-X', '-PaddMetadataHints', '-DskipTests', 'package' + + then: + buildSucceeded + + and: + file("target/classes/META-INF/native-image/org.graalvm.internal/library-with-reflection/1.5/reflect-config.json").text.trim() == '''[ + { + "name": "org.graalvm.internal.reflect.Message", + "allDeclaredFields": true, + "allDeclaredMethods": true + } +]''' + } } 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 new file mode 100644 index 000000000..1497740f6 --- /dev/null +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2020, 2022, 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 java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.toolchain.ToolchainManager; +import org.graalvm.buildtools.Utils; +import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration; +import org.graalvm.buildtools.utils.NativeImageUtils; +import org.graalvm.buildtools.utils.SharedConstants; + +/** + * @author Sebastien Deleuze + */ +public abstract class AbstractNativeImageMojo extends AbstractNativeMojo { + protected static final String NATIVE_IMAGE_META_INF = "META-INF/native-image"; + protected static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties"; + protected static final String NATIVE_IMAGE_DRY_RUN = "nativeDryRun"; + + @Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only + protected PluginDescriptor plugin; + + @Parameter(defaultValue = "${session}", readonly = true) + protected MavenSession session; + + @Parameter(defaultValue = "${mojoExecution}") + protected MojoExecution mojoExecution; + + @Parameter(property = "plugin.artifacts", required = true, readonly = true) + protected List pluginArtifacts; + + @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) + protected File outputDirectory; + + @Parameter(property = "mainClass") + protected String mainClass; + + @Parameter(property = "imageName", defaultValue = "${project.artifactId}") + protected String imageName; + + @Parameter(property = "classpath") + protected List classpath; + + @Parameter(property = "classesDirectory") + protected File classesDirectory; + + @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) + protected File defaultClassesDirectory; + + protected final List imageClasspath; + + @Parameter(property = "debug", defaultValue = "false") + protected boolean debug; + + @Parameter(property = "fallback", defaultValue = "false") + protected boolean fallback; + + @Parameter(property = "verbose", defaultValue = "false") + protected boolean verbose; + + @Parameter(property = "sharedLibrary", defaultValue = "false") + protected boolean sharedLibrary; + + @Parameter(property = "quickBuild", defaultValue = "false") + protected boolean quickBuild; + + @Parameter(property = "useArgFile") + protected Boolean useArgFile; + + @Parameter(property = "buildArgs") + protected List buildArgs; + + @Parameter(defaultValue = "${project.build.directory}/native/generated", property = "resourcesConfigDirectory", required = true) + protected File resourcesConfigDirectory; + + @Parameter(property = "agentResourceDirectory") + protected File agentResourceDirectory; + + @Parameter(property = "excludeConfig") + protected List excludeConfig; + + @Parameter(property = "environmentVariables") + protected Map environment; + + @Parameter(property = "systemPropertyVariables") + protected Map systemProperties; + + @Parameter(property = "configurationFileDirectories") + protected List configFiles; + + @Parameter(property = "jvmArgs") + protected List jvmArgs; + + @Parameter(property = NATIVE_IMAGE_DRY_RUN, defaultValue = "false") + protected boolean dryRun; + + @Component + protected ToolchainManager toolchainManager; + + @Inject + protected AbstractNativeImageMojo() { + imageClasspath = new ArrayList<>(); + useArgFile = SharedConstants.IS_WINDOWS; + } + + protected List getBuildArgs() throws MojoExecutionException { + final List cliArgs = new ArrayList<>(); + + if (excludeConfig != null) { + excludeConfig.forEach(entry -> { + cliArgs.add("--exclude-config"); + cliArgs.add(entry.getJarPath()); + cliArgs.add(String.format("\"%s\"", entry.getResourcePattern())); + }); + } + + cliArgs.add("-cp"); + cliArgs.add(getClasspath()); + + if (debug) { + cliArgs.add("-g"); + } + if (!fallback) { + cliArgs.add("--no-fallback"); + } + if (verbose) { + cliArgs.add("--verbose"); + } + if (sharedLibrary) { + cliArgs.add("--shared"); + } + + // Let's allow user to specify environment option to toggle quick build. + String quickBuildEnv = System.getenv("GRAALVM_QUICK_BUILD"); + if (quickBuildEnv != null) { + logger.warn("Quick build environment variable is set."); + quickBuild = quickBuildEnv.isEmpty() || Boolean.parseBoolean(quickBuildEnv); + } + + if (quickBuild) { + cliArgs.add("-Ob"); + } + + cliArgs.add("-H:Path=" + outputDirectory.toPath().toAbsolutePath()); + cliArgs.add("-H:Name=" + imageName); + + if (systemProperties != null) { + for (Map.Entry entry : systemProperties.entrySet()) { + cliArgs.add("-D" + entry.getKey() + "=" + entry.getValue()); + } + } + + if (jvmArgs != null) { + jvmArgs.forEach(jvmArg -> cliArgs.add("-J" + jvmArg)); + } + + maybeAddGeneratedResourcesConfig(buildArgs); + maybeAddReachabilityMetadata(configFiles); + + if (configFiles != null && !configFiles.isEmpty()) { + cliArgs.add("-H:ConfigurationFileDirectories=" + + configFiles.stream() + .map(Paths::get) + .map(Path::toAbsolutePath) + .map(Path::toString) + .collect(Collectors.joining(",")) + ); + } + + if (mainClass != null && !mainClass.equals(".")) { + cliArgs.add("-H:Class=" + mainClass); + } + + if (buildArgs != null && !buildArgs.isEmpty()) { + for (String buildArg : buildArgs) { + cliArgs.addAll(Arrays.asList(buildArg.split("\\s+"))); + } + } + + if (useArgFile) { + Path tmpDir = Paths.get("target", "tmp"); + return NativeImageUtils.convertToArgsFile(cliArgs, tmpDir); + } + return Collections.unmodifiableList(cliArgs); + } + + protected Path processSupportedArtifacts(Artifact artifact) throws MojoExecutionException { + return processArtifact(artifact, "jar", "test-jar", "war"); + } + + protected Path processArtifact(Artifact artifact, String... artifactTypes) throws MojoExecutionException { + File artifactFile = artifact.getFile(); + + if (artifactFile == null) { + logger.debug("Missing artifact file for artifact " + artifact + " (type: " + artifact.getType() + ")"); + return null; + } + + if (Arrays.stream(artifactTypes).noneMatch(a -> a.equals(artifact.getType()))) { + logger.warn("Ignoring ImageClasspath Entry '" + artifact + "' with unsupported type '" + artifact.getType() + "'"); + return null; + } + if (!artifactFile.exists()) { + throw new MojoExecutionException("Missing jar-file for " + artifact + ". " + + "Ensure that " + plugin.getArtifactId() + " runs in package phase."); + } + + Path jarFilePath = artifactFile.toPath(); + logger.debug("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")"); + + warnIfWrongMetaInfLayout(jarFilePath, artifact); + return jarFilePath; + } + + protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionException { + Optional.ofNullable(processSupportedArtifacts(artifact)).ifPresent(imageClasspath::add); + } + + protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException { + if (jarFilePath.toFile().isDirectory()) { + logger.debug("Artifact `" + jarFilePath + "` is a directory."); + return; + } + URI jarFileURI = URI.create("jar:" + jarFilePath.toUri()); + try (FileSystem jarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap())) { + Path nativeImageMetaInfBase = jarFS.getPath("/" + NATIVE_IMAGE_META_INF); + if (Files.isDirectory(nativeImageMetaInfBase)) { + try (Stream stream = Files.walk(nativeImageMetaInfBase)) { + List nativeImageProperties = stream + .filter(p -> p.endsWith(NATIVE_IMAGE_PROPERTIES_FILENAME)).collect(Collectors.toList()); + for (Path nativeImageProperty : nativeImageProperties) { + Path relativeSubDir = nativeImageMetaInfBase.relativize(nativeImageProperty).getParent(); + boolean valid = relativeSubDir != null && (relativeSubDir.getNameCount() == 2); + valid = valid && relativeSubDir.getName(0).toString().equals(artifact.getGroupId()); + valid = valid && relativeSubDir.getName(1).toString().equals(artifact.getArtifactId()); + if (!valid) { + String example = NATIVE_IMAGE_META_INF + "/%s/%s/" + NATIVE_IMAGE_PROPERTIES_FILENAME; + example = String.format(example, artifact.getGroupId(), artifact.getArtifactId()); + logger.warn("Properties file at '" + nativeImageProperty.toUri() + "' does not match the recommended '" + example + "' layout."); + } + } + } + } + } catch (IOException e) { + throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath", e); + } + } + + protected abstract List getDependencyScopes(); + + protected void addDependenciesToClasspath() throws MojoExecutionException { + configureMetadataRepository(); + for (Artifact dependency : project.getArtifacts().stream() + .filter(artifact -> getDependencyScopes().contains(artifact.getScope())) + .collect(Collectors.toSet())) { + addArtifactToClasspath(dependency); + maybeAddDependencyMetadata(dependency, file -> { + buildArgs.add("--exclude-config"); + buildArgs.add(Pattern.quote(dependency.getFile().getAbsolutePath())); + buildArgs.add("^/META-INF/native-image/"); + }); + } + } + + /** + * Returns path to where application classes are stored, or jar artifact if it is produced. + * @return Path to application classes + * @throws MojoExecutionException failed getting main build path + */ + protected Path getMainBuildPath() throws MojoExecutionException { + if (classesDirectory != null) { + return classesDirectory.toPath(); + } else { + Path artifactPath = processArtifact(project.getArtifact(), project.getPackaging()); + if (artifactPath != null) { + return artifactPath; + } else { + return defaultClassesDirectory.toPath(); + } + } + } + + protected void populateApplicationClasspath() throws MojoExecutionException { + imageClasspath.add(getMainBuildPath()); + } + + protected void populateClasspath() throws MojoExecutionException { + if (classpath != null && !classpath.isEmpty()) { + imageClasspath.addAll(classpath.stream() + .map(Paths::get) + .map(Path::toAbsolutePath) + .collect(Collectors.toSet()) + ); + } else { + populateApplicationClasspath(); + addDependenciesToClasspath(); + } + imageClasspath.removeIf(entry -> !entry.toFile().exists()); + } + + protected String getClasspath() throws MojoExecutionException { + populateClasspath(); + if (imageClasspath.isEmpty()) { + throw new MojoExecutionException("Image classpath is empty. " + + "Check if your classpath configuration is correct."); + } + return imageClasspath.stream() + .map(Path::toString) + .collect(Collectors.joining(File.pathSeparator)); + } + + protected void buildImage() throws MojoExecutionException { + Path nativeImageExecutable = Utils.getNativeImage(logger); + + try { + ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); + processBuilder.command().addAll(getBuildArgs()); + + if (environment != null) { + processBuilder.environment().putAll(environment); + } + + if (!outputDirectory.exists() && !outputDirectory.mkdirs()) { + throw new MojoExecutionException("Failed creating output directory"); + } + processBuilder.inheritIO(); + + String commandString = String.join(" ", processBuilder.command()); + logger.info("Executing: " + commandString); + + if (dryRun) { + logger.warn("Skipped native-image building due to `" + NATIVE_IMAGE_DRY_RUN + "` being specified."); + return; + } + + Process imageBuildProcess = processBuilder.start(); + if (imageBuildProcess.waitFor() != 0) { + throw new MojoExecutionException("Execution of " + commandString + " returned non-zero result"); + } + } catch (IOException | InterruptedException e) { + throw new MojoExecutionException("Building image with " + nativeImageExecutable + " failed", e); + } + } + + protected void maybeAddGeneratedResourcesConfig(List into) { + if (resourcesConfigDirectory.exists() || agentResourceDirectory != null) { + File[] dirs = resourcesConfigDirectory.listFiles(); + Stream configDirs = + Stream.concat(dirs == null ? Stream.empty() : Arrays.stream(dirs), + agentResourceDirectory == null ? Stream.empty() : Stream.of(agentResourceDirectory).filter(File::isDirectory)); + + String value = configDirs.map(File::getAbsolutePath).collect(Collectors.joining(",")); + if (!value.isEmpty()) { + into.add("-H:ConfigurationFileDirectories=" + value); + if (agentResourceDirectory != null && agentResourceDirectory.isDirectory()) { + // The generated reflect config file contains references to java.* + // and org.apache.maven.surefire that we'd need to remove using + // a proper JSON parser/writer instead + into.add("-H:+AllowIncompleteClasspath"); + } + } + } + } + + + + protected void maybeAddReachabilityMetadata(List configDirs) { + if (isMetadataRepositoryEnabled() && !metadataRepositoryConfigurations.isEmpty()) { + metadataRepositoryConfigurations.stream() + .map(configuration -> configuration.getDirectory().toAbsolutePath()) + .map(Path::toFile) + .map(File::getAbsolutePath) + .forEach(configDirs::add); + } + } +} diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java index 3bb5acac4..852d1ea29 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeMojo.java @@ -41,416 +41,61 @@ package org.graalvm.buildtools.maven; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.execution.MavenSession; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecution; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.descriptor.PluginDescriptor; -import org.apache.maven.plugins.annotations.Component; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.toolchain.ToolchainManager; -import org.codehaus.plexus.logging.Logger; -import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.maven.config.ExcludeConfigConfiguration; -import org.graalvm.buildtools.maven.config.MetadataRepositoryConfiguration; -import org.graalvm.buildtools.utils.FileUtils; -import org.graalvm.buildtools.utils.NativeImageUtils; -import org.graalvm.buildtools.utils.SharedConstants; -import org.graalvm.reachability.DirectoryConfiguration; -import org.graalvm.reachability.GraalVMReachabilityMetadataRepository; -import org.graalvm.reachability.internal.FileSystemRepository; +import static org.graalvm.buildtools.utils.SharedConstants.METADATA_REPO_URL_TEMPLATE; -import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; -import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static org.graalvm.buildtools.utils.SharedConstants.METADATA_REPO_URL_TEMPLATE; +import javax.inject.Inject; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; +import org.graalvm.buildtools.maven.config.MetadataRepositoryConfiguration; +import org.graalvm.buildtools.utils.FileUtils; +import org.graalvm.buildtools.utils.SharedConstants; +import org.graalvm.reachability.DirectoryConfiguration; +import org.graalvm.reachability.GraalVMReachabilityMetadataRepository; +import org.graalvm.reachability.internal.FileSystemRepository; /** * @author Sebastien Deleuze */ - public abstract class AbstractNativeMojo extends AbstractMojo { - protected static final String NATIVE_IMAGE_META_INF = "META-INF/native-image"; - protected static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties"; - protected static final String NATIVE_IMAGE_DRY_RUN = "nativeDryRun"; - - @Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only - protected PluginDescriptor plugin; - - @Parameter(defaultValue = "${session}", readonly = true) - protected MavenSession session; @Parameter(defaultValue = "${project}", readonly = true, required = true) protected MavenProject project; - @Parameter(defaultValue = "${mojoExecution}") - protected MojoExecution mojoExecution; - - @Parameter(property = "plugin.artifacts", required = true, readonly = true) - protected List pluginArtifacts; - - @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true) - protected File outputDirectory; - - @Parameter(property = "mainClass") - protected String mainClass; - - @Parameter(property = "imageName", defaultValue = "${project.artifactId}") - protected String imageName; - - @Parameter(property = "classpath") - protected List classpath; - - @Parameter(property = "classesDirectory") - protected File classesDirectory; - - @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) - protected File defaultClassesDirectory; - - protected final List imageClasspath; - - protected final Set metadataRepositoryConfigurations; - - @Parameter(property = "debug", defaultValue = "false") - protected boolean debug; - - @Parameter(property = "fallback", defaultValue = "false") - protected boolean fallback; - - @Parameter(property = "verbose", defaultValue = "false") - protected boolean verbose; - - @Parameter(property = "sharedLibrary", defaultValue = "false") - protected boolean sharedLibrary; - - @Parameter(property = "quickBuild", defaultValue = "false") - protected boolean quickBuild; - - @Parameter(property = "useArgFile") - protected Boolean useArgFile; - - @Parameter(property = "buildArgs") - protected List buildArgs; - - @Parameter(defaultValue = "${project.build.directory}/native/generated", property = "resourcesConfigDirectory", required = true) - protected File resourcesConfigDirectory; - - @Parameter(property = "agentResourceDirectory") - protected File agentResourceDirectory; - - @Parameter(property = "excludeConfig") - protected List excludeConfig; - - @Parameter(property = "environmentVariables") - protected Map environment; - - @Parameter(property = "systemPropertyVariables") - protected Map systemProperties; - - @Parameter(property = "configurationFileDirectories") - protected List configFiles; - - @Parameter(property = "jvmArgs") - protected List jvmArgs; + @Parameter(defaultValue = "${project.build.directory}/graalvm-reachability-metadata", required = true) + protected File reachabilityMetadataOutputDirectory; @Parameter(alias = "metadataRepository") protected MetadataRepositoryConfiguration metadataRepositoryConfiguration; - @Parameter(property = NATIVE_IMAGE_DRY_RUN, defaultValue = "false") - protected boolean dryRun; + protected final Set metadataRepositoryConfigurations; protected GraalVMReachabilityMetadataRepository metadataRepository; @Component protected Logger logger; - @Component - protected ToolchainManager toolchainManager; - @Inject protected AbstractNativeMojo() { - imageClasspath = new ArrayList<>(); metadataRepositoryConfigurations = new HashSet<>(); - useArgFile = SharedConstants.IS_WINDOWS; - } - - protected List getBuildArgs() throws MojoExecutionException { - final List cliArgs = new ArrayList<>(); - - if (excludeConfig != null) { - excludeConfig.forEach(entry -> { - cliArgs.add("--exclude-config"); - cliArgs.add(entry.getJarPath()); - cliArgs.add(String.format("\"%s\"", entry.getResourcePattern())); - }); - } - - cliArgs.add("-cp"); - cliArgs.add(getClasspath()); - - if (debug) { - cliArgs.add("-g"); - } - if (!fallback) { - cliArgs.add("--no-fallback"); - } - if (verbose) { - cliArgs.add("--verbose"); - } - if (sharedLibrary) { - cliArgs.add("--shared"); - } - - // Let's allow user to specify environment option to toggle quick build. - String quickBuildEnv = System.getenv("GRAALVM_QUICK_BUILD"); - if (quickBuildEnv != null) { - logger.warn("Quick build environment variable is set."); - quickBuild = quickBuildEnv.isEmpty() || Boolean.parseBoolean(quickBuildEnv); - } - - if (quickBuild) { - cliArgs.add("-Ob"); - } - - cliArgs.add("-H:Path=" + outputDirectory.toPath().toAbsolutePath()); - cliArgs.add("-H:Name=" + imageName); - - if (systemProperties != null) { - for (Map.Entry entry : systemProperties.entrySet()) { - cliArgs.add("-D" + entry.getKey() + "=" + entry.getValue()); - } - } - - if (jvmArgs != null) { - jvmArgs.forEach(jvmArg -> cliArgs.add("-J" + jvmArg)); - } - - maybeAddGeneratedResourcesConfig(buildArgs); - maybeAddReachabilityMetadata(configFiles); - - if (configFiles != null && !configFiles.isEmpty()) { - cliArgs.add("-H:ConfigurationFileDirectories=" + - configFiles.stream() - .map(Paths::get) - .map(Path::toAbsolutePath) - .map(Path::toString) - .collect(Collectors.joining(",")) - ); - } - - if (mainClass != null && !mainClass.equals(".")) { - cliArgs.add("-H:Class=" + mainClass); - } - - if (buildArgs != null && !buildArgs.isEmpty()) { - for (String buildArg : buildArgs) { - cliArgs.addAll(Arrays.asList(buildArg.split("\\s+"))); - } - } - - if (useArgFile) { - Path tmpDir = Paths.get("target", "tmp"); - return NativeImageUtils.convertToArgsFile(cliArgs, tmpDir); - } - return Collections.unmodifiableList(cliArgs); - } - - protected Path processSupportedArtifacts(Artifact artifact) throws MojoExecutionException { - return processArtifact(artifact, "jar", "test-jar", "war"); - } - - protected Path processArtifact(Artifact artifact, String... artifactTypes) throws MojoExecutionException { - File artifactFile = artifact.getFile(); - - if (artifactFile == null) { - logger.debug("Missing artifact file for artifact " + artifact + " (type: " + artifact.getType() + ")"); - return null; - } - - if (Arrays.stream(artifactTypes).noneMatch(a -> a.equals(artifact.getType()))) { - logger.warn("Ignoring ImageClasspath Entry '" + artifact + "' with unsupported type '" + artifact.getType() + "'"); - return null; - } - if (!artifactFile.exists()) { - throw new MojoExecutionException("Missing jar-file for " + artifact + ". " + - "Ensure that " + plugin.getArtifactId() + " runs in package phase."); - } - - Path jarFilePath = artifactFile.toPath(); - logger.debug("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")"); - - warnIfWrongMetaInfLayout(jarFilePath, artifact); - return jarFilePath; - } - - protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionException { - Optional.ofNullable(processSupportedArtifacts(artifact)).ifPresent(imageClasspath::add); - } - - protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException { - if (jarFilePath.toFile().isDirectory()) { - logger.debug("Artifact `" + jarFilePath + "` is a directory."); - return; - } - URI jarFileURI = URI.create("jar:" + jarFilePath.toUri()); - try (FileSystem jarFS = FileSystems.newFileSystem(jarFileURI, Collections.emptyMap())) { - Path nativeImageMetaInfBase = jarFS.getPath("/" + NATIVE_IMAGE_META_INF); - if (Files.isDirectory(nativeImageMetaInfBase)) { - try (Stream stream = Files.walk(nativeImageMetaInfBase)) { - List nativeImageProperties = stream - .filter(p -> p.endsWith(NATIVE_IMAGE_PROPERTIES_FILENAME)).collect(Collectors.toList()); - for (Path nativeImageProperty : nativeImageProperties) { - Path relativeSubDir = nativeImageMetaInfBase.relativize(nativeImageProperty).getParent(); - boolean valid = relativeSubDir != null && (relativeSubDir.getNameCount() == 2); - valid = valid && relativeSubDir.getName(0).toString().equals(artifact.getGroupId()); - valid = valid && relativeSubDir.getName(1).toString().equals(artifact.getArtifactId()); - if (!valid) { - String example = NATIVE_IMAGE_META_INF + "/%s/%s/" + NATIVE_IMAGE_PROPERTIES_FILENAME; - example = String.format(example, artifact.getGroupId(), artifact.getArtifactId()); - logger.warn("Properties file at '" + nativeImageProperty.toUri() + "' does not match the recommended '" + example + "' layout."); - } - } - } - } - } catch (IOException e) { - throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath", e); - } - } - - protected abstract List getDependencyScopes(); - - protected void addDependenciesToClasspath() throws MojoExecutionException { - configureMetadataRepository(); - for (Artifact dependency : project.getArtifacts().stream() - .filter(artifact -> getDependencyScopes().contains(artifact.getScope())) - .collect(Collectors.toSet())) { - addArtifactToClasspath(dependency); - maybeAddDependencyMetadata(dependency); - } - } - - /** - * Returns path to where application classes are stored, or jar artifact if it is produced. - * @return Path to application classes - * @throws MojoExecutionException failed getting main build path - */ - protected Path getMainBuildPath() throws MojoExecutionException { - if (classesDirectory != null) { - return classesDirectory.toPath(); - } else { - Path artifactPath = processArtifact(project.getArtifact(), project.getPackaging()); - if (artifactPath != null) { - return artifactPath; - } else { - return defaultClassesDirectory.toPath(); - } - } - } - - protected void populateApplicationClasspath() throws MojoExecutionException { - imageClasspath.add(getMainBuildPath()); - } - - protected void populateClasspath() throws MojoExecutionException { - if (classpath != null && !classpath.isEmpty()) { - imageClasspath.addAll(classpath.stream() - .map(Paths::get) - .map(Path::toAbsolutePath) - .collect(Collectors.toSet()) - ); - } else { - populateApplicationClasspath(); - addDependenciesToClasspath(); - } - imageClasspath.removeIf(entry -> !entry.toFile().exists()); - } - - protected String getClasspath() throws MojoExecutionException { - populateClasspath(); - if (imageClasspath.isEmpty()) { - throw new MojoExecutionException("Image classpath is empty. " + - "Check if your classpath configuration is correct."); - } - return imageClasspath.stream() - .map(Path::toString) - .collect(Collectors.joining(File.pathSeparator)); - } - - protected void buildImage() throws MojoExecutionException { - Path nativeImageExecutable = Utils.getNativeImage(logger); - - try { - ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); - processBuilder.command().addAll(getBuildArgs()); - - if (environment != null) { - processBuilder.environment().putAll(environment); - } - - if (!outputDirectory.exists() && !outputDirectory.mkdirs()) { - throw new MojoExecutionException("Failed creating output directory"); - } - processBuilder.inheritIO(); - - String commandString = String.join(" ", processBuilder.command()); - logger.info("Executing: " + commandString); - - if (dryRun) { - logger.warn("Skipped native-image building due to `" + NATIVE_IMAGE_DRY_RUN + "` being specified."); - return; - } - - Process imageBuildProcess = processBuilder.start(); - if (imageBuildProcess.waitFor() != 0) { - throw new MojoExecutionException("Execution of " + commandString + " returned non-zero result"); - } - } catch (IOException | InterruptedException e) { - throw new MojoExecutionException("Building image with " + nativeImageExecutable + " failed", e); - } - } - - protected void maybeAddGeneratedResourcesConfig(List into) { - if (resourcesConfigDirectory.exists() || agentResourceDirectory != null) { - File[] dirs = resourcesConfigDirectory.listFiles(); - Stream configDirs = - Stream.concat(dirs == null ? Stream.empty() : Arrays.stream(dirs), - agentResourceDirectory == null ? Stream.empty() : Stream.of(agentResourceDirectory).filter(File::isDirectory)); - - String value = configDirs.map(File::getAbsolutePath).collect(Collectors.joining(",")); - if (!value.isEmpty()) { - into.add("-H:ConfigurationFileDirectories=" + value); - if (agentResourceDirectory != null && agentResourceDirectory.isDirectory()) { - // The generated reflect config file contains references to java.* - // and org.apache.maven.surefire that we'd need to remove using - // a proper JSON parser/writer instead - into.add("-H:+AllowIncompleteClasspath"); - } - } - } } protected boolean isMetadataRepositoryEnabled() { @@ -460,7 +105,7 @@ protected boolean isMetadataRepositoryEnabled() { protected void configureMetadataRepository() { if (isMetadataRepositoryEnabled()) { Path repoPath = null; - Path destinationRoot = outputDirectory.toPath().resolve("graalvm-reachability-metadata"); + Path destinationRoot = reachabilityMetadataOutputDirectory.toPath(); if (Files.exists(destinationRoot) && !Files.isDirectory(destinationRoot)) { throw new RuntimeException("Metadata repository must be a directory, please remove regular file at: " + destinationRoot); } @@ -472,7 +117,7 @@ protected void configureMetadataRepository() { if (metadataRepositoryConfiguration.getLocalPath() != null) { Path localPath = metadataRepositoryConfiguration.getLocalPath().toPath(); - Path destination = outputDirectory.toPath().resolve(FileUtils.hashFor(localPath.toUri())); + Path destination = destinationRoot.resolve(FileUtils.hashFor(localPath.toUri())); repoPath = unzipLocalMetadata(localPath, destination); } else { URL targetUrl = metadataRepositoryConfiguration.getUrl(); @@ -527,17 +172,7 @@ public boolean isArtifactExcludedFromMetadataRepository(Artifact dependency) { } } - protected void maybeAddReachabilityMetadata(List configDirs) { - if (isMetadataRepositoryEnabled() && !metadataRepositoryConfigurations.isEmpty()) { - metadataRepositoryConfigurations.stream() - .map(configuration -> configuration.getDirectory().toAbsolutePath()) - .map(Path::toFile) - .map(File::getAbsolutePath) - .forEach(configDirs::add); - } - } - - protected void maybeAddDependencyMetadata(Artifact dependency) { + protected void maybeAddDependencyMetadata(Artifact dependency, Consumer excludeAction) { if (isMetadataRepositoryEnabled() && metadataRepository != null && !isArtifactExcludedFromMetadataRepository(dependency)) { Set configurations = metadataRepository.findConfigurationsFor(q -> { q.useLatestConfigWhenVersionIsUntested(); @@ -550,10 +185,8 @@ protected void maybeAddDependencyMetadata(Artifact dependency) { }); }); metadataRepositoryConfigurations.addAll(configurations); - if (configurations.stream().anyMatch(DirectoryConfiguration::isOverride)) { - buildArgs.add("--exclude-config"); - buildArgs.add(Pattern.quote(dependency.getFile().getAbsolutePath())); - buildArgs.add("^/META-INF/native-image/"); + if (excludeAction != null && configurations.stream().anyMatch(DirectoryConfiguration::isOverride)) { + excludeAction.accept(dependency.getFile()); } } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AddReachabilityMetadataMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AddReachabilityMetadataMojo.java new file mode 100644 index 000000000..bde93a140 --- /dev/null +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AddReachabilityMetadataMojo.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, 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 java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +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.graalvm.reachability.DirectoryConfiguration; + +@Mojo(name = "add-reachability-metadata", defaultPhase = LifecyclePhase.GENERATE_RESOURCES, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) +public class AddReachabilityMetadataMojo extends AbstractNativeMojo { + + private static final Set SCOPES; + static { + Set scopes = new HashSet<>(); + scopes.add(Artifact.SCOPE_COMPILE); + scopes.add(Artifact.SCOPE_RUNTIME); + scopes.add(Artifact.SCOPE_COMPILE_PLUS_RUNTIME); + SCOPES = Collections.unmodifiableSet(scopes); + } + + @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) + protected File outputDirectory; + + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + configureMetadataRepository(); + project.getArtifacts().stream().filter(this::isInScope) + .forEach(dependency -> maybeAddDependencyMetadata(dependency, null)); + if (isMetadataRepositoryEnabled() && !metadataRepositoryConfigurations.isEmpty()) { + Path destination = outputDirectory.toPath(); + try { + DirectoryConfiguration.copy(metadataRepositoryConfigurations, destination); + } catch (IOException ex) { + throw new MojoExecutionException(ex.getMessage(), ex); + } + } + } + + private boolean isInScope(Artifact artifact) { + return SCOPES.contains(artifact.getScope()); + } + +} 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 7993159ac..dbea0f57f 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 @@ -66,7 +66,7 @@ @Mojo(name = "compile-no-fork", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, requiresDependencyCollection = ResolutionScope.RUNTIME) -public class NativeCompileNoForkMojo extends AbstractNativeMojo { +public class NativeCompileNoForkMojo extends AbstractNativeImageMojo { @Parameter(property = "skipNativeBuild", defaultValue = "false") private boolean skip; diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java index 4ae827169..c395d26ee 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeTestMojo.java @@ -76,7 +76,7 @@ @Mojo(name = "test", defaultPhase = LifecyclePhase.TEST, threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST, requiresDependencyCollection = ResolutionScope.TEST) -public class NativeTestMojo extends AbstractNativeMojo { +public class NativeTestMojo extends AbstractNativeImageMojo { @Parameter(property = "skipTests", defaultValue = "false") private boolean skipTests; diff --git a/samples/metadata-repo-integration/build.gradle b/samples/metadata-repo-integration/build.gradle index 426b1aae6..77225c41f 100644 --- a/samples/metadata-repo-integration/build.gradle +++ b/samples/metadata-repo-integration/build.gradle @@ -38,6 +38,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + plugins { id 'application' id 'org.graalvm.buildtools.native' @@ -66,3 +67,7 @@ graalvmNative { enabled = true } } + +tasks.named("jar") { + from collectReachabilityMetadata +} diff --git a/samples/native-config-integration/pom.xml b/samples/native-config-integration/pom.xml index 85a470f04..751452c54 100644 --- a/samples/native-config-integration/pom.xml +++ b/samples/native-config-integration/pom.xml @@ -358,6 +358,34 @@ + + addMetadataHints + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin.version} + + + true + ${project.basedir}/config-directory + + + + + + add-reachability-metadata + + add-reachability-metadata + + + + + + + + excludeConfigTest