From 1a90901c08cfa6fd1d49bccb8a455afa7369a910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Tue, 7 Jun 2022 02:07:52 +0200 Subject: [PATCH 1/5] Rework Maven plugin, add excludeConfig option, set useBuildArg to false on nix systems. - Completely reworked Maven plugin (should fix many of previous issues and inconsistencies between main and test builds). - Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig` and `jvmArgs` properties in order to match those present in the Gradle plugin. - `useArgFile` is now set to true by default only on Windows - Added `excludeConfig` configuration option that allows skipping of configuration files that are present in classpath `jar` s. --- docs/src/docs/asciidoc/index.adoc | 7 + .../buildtools/gradle/NativeImagePlugin.java | 5 +- .../gradle/dsl/GraalVMExtension.java | 4 +- .../gradle/dsl/NativeImageOptions.java | 14 +- .../NativeImageCommandLineProvider.java | 8 +- .../org/graalvm/build/maven/MavenTask.java | 2 +- ....build.maven-functional-testing.gradle.kts | 3 - .../MetadataRepositoryFunctionalTest.groovy | 22 +- .../buildtools/maven/AbstractNativeMojo.java | 451 +++++++++++++++++- .../buildtools/maven/NativeBuildMojo.java | 291 +---------- .../buildtools/maven/NativeTestMojo.java | 215 +++------ .../config/ExcludeConfigConfiguration.java | 66 +++ .../AbstractGraalVMMavenFunctionalTest.groovy | 11 +- 13 files changed, 646 insertions(+), 453 deletions(-) create mode 100644 native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/ExcludeConfigConfiguration.java diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 091352487..ac4847e5e 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -23,6 +23,13 @@ If you are interested in contributing, please refer to our https://github.com/gr * Introduced the `metadataCopy` task. * Introduced the concept of agent modes. ** Under the hood, the agent mode dictates what options are passed to the agent and how metadata produced by multiple runs get merged. +* Added `excludeConfig` configuration option that allows skipping of configuration files that are present in classpath `jar` s. +* `useArgFile` is now set to true by default only on Windows. + +==== Maven plugin +* Completely reworked Maven plugin (should fix many of previous issues and inconsistencies between main and test builds). +* Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig` and `jvmArgs` properties in order to match those present in the Gradle plugin. +* `useArgFile` is now set to true by default only on Windows. === Release 0.9.11 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 4c054ff0b..6673e0c23 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -126,6 +126,7 @@ import static org.graalvm.buildtools.gradle.internal.GradleUtils.transitiveProjectArtifacts; import static org.graalvm.buildtools.gradle.internal.NativeImageExecutableLocator.graalvmHomeProvider; import static org.graalvm.buildtools.utils.SharedConstants.AGENT_PROPERTY; +import static org.graalvm.buildtools.utils.SharedConstants.IS_WINDOWS; /** * Gradle plugin for GraalVM Native Image. @@ -170,7 +171,7 @@ public void apply(Project project) { logger = GraalVMLogger.of(project.getLogger()); DefaultGraalVmExtension graalExtension = (DefaultGraalVmExtension) registerGraalVMExtension(project); - graalExtension.getUseArgFile().convention(true); + graalExtension.getUseArgFile().convention(IS_WINDOWS); project.getPlugins() .withType(JavaPlugin.class, javaPlugin -> configureJavaProject(project, nativeImageServiceProvider, graalExtension)); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/GraalVMExtension.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/GraalVMExtension.java index e9f1ee2b3..dd7089704 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/GraalVMExtension.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/GraalVMExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -99,7 +99,7 @@ public interface GraalVMExtension { /** * Property driving the use of @-arg files when invoking native image. - * This is enabled by default. For older native-image versions, this + * This is enabled by default on Windows. For older native-image versions, this * needs to be disabled. * * @return the argument file property diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 45025c11d..7335ba8ca 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -171,7 +171,7 @@ public interface NativeImageOptions extends Named { Property getJavaLauncher(); /** - * Returns the list of configuration file directories (e.g resource-config.json, ...) which need + * Returns the list of configuration file directories (e.g. resource-config.json, ...) which need * to be passed to native-image. * * @return a collection of directories @@ -179,6 +179,16 @@ public interface NativeImageOptions extends Named { @InputFiles ConfigurableFileCollection getConfigurationFileDirectories(); + /** + * Returns the map that as contains information about configuration that should be excluded + * during image building. It consists of a jar regular expression as a key and a resource + * regular expression as a value. + * + * @return a map of filters for configuration exclusion + */ + @Input + MapProperty getExcludeConfig(); + @Nested NativeResourcesOptions getResources(); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java index 7a12094f4..3c40684ce 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -104,6 +104,12 @@ public List asArguments() { NativeImageOptions options = getOptions().get(); List cliArgs = new ArrayList<>(20); + options.getExcludeConfig().get().forEach((jarPath, resourcePattern) -> { + cliArgs.add("--exclude-config"); + cliArgs.add(jarPath); + cliArgs.add(resourcePattern); + }); + cliArgs.add("-cp"); String classpathString = buildClasspathString(options); cliArgs.add(classpathString); diff --git a/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/MavenTask.java b/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/MavenTask.java index b0947f65b..a5a8842c3 100644 --- a/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/MavenTask.java +++ b/native-maven-plugin/build-plugins/src/main/java/org/graalvm/build/maven/MavenTask.java @@ -88,7 +88,7 @@ protected void executeMaven() { File projectdir = getProjectDirectory().getAsFile().get(); getExecOperations().javaexec(spec -> { spec.setClasspath(getMavenEmbedderClasspath()); - spec.setMain("org.apache.maven.cli.MavenCli"); + spec.getMainClass().set("org.apache.maven.cli.MavenCli"); spec.systemProperty("maven.multiModuleProjectDirectory", projectdir.getAbsolutePath()); prepareSpec(spec); List arguments = new ArrayList<>(); diff --git a/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-functional-testing.gradle.kts b/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-functional-testing.gradle.kts index 2f9d8d408..707a0cb23 100644 --- a/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-functional-testing.gradle.kts +++ b/native-maven-plugin/build-plugins/src/main/kotlin/org.graalvm.build.maven-functional-testing.gradle.kts @@ -1,6 +1,3 @@ -import org.graalvm.build.maven.MavenTask -import org.gradle.util.GFileUtils - /* * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 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 2f472fd55..b9e4e4527 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 @@ -64,7 +64,7 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes then: buildSucceeded - outputContains "JVM reachability metadata repository is enabled, but no repository has been configured" + outputContains "GraalVM reachability metadata repository is enabled, but no repository has been configured" outputContains "Reflection failed" } @@ -77,8 +77,8 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes then: buildSucceeded - outputContains "The official JVM reachability metadata repository is not released yet. Only local repositories are supported" - outputContains "JVM reachability metadata repository is enabled, but no repository has been configured" + outputContains "The official GraalVM reachability metadata repository is not released yet. Only local repositories are supported" + outputContains "GraalVM reachability metadata repository is enabled, but no repository has been configured" outputContains "Reflection failed" } @@ -94,10 +94,10 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes outputContains "Hello, from reflection!" and: "it doesn't find a configuration directory for the current version" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." and: "but it finds one thanks to the latest configuration field" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" } void "if the path doesn't exist it throws an error"() { @@ -109,7 +109,7 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes then: buildSucceeded - outputContains "JVM reachability metadata repository path does not exist" + outputContains "GraalVM reachability metadata repository path does not exist" outputContains "Reflection failed" } @@ -134,7 +134,7 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes then: buildSucceeded - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration is forced to version 2" + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration is forced to version 2" outputContains "Reflection failed" } @@ -150,10 +150,10 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes outputContains "Hello, from reflection!" and: "it doesn't find a configuration directory for the current version" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." and: "but it finds one thanks to the latest configuration field" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" } void "it can download a remote repository"() { @@ -170,10 +170,10 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes outputContains "Downloaded GraalVM reachability metadata repository from http://localhost:${localServerPort}/target/repo.zip" and: "it doesn't find a configuration directory for the current version" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory not found. Trying latest version." and: "but it finds one thanks to the latest configuration field" - outputContains "[jvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" + outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" } void "when pointing to a missing URL, reflection fails"() { 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 4a4e8f8d7..6d65b2729 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -44,57 +44,371 @@ 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.JvmReachabilityMetadataRepository; +import org.graalvm.reachability.internal.FileSystemRepository; +import javax.inject.Inject; import java.io.File; +import java.io.IOException; +import java.net.URI; +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.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @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 + protected File classesDirectory; + + @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) + protected File defaultClassesDirectory; + + protected final List imageClasspath; + + protected final Set metadataRepositoryPaths; + + @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 = "useArgFile") + protected Boolean useArgFile; + @Parameter(property = "buildArgs") protected List buildArgs; - @Parameter(defaultValue = "${session}", readonly = true) - protected MavenSession session; - @Parameter(defaultValue = "${project.build.directory}/native/generated", property = "resourcesConfigDirectory", required = true) - private File resourcesConfigDirectory; + protected File resourcesConfigDirectory; @Parameter(property = "agentResourceDirectory") - private File agentResourceDirectory; + protected File agentResourceDirectory; - @Component - protected ToolchainManager toolchainManager; + @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(alias = "metadataRepository") + protected MetadataRepositoryConfiguration metadataRepositoryConfiguration; + + protected JvmReachabilityMetadataRepository metadataRepository; @Component protected Logger logger; + @Component + protected ToolchainManager toolchainManager; + + @Inject + protected AbstractNativeMojo() { + imageClasspath = new ArrayList<>(); + metadataRepositoryPaths = 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(entry.getResourcePattern()); + }); + } + + cliArgs.add("-cp"); + cliArgs.add(getClasspath()); + + if (debug) { + cliArgs.add("-H:GenerateDebugInfo=1"); + } + if (!fallback) { + cliArgs.add("--no-fallback"); + } + if (verbose) { + cliArgs.add("--verbose"); + } + if (sharedLibrary) { + cliArgs.add("--shared"); + } + + 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(cliArgs); + + 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) { + return NativeImageUtils.convertToArgsFile(cliArgs); + } + return Collections.unmodifiableList(cliArgs); + } + + protected Path processArtifact(Artifact artifact, String artifactType) throws MojoExecutionException { + File artifactFile = artifact.getFile(); + if (!artifactType.equals(artifact.getType())) { + logger.warn("Ignoring non-jar type ImageClasspath Entry " + artifact); + 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.info("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")"); + + warnIfWrongMetaInfLayout(jarFilePath, artifact); + return jarFilePath; + } + + protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionException { + Optional.ofNullable(processArtifact(artifact, "jar")).ifPresent(imageClasspath::add); + } + + protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException { + if (jarFilePath.toFile().isDirectory()) { + logger.warn("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 + "/${groupId}/${artifactId}/" + NATIVE_IMAGE_PROPERTIES_FILENAME; + logger.warn(nativeImageProperty.toUri() + " does not match 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(); + } + } + + 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(); + + 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.directory(outputDirectory); + processBuilder.inheritIO(); + + String commandString = String.join(" ", processBuilder.command()); + logger.info("Executing: " + commandString); + + if (System.getProperty(NATIVE_IMAGE_DRY_RUN) != null) { + 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) - ); - into.add("-H:ConfigurationFileDirectories=" + - configDirs - .map(File::getAbsolutePath) - .collect(Collectors.joining(","))); + Stream configDirs = + Stream.concat(dirs == null ? Stream.empty() : Arrays.stream(dirs), + agentResourceDirectory == null ? Stream.empty() : Stream.of(agentResourceDirectory).filter(File::isDirectory)); + + into.add("-H:ConfigurationFileDirectories=" + configDirs.map(File::getAbsolutePath).collect(Collectors.joining(","))); 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 @@ -103,4 +417,109 @@ protected void maybeAddGeneratedResourcesConfig(List into) { } } } + + protected boolean isMetadataRepositoryEnabled() { + return metadataRepositoryConfiguration != null && metadataRepositoryConfiguration.isEnabled(); + } + + protected void configureMetadataRepository() { + if (isMetadataRepositoryEnabled()) { + Path repoPath = null; + if (metadataRepositoryConfiguration.getVersion() != null) { + logger.warn("The official GraalVM reachability metadata repository is not released yet. Only local repositories are supported"); + } + if (metadataRepositoryConfiguration.getLocalPath() != null) { + Path localPath = metadataRepositoryConfiguration.getLocalPath().toPath(); + repoPath = unzipLocalMetadata(localPath); + } else if (metadataRepositoryConfiguration.getUrl() != null) { + Optional download = downloadMetadata(metadataRepositoryConfiguration.getUrl()); + if (download.isPresent()) { + logger.info("Downloaded GraalVM reachability metadata repository from " + metadataRepositoryConfiguration.getUrl()); + repoPath = unzipLocalMetadata(download.get()); + } + } + + if (repoPath == null) { + logger.warn("GraalVM reachability metadata repository is enabled, but no repository has been configured"); + } else { + metadataRepository = new FileSystemRepository(repoPath, new FileSystemRepository.Logger() { + @Override + public void log(String groupId, String artifactId, String version, Supplier message) { + logger.info(String.format("[graalvm reachability metadata repository for %s:%s:%s]: %s", groupId, artifactId, version, message.get())); + } + }); + } + } + } + + public boolean isArtifactExcludedFromMetadataRepository(Artifact dependency) { + if (metadataRepositoryConfiguration == null) { + return false; + } else { + return metadataRepositoryConfiguration.isArtifactExcluded(dependency); + } + } + + protected void maybeAddReachabilityMetadata(List args) { + if (isMetadataRepositoryEnabled() && !metadataRepositoryPaths.isEmpty()) { + String arg = metadataRepositoryPaths.stream() + .map(Path::toAbsolutePath) + .map(Path::toFile) + .map(File::getAbsolutePath) + .collect(Collectors.joining(",")); + + if (!arg.isEmpty()) { + args.add("-H:ConfigurationFileDirectories=" + arg); + } + } + } + + protected void maybeAddDependencyMetadata(Artifact dependency) { + if (isMetadataRepositoryEnabled() && metadataRepository != null && !isArtifactExcludedFromMetadataRepository(dependency)) { + metadataRepositoryPaths.addAll(metadataRepository.findConfigurationDirectoriesFor(q -> { + q.useLatestConfigWhenVersionIsUntested(); + q.forArtifact(artifact -> { + artifact.gav(String.join(":", + dependency.getGroupId(), + dependency.getArtifactId(), + dependency.getVersion())); + getMetadataVersion(dependency).ifPresent(artifact::forceConfigVersion); + }); + })); + } + } + + protected Optional getMetadataVersion(Artifact dependency) { + if (metadataRepositoryConfiguration == null) { + return Optional.empty(); + } else { + return metadataRepositoryConfiguration.getMetadataVersion(dependency); + } + } + + protected Optional downloadMetadata(URL url) { + Path destination = outputDirectory.toPath().resolve("graalvm-reachability-metadata"); + return FileUtils.download(url, destination, logger::error); + } + + protected Path unzipLocalMetadata(Path localPath) { + if (Files.exists(localPath)) { + if (FileUtils.isZip(localPath)) { + Path destination = outputDirectory.toPath().resolve("graalvm-reachability-metadata"); + if (!Files.exists(destination) && !destination.toFile().mkdirs()) { + throw new RuntimeException("Failed creating destination directory"); + } + FileUtils.extract(localPath, destination, logger::error); + return destination; + } else if (Files.isDirectory(localPath)) { + return localPath; + } else { + logger.warn("Unable to extract metadata repository from " + localPath + ". " + + "It needs to be either a ZIP file or an exploded directory"); + } + } else { + logger.error("GraalVM reachability metadata repository path does not exist: " + localPath); + } + return null; + } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildMojo.java index 888ed78f7..602446651 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeBuildMojo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -45,83 +45,35 @@ import org.apache.maven.model.ConfigurationContainer; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; -import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.PluginParameterExpressionEvaluator; -import org.apache.maven.plugin.descriptor.PluginDescriptor; 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.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; import org.codehaus.plexus.util.xml.Xpp3Dom; -import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.maven.config.MetadataRepositoryConfiguration; -import org.graalvm.buildtools.utils.FileUtils; -import org.graalvm.buildtools.utils.NativeImageUtils; -import org.graalvm.reachability.JvmReachabilityMetadataRepository; -import org.graalvm.reachability.internal.FileSystemRepository; -import java.io.File; -import java.io.IOException; -import java.net.URI; -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.Optional; -import java.util.Set; import java.util.function.BiFunction; -import java.util.function.Supplier; -import java.util.stream.Collectors; -@Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE) +@Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE, + requiresDependencyResolution = ResolutionScope.RUNTIME, + requiresDependencyCollection = ResolutionScope.RUNTIME) public class NativeBuildMojo extends AbstractNativeMojo { - private static final String NATIVE_IMAGE_META_INF = "META-INF/native-image"; - private static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties"; - - @Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only - private PluginDescriptor plugin; - - @Parameter(defaultValue = "${project.build.directory}", property = "outputDir", required = true)// - private File outputDirectory; - - @Parameter(property = "mainClass") - private String mainClass; - - @Parameter(property = "imageName") - private String imageName; - @Parameter(property = "skipNativeBuild", defaultValue = "false") private boolean skip; - @Parameter(defaultValue = "${mojoExecution}") - private MojoExecution mojoExecution; - - @Parameter(property = "classpath") - private List classpath; - - @Parameter(property = "useArgFile", defaultValue = "true") - private boolean useArgFile; - - @Parameter(alias = "metadataRepository") - private MetadataRepositoryConfiguration metadataRepositoryConfiguration; - - private final List imageClasspath; - private PluginParameterExpressionEvaluator evaluator; - private JvmReachabilityMetadataRepository metadataRepository; - - public NativeBuildMojo() { - this.imageClasspath = new ArrayList<>(); + @Override + protected List getDependencyScopes() { + return Arrays.asList(Artifact.SCOPE_COMPILE, + Artifact.SCOPE_RUNTIME, + Artifact.SCOPE_COMPILE_PLUS_RUNTIME + ); } @Override @@ -130,201 +82,13 @@ public void execute() throws MojoExecutionException { getLog().info("Skipping native-image generation (parameter 'skipNativeBuild' is true)."); return; } - evaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); - - configureMetadataRepository(); - Set metadataRepositoryPaths = new HashSet<>(); - - imageClasspath.clear(); - if (classpath != null && !classpath.isEmpty()) { - imageClasspath.addAll(classpath.stream().map(Paths::get).collect(Collectors.toList())); - } else { - List imageClasspathScopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME); - project.setArtifactFilter(artifact -> imageClasspathScopes.contains(artifact.getScope())); - for (Artifact dependency : project.getArtifacts()) { - addClasspath(dependency); - maybeAddDependencyMetadata(metadataRepositoryPaths, dependency); - } - addClasspath(project.getArtifact(), project.getPackaging()); - } - String classpathStr = imageClasspath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)); - - Path nativeImageExecutable = Utils.getNativeImage(); + evaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); + maybeSetMainClassFromPlugin(this::consumeExecutionsNodeValue, "org.apache.maven.plugins:maven-shade-plugin", "transformers", "transformer", "mainClass"); + maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-assembly-plugin", "archive", "manifest", "mainClass"); + maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass"); maybeAddGeneratedResourcesConfig(buildArgs); - maybeAddReachabilityMetadata(buildArgs, metadataRepositoryPaths); - - try { - List cliArgs = new ArrayList<>(); - cliArgs.add("-cp"); - cliArgs.add(classpathStr); - cliArgs.addAll(getBuildArgs()); - if (useArgFile) { - cliArgs = NativeImageUtils.convertToArgsFile(cliArgs); - } - ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); - processBuilder.command().addAll(cliArgs); - processBuilder.directory(getWorkingDirectory().toFile()); - processBuilder.inheritIO(); - - String commandString = String.join(" ", processBuilder.command()); - getLog().info("Executing: " + commandString); - 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); - } - } - - private void configureMetadataRepository() { - if (isMetadataRepositoryEnabled()) { - Path repoPath = null; - if (metadataRepositoryConfiguration.getVersion() != null) { - getLog().warn("The official JVM reachability metadata repository is not released yet. Only local repositories are supported"); - } - if (metadataRepositoryConfiguration.getLocalPath() != null) { - Path localPath = metadataRepositoryConfiguration.getLocalPath().toPath(); - repoPath = unzipLocalPath(localPath); - } else if (metadataRepositoryConfiguration.getUrl() != null) { - Optional download = download(metadataRepositoryConfiguration.getUrl()); - if (download.isPresent()) { - getLog().info("Downloaded GraalVM reachability metadata repository from " + metadataRepositoryConfiguration.getUrl()); - repoPath = unzipLocalPath(download.get()); - } - } - - if (repoPath == null) { - getLog().warn("JVM reachability metadata repository is enabled, but no repository has been configured"); - } else { - metadataRepository = new FileSystemRepository(repoPath, new FileSystemRepository.Logger() { - @Override - public void log(String groupId, String artifactId, String version, Supplier message) { - getLog().info(String.format("[jvm reachability metadata repository for %s:%s:%s]: %s", groupId, artifactId, version, message.get())); - } - }); - } - } - } - - private Optional download(URL url) { - Path destination = outputDirectory.toPath().resolve("graalvm-reachability-metadata"); - return FileUtils.download(url, destination, getLog()::error); - } - - private Path unzipLocalPath(Path localPath) { - if (Files.exists(localPath)) { - if (FileUtils.isZip(localPath)) { - Path destination = outputDirectory.toPath().resolve("graalvm-reachability-metadata"); - if (!Files.exists(destination)) { - destination.toFile().mkdirs(); - } - - FileUtils.extract(localPath, destination, getLog()::error); - return destination; - } else if (Files.isDirectory(localPath)) { - return localPath; - } else { - getLog().warn("Unable to extract metadata repository from " + localPath + ". It needs to be either a ZIP file or an exploded directory"); - } - } else { - getLog().error("JVM reachability metadata repository path does not exist: " + localPath); - } - return null; - } - - private void maybeAddDependencyMetadata(Set metadataRepositoryPaths, Artifact dependency) { - if (isMetadataRepositoryEnabled() && metadataRepository != null && !isExcluded(dependency)) { - metadataRepositoryPaths.addAll(metadataRepository.findConfigurationDirectoriesFor(q -> { - q.useLatestConfigWhenVersionIsUntested(); - q.forArtifact(artifact -> { - artifact.gav(String.join(":", dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion())); - getMetadataVersion(dependency).ifPresent(artifact::forceConfigVersion); - }); - })); - } - } - - private Optional getMetadataVersion(Artifact dependency) { - if (metadataRepositoryConfiguration == null) { - return Optional.empty(); - } else { - return metadataRepositoryConfiguration.getMetadataVersion(dependency); - } - } - - private boolean isExcluded(Artifact dependency) { - if (metadataRepositoryConfiguration == null) { - return false; - } else { - return metadataRepositoryConfiguration.isArtifactExcluded(dependency); - } - } - - private void maybeAddReachabilityMetadata(List buildArgs, Set paths) { - if (isMetadataRepositoryEnabled() && !paths.isEmpty()) { - String arg = paths.stream() - .map(Path::toAbsolutePath) - .map(Path::toFile) - .map(File::getAbsolutePath) - .collect(Collectors.joining(",")); - - if (!arg.isEmpty()) { - buildArgs.add("-H:ConfigurationFileDirectories=" + arg); - } - } - } - - private boolean isMetadataRepositoryEnabled() { - return metadataRepositoryConfiguration != null && metadataRepositoryConfiguration.isEnabled(); - } - - private void addClasspath(Artifact artifact) throws MojoExecutionException { - addClasspath(artifact, "jar"); - } - - private void addClasspath(Artifact artifact, String artifactType) throws MojoExecutionException { - if (!artifactType.equals(artifact.getType())) { - getLog().warn("Ignoring non-jar type ImageClasspath Entry " + artifact); - return; - } - File artifactFile = artifact.getFile(); - if (artifactFile == null) { - throw new MojoExecutionException("Missing jar-file for " + artifact + ". Ensure that" + plugin.getArtifactId() + " runs in package phase."); - } - Path jarFilePath = artifactFile.toPath(); - getLog().info("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")"); - - 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)) { - List nativeImageProperties = Files.walk(nativeImageMetaInfBase) - .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 + "/${groupId}/${artifactId}/" + NATIVE_IMAGE_PROPERTIES_FILENAME; - getLog().warn(nativeImageProperty.toUri() + " does not match recommended " + example + " layout."); - } - } - } - } catch (IOException e) { - throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath", e); - } - - imageClasspath.add(jarFilePath); - } - - private Path getWorkingDirectory() { - outputDirectory.mkdirs(); - return outputDirectory.toPath(); + buildImage(); } private String consumeConfigurationNodeValue(String pluginKey, String... nodeNames) { @@ -371,7 +135,7 @@ private String evaluateValue(String value) { if (evaluatedValue instanceof String) { return (String) evaluatedValue; } - } catch (ExpressionEvaluationException exception) { + } catch (ExpressionEvaluationException ignored) { } } @@ -387,25 +151,4 @@ private void maybeSetMainClassFromPlugin(BiFunction ma } } } - - private List getBuildArgs() { - maybeSetMainClassFromPlugin(this::consumeExecutionsNodeValue, "org.apache.maven.plugins:maven-shade-plugin", "transformers", "transformer", "mainClass"); - maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-assembly-plugin", "archive", "manifest", "mainClass"); - maybeSetMainClassFromPlugin(this::consumeConfigurationNodeValue, "org.apache.maven.plugins:maven-jar-plugin", "archive", "manifest", "mainClass"); - - List list = new ArrayList<>(); - if (buildArgs != null && !buildArgs.isEmpty()) { - for (String buildArg : buildArgs) { - list.addAll(Arrays.asList(buildArg.split("\\s+"))); - } - } - if (mainClass != null && !mainClass.equals(".")) { - list.add("-H:Class=" + mainClass); - } - if (imageName == null) { - imageName = project.getArtifactId(); - } - list.add("-H:Name=" + imageName); - return list; - } } 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 013ace42f..9d079f2f2 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -42,18 +42,15 @@ package org.graalvm.buildtools.maven; import org.apache.maven.artifact.Artifact; -import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.model.FileSet; import org.apache.maven.model.Plugin; 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.codehaus.plexus.util.xml.Xpp3Dom; import org.graalvm.buildtools.Utils; -import org.graalvm.buildtools.utils.NativeImageUtils; import org.graalvm.junit.platform.JUnitPlatformFeature; import java.io.File; @@ -67,9 +64,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import java.util.stream.Stream; import static org.graalvm.buildtools.Utils.NATIVE_TESTS_EXE; @@ -88,82 +83,102 @@ public class NativeTestMojo extends AbstractNativeMojo { @Parameter(property = "skipNativeTests", defaultValue = "false") private boolean skipNativeTests; - @Parameter(property = "classpath") - private List classpath; - - @Parameter(property = "project.build.directory") - private File buildDirectory; + @Override + protected void populateApplicationClasspath() throws MojoExecutionException { + super.populateApplicationClasspath(); + imageClasspath.add(Paths.get(project.getBuild().getTestOutputDirectory())); + project.getBuild() + .getTestResources() + .stream() + .map(FileSet::getDirectory) + .map(Paths::get) + .forEach(imageClasspath::add); + } - @Parameter(property = "environmentVariables") - private Map environment; + @Override + protected List getDependencyScopes() { + return Arrays.asList( + Artifact.SCOPE_COMPILE, + Artifact.SCOPE_RUNTIME, + Artifact.SCOPE_TEST, + Artifact.SCOPE_COMPILE_PLUS_RUNTIME + ); + } - @Parameter(property = "systemPropertyVariables") - private Map systemProperties; + @Override + protected void addDependenciesToClasspath() throws MojoExecutionException { + super.addDependenciesToClasspath(); + pluginArtifacts.stream() + .filter(it -> it.getGroupId().startsWith(Utils.MAVEN_GROUP_ID) || it.getGroupId().startsWith("org.junit")) + .map(it -> it.getFile().toPath()) + .forEach(imageClasspath::add); + findNativePlatformJar().ifPresent(imageClasspath::add); + } @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void execute() throws MojoExecutionException { if (skipTests || skipNativeTests) { logger.info("Skipping native-image tests (parameter 'skipTests' or 'skipNativeTests' is true)."); return; } - configureEnvironment(); if (!hasTests()) { logger.info("Skipped native-image tests since there are no test classes."); return; } - - String classpath = getClassPath(); - Optional nativePlatformJar = findNativePlatformJar(); - if (nativePlatformJar.isPresent()) { - classpath += File.pathSeparator + nativePlatformJar.get().toFile().getAbsolutePath(); + if (!hasTestIds()) { + logger.error("Test configuration file wasn't found. Make sure that test execution wasn't skipped."); + throw new IllegalStateException("Test configuration file wasn't found."); } - Path targetFolder = new File(project.getBuild().getDirectory()).toPath(); - targetFolder.toFile().mkdirs(); logger.info("===================="); logger.info("Initializing project: " + project.getName()); logger.info("===================="); - if (!hasTestIds()) { - logger.error("Test configuration file wasn't found. Make sure that test execution wasn't skipped."); - throw new IllegalStateException("Test configuration file wasn't found."); + configureEnvironment(); + buildArgs.add("--features=org.graalvm.junit.platform.JUnitPlatformFeature"); + + if (systemProperties == null) { + systemProperties = new HashMap<>(); } + systemProperties.put("junit.platform.listeners.uid.tracking.output.dir", + NativeExtension.testIdsDirectory(outputDirectory.getAbsolutePath())); - logger.debug("Classpath: " + classpath); - buildImage(classpath, targetFolder); + imageName = NATIVE_TESTS_EXE; + mainClass = "org.graalvm.junit.platform.NativeImageJUnitLauncher"; - runTests(targetFolder); + buildImage(); + runNativeTests(outputDirectory.toPath().resolve(NATIVE_TESTS_EXE)); } private void configureEnvironment() { - // inherit from surefire mojo - Plugin plugin = project.getPlugin("org.apache.maven.plugins:maven-surefire-plugin"); - if (plugin != null) { - Object configuration = plugin.getConfiguration(); - if (configuration instanceof Xpp3Dom) { - Xpp3Dom dom = (Xpp3Dom) configuration; - Xpp3Dom environmentVariables = dom.getChild("environmentVariables"); - if (environmentVariables != null) { - Xpp3Dom[] children = environmentVariables.getChildren(); - if (environment == null) { - environment = new HashMap<>(children.length); - } - for (Xpp3Dom child : children) { - environment.put(child.getName(), child.getValue()); - } + // inherit from surefire mojo + Plugin plugin = project.getPlugin("org.apache.maven.plugins:maven-surefire-plugin"); + if (plugin != null) { + Object configuration = plugin.getConfiguration(); + if (configuration instanceof Xpp3Dom) { + Xpp3Dom dom = (Xpp3Dom) configuration; + Xpp3Dom environmentVariables = dom.getChild("environmentVariables"); + if (environmentVariables != null) { + Xpp3Dom[] children = environmentVariables.getChildren(); + if (environment == null) { + environment = new HashMap<>(children.length); } - Xpp3Dom systemProps = dom.getChild("systemPropertyVariables"); - if (systemProps != null) { - Xpp3Dom[] children = systemProps.getChildren(); - if (systemProperties == null) { - systemProperties = new HashMap<>(children.length); - } - for (Xpp3Dom child : children) { - systemProperties.put(child.getName(), child.getValue()); - } + for (Xpp3Dom child : children) { + environment.put(child.getName(), child.getValue()); + } + } + Xpp3Dom systemProps = dom.getChild("systemPropertyVariables"); + if (systemProps != null) { + Xpp3Dom[] children = systemProps.getChildren(); + if (systemProperties == null) { + systemProperties = new HashMap<>(children.length); + } + for (Xpp3Dom child : children) { + systemProperties.put(child.getName(), child.getValue()); } } } + } } private boolean hasTests() { @@ -178,53 +193,22 @@ private boolean hasTests() { return false; } - private void buildImage(String classpath, Path targetFolder) throws MojoExecutionException { - Path nativeImageExecutable = Utils.getNativeImage(); - - List command = new ArrayList<>(Arrays.asList( - "-cp", classpath, - "--features=org.graalvm.junit.platform.JUnitPlatformFeature", - "-Djunit.platform.listeners.uid.tracking.output.dir=" + NativeExtension.testIdsDirectory(buildDirectory.getAbsolutePath()), - "-H:Path=" + targetFolder.toAbsolutePath(), - "-H:Name=" + NATIVE_TESTS_EXE)); - maybeAddGeneratedResourcesConfig(command); - - if (buildArgs != null) { - command.addAll(buildArgs); + private void runNativeTests(Path executable) throws MojoExecutionException { + Path xmlLocation = outputDirectory.toPath().resolve("native-test-reports"); + if (!xmlLocation.toFile().exists() && !xmlLocation.toFile().mkdirs()) { + throw new MojoExecutionException("Failed creating xml output directory"); } - try { - ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); - prepareVariables(processBuilder, command); - command = NativeImageUtils.convertToArgsFile(command); - processBuilder.command().addAll(command); - processBuilder.command().add("org.graalvm.junit.platform.NativeImageJUnitLauncher"); - processBuilder.directory(new File(project.getBuild().getDirectory())); + ProcessBuilder processBuilder = new ProcessBuilder(executable.toAbsolutePath().toString()); processBuilder.inheritIO(); - String commandString = String.join(" ", processBuilder.command()); - getLog().info("Executing: " + commandString); - 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); - } - } - - private void runTests(Path targetFolder) throws MojoExecutionException { - Path xmlLocation = targetFolder.resolve("native-test-reports"); - xmlLocation.toFile().mkdirs(); - try { - ProcessBuilder processBuilder = new ProcessBuilder( - targetFolder.resolve(NATIVE_TESTS_EXE).toAbsolutePath().toString()); - processBuilder.inheritIO(); List command = new ArrayList<>(); - prepareVariables(processBuilder, command); command.add("--xml-output-dir"); command.add(xmlLocation.toString()); + systemProperties.forEach((key, value) -> command.add("-D" + key + "=" + value)); processBuilder.command().addAll(command); + processBuilder.environment().putAll(environment); + String commandString = String.join(" ", processBuilder.command()); getLog().info("Executing: " + commandString); Process imageBuildProcess = processBuilder.start(); @@ -236,48 +220,6 @@ private void runTests(Path targetFolder) throws MojoExecutionException { } } - private void prepareVariables(ProcessBuilder processBuilder, List command) { - if (environment != null) { - processBuilder.environment().putAll(environment); - } - if (systemProperties != null) { - for (Map.Entry entry : systemProperties.entrySet()) { - command.add("-D" + entry.getKey() + "=" + entry.getValue()); - } - } - } - - private String getClassPath() throws MojoFailureException { - if (classpath != null && !classpath.isEmpty()) { - return String.join(File.pathSeparator, classpath); - } - try { - List pluginDependencies = pluginArtifacts.stream() - .filter(it -> it.getGroupId().startsWith(Utils.MAVEN_GROUP_ID) || it.getGroupId().startsWith("org.junit")) - .collect(Collectors.toList()); - - List projectClassPath = new ArrayList<>(project - .getTestClasspathElements()); - - Stream allResources = Stream.concat( - project.getBuild() - .getResources() - .stream() - .map(FileSet::getDirectory), - project.getBuild() - .getTestResources() - .stream() - .map(FileSet::getDirectory) - ); - - return Stream.concat(Stream.concat(projectClassPath.stream(), allResources), pluginDependencies.stream() - .map(it -> it.getFile().toString())) - .collect(Collectors.joining(File.pathSeparator)); - } catch (DependencyResolutionRequiredException e) { - throw new MojoFailureException(e.getMessage(), e); - } - } - private boolean hasTestIds() { try { Path buildDir = Paths.get(project.getBuild().getDirectory()); @@ -288,6 +230,7 @@ private boolean hasTestIds() { } } + @SuppressWarnings("SameParameterValue") private Stream readAllFiles(Path dir, String prefix) throws IOException { return findFiles(dir, prefix).map(outputFile -> { try { @@ -307,7 +250,6 @@ private static Stream findFiles(Path dir, String prefix) throws IOExceptio && path.getFileName().toString().startsWith(prefix))); } - private static Optional findNativePlatformJar() { try { return Optional.of(new File(JUnitPlatformFeature.class.getProtectionDomain().getCodeSource().getLocation().toURI()).toPath()); @@ -315,5 +257,4 @@ private static Optional findNativePlatformJar() { return Optional.empty(); } } - } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/ExcludeConfigConfiguration.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/ExcludeConfigConfiguration.java new file mode 100644 index 000000000..c41d263b1 --- /dev/null +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/config/ExcludeConfigConfiguration.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, 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.config; + +import org.apache.maven.plugins.annotations.Parameter; + +public class ExcludeConfigConfiguration { + @Parameter + private String jarPath; + @Parameter + private String resourcePattern; + + public String getJarPath() { + return jarPath; + } + + public void setJarPath(String jarPath) { + this.jarPath = jarPath; + } + + public String getResourcePattern() { + return resourcePattern; + } + + public void setResourcePattern(String resourcePattern) { + this.resourcePattern = resourcePattern; + } +} diff --git a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy index f1c6207a1..47fa9fdd7 100644 --- a/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy +++ b/native-maven-plugin/src/testFixtures/groovy/org/graalvm/buildtools/maven/AbstractGraalVMMavenFunctionalTest.groovy @@ -56,6 +56,8 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { @TempDir Path testDirectory + Path testOrigin; + private IsolatedMavenExecutor executor MavenExecutionResult result @@ -92,13 +94,13 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { } protected void withReproducer(String name) { - File sampleDir = new File("reproducers/$name") - copySample(sampleDir.toPath(), testDirectory) + testOrigin = new File("reproducers/$name").toPath() + copySample(testOrigin, testDirectory) } protected void withSample(String name) { - File sampleDir = new File("../samples/$name") - copySample(sampleDir.toPath(), testDirectory) + testOrigin = new File("../samples/$name").toPath() + copySample(testOrigin, testDirectory) } protected void withLocalServer() { @@ -141,6 +143,7 @@ abstract class AbstractGraalVMMavenFunctionalTest extends Specification { } void mvn(String... args) { + System.out.println("Running copy of maven project `" + testOrigin + "` with `" + args + "`"); result = executor.execute( testDirectory.toFile(), [ From ac68bcec9d46041603cda95fdee1b07ca1cea0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Wed, 8 Jun 2022 16:09:03 +0200 Subject: [PATCH 2/5] Fixing tests and docs --- .../workflows/test-native-gradle-plugin.yml | 2 +- docs/src/docs/asciidoc/gradle-plugin.adoc | 2 +- docs/src/docs/asciidoc/index.adoc | 8 +- docs/src/docs/asciidoc/maven-plugin.adoc | 129 ++++++++++++++++-- .../docs/snippets/gradle/groovy/build.gradle | 4 +- .../snippets/gradle/kotlin/build.gradle.kts | 4 +- .../gradle/dsl/NativeImageOptions.java | 17 ++- .../internal/BaseNativeImageOptions.java | 24 +++- .../NativeImageCommandLineProvider.java | 34 ++++- .../gradle/tasks/BuildNativeImageTask.java | 5 +- .../reproducers/issue-144/pom.xml | 11 +- .../JavaApplicationFunctionalTest.groovy | 14 ++ .../MetadataRepositoryFunctionalTest.groovy | 12 ++ .../java/org/graalvm/buildtools/Utils.java | 24 +++- .../buildtools/maven/AbstractNativeMojo.java | 41 +++--- .../buildtools/maven/MergeAgentFilesMojo.java | 9 +- .../java-application-with-reflection/pom.xml | 12 +- .../java-application-with-resources/pom.xml | 12 +- samples/java-application-with-tests/pom.xml | 16 ++- samples/java-application/pom.xml | 16 ++- samples/java-library/pom.xml | 14 +- .../native-config-integration/build.gradle | 1 + samples/native-config-integration/pom.xml | 38 +++++- 23 files changed, 362 insertions(+), 87 deletions(-) diff --git a/.github/workflows/test-native-gradle-plugin.yml b/.github/workflows/test-native-gradle-plugin.yml index 8f253c603..7b8aed887 100644 --- a/.github/workflows/test-native-gradle-plugin.yml +++ b/.github/workflows/test-native-gradle-plugin.yml @@ -53,7 +53,7 @@ jobs: strategy: fail-fast: false matrix: - gradle-version: ["current", "7.3.3", "7.2", "7.1", "6.8.3","6.7.1"] + gradle-version: ["current", "7.3.3", "6.7.1"] graalvm-version: [ dev ] java-version: [ 11 ] os: [ ubuntu-20.04 ] diff --git a/docs/src/docs/asciidoc/gradle-plugin.adoc b/docs/src/docs/asciidoc/gradle-plugin.adoc index b432ef97d..cc58058e3 100644 --- a/docs/src/docs/asciidoc/gradle-plugin.adoc +++ b/docs/src/docs/asciidoc/gradle-plugin.adoc @@ -67,7 +67,7 @@ include::../snippets/gradle/kotlin/settings.gradle.kts[tags=pre-release, indent= ---- ==== -=== Installing GraalVM native image tool +=== Installing GraalVM Native Image tool The plugin relies on Gradle's https://docs.gradle.org/7.1.1/userguide/toolchains.html[JVM toolchain support], allowing to decorrelate the tool used to run Gradle, the compiler used to build your application, and eventually the SDK used to generate a native image. diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index ac4847e5e..3d7129042 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -23,13 +23,17 @@ If you are interested in contributing, please refer to our https://github.com/gr * Introduced the `metadataCopy` task. * Introduced the concept of agent modes. ** Under the hood, the agent mode dictates what options are passed to the agent and how metadata produced by multiple runs get merged. -* Added `excludeConfig` configuration option that allows skipping of configuration files that are present in classpath `jar` s. +* Added `excludeConfig` configuration option that allows skipping of configuration files that are present in dependencies. * `useArgFile` is now set to true by default only on Windows. +* Added `quickBuild` configuration option. ==== Maven plugin * Completely reworked Maven plugin (should fix many of previous issues and inconsistencies between main and test builds). -* Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig` and `jvmArgs` properties in order to match those present in the Gradle plugin. +* Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig`, `quickBuild`, and `jvmArgs` properties in order to match those present in the Gradle plugin. + ++ +See <> for more information. * `useArgFile` is now set to true by default only on Windows. +* Changed lookup order for `native-image` discovery -- `GRAALVM_HOME`, `JAVA_HOME`, `PATH`. === Release 0.9.11 diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc index 8b9e4dcfd..86efbd296 100644 --- a/docs/src/docs/asciidoc/maven-plugin.adoc +++ b/docs/src/docs/asciidoc/maven-plugin.adoc @@ -114,10 +114,118 @@ Build Configuration]. It is also possible to customize the plugin within a image name is not supplied, the artifact ID of the project will be used by default. ``:: If you want to pass additional arguments to the native image builder, use `` - in the configuration of the plugin. + in the configuration of the plugin: +[source,xml] +---- + + --argument + +---- ``:: - To skip generation of the native image, supply - `true` in the configuration of the plugin. + To skip generation of the native image, supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + To skip generation and execution of the native image compiled tests, supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + If you want to enable generation of debugging information supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + If you want to enable verbose output during native-image building supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + If you want to build image as a shared library supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + If you want to use argument file for native-image building supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + If you want to build the image using https://blogs.oracle.com/java/post/graalvm-enterprise-221--faster-smarter-leaner[quick build mode], supply the following in the configuration of the plugin: +[source,xml] +---- +true +---- +``:: + In order to exclude configuration from present jar files, specify: +[source,xml] +---- + + + dummy/path/to/file.jar + * + + +---- +``:: + To set environment options for native-image building supply the following in the configuration of the plugin: +[source,xml] +---- + + value + +---- +``:: + To specify system properties used for native-image building supply the following in the configuration of the plugin: +[source,xml] +---- + + value + +---- +``:: + To specify JVM arguments used for native-image building supply the following in the configuration of the plugin: +[source,xml] +---- + + argument1 + argument2 + +---- +``:: + If you want to specify custom directories where configuration files should be looked up, supply the following in the configuration of the plugin: +[source,xml] +---- + + path/to/dir + +---- +` + path/to/file.jar + path/to/classes + +---- +``:: + If you want to specify custom path to packed JAR, or a custom directory that contains +only application classes, but want the plugin to still automatically add classpath entries for +dependencies, simply add: +[source,xml] +---- + + path/to/dir + +---- ``:: Configuration of the <>. See <> and <> for details. @@ -126,15 +234,17 @@ For example, to build a native image named `executable-name` that uses `org.example.ClassName` as its main class with assertions enabled, add the following `` block for the `native-maven-plugin`. -```xml +[source,xml] +---- executable-name org.example.ClassName - - --no-fallback - + false + true -``` +---- + +NOTE: Most of the aforementioned properties can also be set from command line as a part of Maven invocation -- for example if you want to temporarily enable verbose mode you can append `-Dverbose` to your Maven invocation. NOTE: If you use GraalVM Enterprise as the `JAVA_HOME` environment, the plugin builds a native image with enterprise features enabled -- for example, an executable will automatically be built with https://medium.com/graalvm/isolates-and-compressed-references-more-flexible-and-efficient-memory-management-for-graalvm-a044cc50b67e[compressed references] and other optimizations enabled. @@ -152,7 +262,7 @@ The `` element can be combined between parent and children POMs. Supp ${project.artifactId} ${exec.mainClass} - --no-fallback + --no-fallback @@ -323,7 +433,6 @@ Depending on the other plugins your build uses (typically the Spring Boot plugin ... ---- - To be able to <>, you will need more setup: - Create a `src/assembly/test-jar-with-dependencies.xml` file with the following contents: diff --git a/docs/src/docs/snippets/gradle/groovy/build.gradle b/docs/src/docs/snippets/gradle/groovy/build.gradle index b83d64ad0..69f7e21c2 100644 --- a/docs/src/docs/snippets/gradle/groovy/build.gradle +++ b/docs/src/docs/snippets/gradle/groovy/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * 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 @@ -119,9 +119,11 @@ graalvmNative { verbose = true // Add verbose output, defaults to false fallback = true // Sets the fallback mode of native-image, defaults to false sharedLibrary = false // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included + quickBuild = false // Determines if image is being built in quick build mode systemProperties = [name1: 'value1', name2: 'value2'] // Sets the system properties to use for the native image builder configurationFileDirectories.from(file('src/my-config')) // Adds a native image configuration file directory, containing files like reflection configuration + excludeConfig.put("org.example.test", ["META-INF/native-image/*", "config/*"]) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates. // Advanced options buildArgs.add('-H:Extra') // Passes '-H:Extra' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension diff --git a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts index 70380760d..2d001fde5 100644 --- a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts +++ b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. All rights reserved. + * 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 @@ -77,9 +77,11 @@ graalvmNative { verbose.set(true) // Add verbose output, defaults to false fallback.set(true) // Sets the fallback mode of native-image, defaults to false sharedLibrary.set(false) // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included + quickBuild.set(false) // Determines if image is being built in quick build mode systemProperties.putAll(mapOf("name1" to "value1", "name2" to "value2")) // Sets the system properties to use for the native image builder configurationFileDirectories.from(file("src/my-config")) // Adds a native image configuration file directory, containing files like reflection configuration + excludeConfig.put("org.example.test", listOf("META-INF/native-image/*", "config/*")) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates. // Advanced options buildArgs.add("-H:Extra") // Passes '-H:Extra' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 7335ba8ca..85f4514b2 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -56,6 +56,7 @@ import org.gradle.api.tasks.Optional; import org.gradle.jvm.toolchain.JavaLauncher; +import java.util.List; import java.util.Map; @@ -162,6 +163,14 @@ public interface NativeImageOptions extends Named { @Input Property getSharedLibrary(); + /** + * Gets the value which determines if image is being built in quick build mode. + * + * @return The value which determines if image is being built in quick build mode. + */ + @Input + Property getQuickBuild(); + /** * Returns the toolchain used to invoke native-image. Currently pointing * to a Java launcher due to Gradle limitations. @@ -180,14 +189,14 @@ public interface NativeImageOptions extends Named { ConfigurableFileCollection getConfigurationFileDirectories(); /** - * Returns the map that as contains information about configuration that should be excluded - * during image building. It consists of a jar regular expression as a key and a resource - * regular expression as a value. + * Returns the MapProperty that contains information about configuration that should be excluded + * during image building. It consists of a dependency coordinates and a list of + * regular expressions that match resources that should be excluded as a value. * * @return a map of filters for configuration exclusion */ @Input - MapProperty getExcludeConfig(); + MapProperty> getExcludeConfig(); @Nested NativeResourcesOptions getResources(); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java index ac4e4abc7..2900887b3 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -64,6 +64,7 @@ import javax.inject.Inject; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -178,7 +179,15 @@ public String getName() { public abstract Property getSharedLibrary(); /** - * Returns the toolchain used to invoke native-image. Currently pointing + * Gets the value which determines if image is being built in quick build mode. + * + * @return The value which determines if image is being built in quick build mode. + */ + @Input + public abstract Property getQuickBuild(); + + /** + * Returns the toolchain used to invoke native-image. Currently, pointing * to a Java launcher due to Gradle limitations. */ @Nested @@ -194,6 +203,16 @@ public String getName() { @InputFiles public abstract ConfigurableFileCollection getConfigurationFileDirectories(); + /** + * Returns the MapProperty that contains information about configuration that should be excluded + * during image building. It consists of a dependency coordinates and a list of + * regular expressions that match resources that should be excluded as a value. + * + * @return a map of filters for configuration exclusion + */ + @Input + public abstract MapProperty> getExcludeConfig(); + @Nested public abstract NativeResourcesOptions getResources(); @@ -212,6 +231,7 @@ public BaseNativeImageOptions(String name, getDebug().convention(false); getFallback().convention(false); getVerbose().convention(false); + getQuickBuild().convention(false); getSharedLibrary().convention(false); getImageName().convention(defaultImageName); getUseFatJar().convention(false); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java index 3c40684ce..be74da4eb 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java @@ -43,7 +43,9 @@ import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.utils.NativeImageUtils; +import org.gradle.api.Project; import org.gradle.api.Transformer; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.file.FileSystemLocation; import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; @@ -66,17 +68,20 @@ public class NativeImageCommandLineProvider implements CommandLineArgumentProvid private final Provider outputDirectory; private final Provider classpathJar; private final Provider useArgFile; + private final Project project; public NativeImageCommandLineProvider(Provider options, Provider executableName, Provider outputDirectory, Provider classpathJar, - Provider useArgFile) { + Provider useArgFile, + Project project) { this.options = options; this.executableName = executableName; this.outputDirectory = outputDirectory; this.classpathJar = classpathJar; this.useArgFile = useArgFile; + this.project = project; } @Nested @@ -104,19 +109,36 @@ public List asArguments() { NativeImageOptions options = getOptions().get(); List cliArgs = new ArrayList<>(20); - options.getExcludeConfig().get().forEach((jarPath, resourcePattern) -> { - cliArgs.add("--exclude-config"); - cliArgs.add(jarPath); - cliArgs.add(resourcePattern); + options.getExcludeConfig().get().forEach((dependency, listOfResourcePatterns) -> { + // Resolve jar for this dependency. + project.getConfigurations().getByName("runtimeClasspath").getIncoming().artifactView(view -> { + view.setLenient(true); + view.componentFilter(id -> { + if (id instanceof ModuleComponentIdentifier) { + ModuleComponentIdentifier mid = (ModuleComponentIdentifier) id; + String gav = String.format("%s:%s", + mid.getGroup(), + mid.getModule() + ); + return dependency.startsWith(gav); + } + return false; + }); + }).getFiles().forEach(jarPath -> listOfResourcePatterns.forEach(resourcePattern -> { + cliArgs.add("--exclude-config"); + cliArgs.add(jarPath.toPath().toAbsolutePath().toString()); + cliArgs.add(String.format("\"%s\"", resourcePattern)); + })); }); cliArgs.add("-cp"); String classpathString = buildClasspathString(options); cliArgs.add(classpathString); - appendBooleanOption(cliArgs, options.getDebug(), "-H:GenerateDebugInfo=1"); + appendBooleanOption(cliArgs, options.getDebug(), "-g"); appendBooleanOption(cliArgs, options.getFallback().map(NEGATE), "--no-fallback"); appendBooleanOption(cliArgs, options.getVerbose(), "--verbose"); appendBooleanOption(cliArgs, options.getSharedLibrary(), "--shared"); + appendBooleanOption(cliArgs, options.getQuickBuild(), "-Ob"); if (getOutputDirectory().isPresent()) { cliArgs.add("-H:Path=" + getOutputDirectory().get()); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index 2588c2f23..ebeedfdc5 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -155,7 +155,9 @@ private List buildActualCommandLineArgs() { // a mapped value before the task was called, when we are actually calling it... getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()), getClasspathJar(), - getUseArgFile()).asArguments(); + getUseArgFile(), + getProject() + ).asArguments(); } // This property provides access to the service instance @@ -165,7 +167,6 @@ private List buildActualCommandLineArgs() { public abstract Property getService(); @TaskAction - @SuppressWarnings("ConstantConditions") public void exec() { List args = buildActualCommandLineArgs(); NativeImageOptions options = getOptions().get(); diff --git a/native-maven-plugin/reproducers/issue-144/pom.xml b/native-maven-plugin/reproducers/issue-144/pom.xml index b2c07eaf8..a378ea30c 100644 --- a/native-maven-plugin/reproducers/issue-144/pom.xml +++ b/native-maven-plugin/reproducers/issue-144/pom.xml @@ -100,9 +100,7 @@ false ${imageName} - - --no-fallback - + false @@ -117,6 +115,13 @@ ${java.version} 1.8 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy index 8d2e40f15..021b3a16f 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationFunctionalTest.groovy @@ -44,6 +44,20 @@ package org.graalvm.buildtools.maven import spock.lang.Issue class JavaApplicationFunctionalTest extends AbstractGraalVMMavenFunctionalTest { + def "proper options are added to the native-image invocation"() { + withSample("java-application") + + when: + mvn '-Pnative', '-DskipTests', '-DnativeDryRun', '-DuseArgFile=false', + '-Dclasspath=/testcp', '-Ddebug', '-Dfallback=false', '-Dverbose', '-DsharedLibrary', + '-DquickBuild', + 'package' + + then: + buildSucceeded + outputContains "native-image -cp /testcp -g --no-fallback --verbose --shared -Ob" + } + def "can build and execute a native image with the Maven plugin"() { withSample("java-application") 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 b9e4e4527..42695b9b4 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 @@ -100,6 +100,18 @@ class MetadataRepositoryFunctionalTest extends AbstractGraalVMMavenFunctionalTes outputContains "[graalvm reachability metadata repository for org.graalvm.internal:library-with-reflection:1.5]: Configuration directory is org/graalvm/internal/library-with-reflection/1" } + void "if excludeConfig is set it is added to the command line invocation"() { + given: + withSample("native-config-integration") + + when: + mvn '-Pnative,metadataLocal,excludeConfigTest', '-DnativeDryRun', 'package' + + then: + buildSucceeded + outputContains "native-image --exclude-config dummy/path/to/file.jar \"*\"" + } + void "if the path doesn't exist it throws an error"() { given: withSample("native-config-integration") diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java index ba783b97f..7536419ba 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -60,14 +60,17 @@ public abstract class Utils implements SharedConstants { public static final String NATIVE_TESTS_EXE = "native-tests" + EXECUTABLE_EXTENSION; public static final String MAVEN_GROUP_ID = "org.graalvm.buildtools"; + public static Path nativeImageCache; + public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failFast) throws MojoExecutionException { String graalHome = System.getenv(javaHomeVariable); if (graalHome == null) { return null; } - Path graalExe = Paths.get(graalHome).resolve("bin").resolve(NATIVE_IMAGE_EXE); - Path guExe = Paths.get(graalHome).resolve("bin").resolve(GU_EXE); + Path graalHomePath = Paths.get(graalHome); + Path graalExe = graalHomePath.resolve("bin").resolve(NATIVE_IMAGE_EXE); + Path guExe = graalHomePath.resolve("bin").resolve(GU_EXE); if (!Files.exists(graalExe)) { if (Files.exists(guExe)) { @@ -95,6 +98,7 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF return null; } } + System.out.println("Found GraalVM installation from " + javaHomeVariable + " variable."); return graalExe; } @@ -107,14 +111,21 @@ public static Path getNativeImageFromPath() { } public static Path getNativeImage() throws MojoExecutionException { - Path nativeImage = getJavaHomeNativeImage("JAVA_HOME", false); + if (nativeImageCache != null) { + return nativeImageCache; + } + + Path nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", false); if (nativeImage == null) { - nativeImage = getNativeImageFromPath(); + nativeImage = getJavaHomeNativeImage("JAVA_HOME", true); } if (nativeImage == null) { - nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", true); + nativeImage = getNativeImageFromPath(); + if (nativeImage != null) { + System.out.println("Found GraalVM installation from PATH variable."); + } } if (nativeImage == null) { @@ -122,6 +133,7 @@ public static Path getNativeImage() throws MojoExecutionException { "Make sure that GRAALVM_HOME environment variable is present."); } + nativeImageCache = nativeImage; return nativeImage; } } 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 6d65b2729..4abdc56c6 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 @@ -120,7 +120,7 @@ public abstract class AbstractNativeMojo extends AbstractMojo { @Parameter(property = "classpath") protected List classpath; - @Parameter + @Parameter(property = "classesDirectory") protected File classesDirectory; @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true) @@ -142,6 +142,9 @@ public abstract class AbstractNativeMojo extends AbstractMojo { @Parameter(property = "sharedLibrary", defaultValue = "false") protected boolean sharedLibrary; + @Parameter(property = "quickBuild", defaultValue = "false") + protected boolean quickBuild; + @Parameter(property = "useArgFile") protected Boolean useArgFile; @@ -172,6 +175,9 @@ public abstract class AbstractNativeMojo extends AbstractMojo { @Parameter(alias = "metadataRepository") protected MetadataRepositoryConfiguration metadataRepositoryConfiguration; + @Parameter(property = NATIVE_IMAGE_DRY_RUN, defaultValue = "false") + protected boolean dryRun; + protected JvmReachabilityMetadataRepository metadataRepository; @Component @@ -194,7 +200,7 @@ protected List getBuildArgs() throws MojoExecutionException { excludeConfig.forEach(entry -> { cliArgs.add("--exclude-config"); cliArgs.add(entry.getJarPath()); - cliArgs.add(entry.getResourcePattern()); + cliArgs.add(String.format("\"%s\"", entry.getResourcePattern())); }); } @@ -202,7 +208,7 @@ protected List getBuildArgs() throws MojoExecutionException { cliArgs.add(getClasspath()); if (debug) { - cliArgs.add("-H:GenerateDebugInfo=1"); + cliArgs.add("-g"); } if (!fallback) { cliArgs.add("--no-fallback"); @@ -213,6 +219,9 @@ protected List getBuildArgs() throws MojoExecutionException { if (sharedLibrary) { cliArgs.add("--shared"); } + if (quickBuild) { + cliArgs.add("-Ob"); + } cliArgs.add("-H:Path=" + outputDirectory.toPath().toAbsolutePath()); cliArgs.add("-H:Name=" + imageName); @@ -228,7 +237,7 @@ protected List getBuildArgs() throws MojoExecutionException { } maybeAddGeneratedResourcesConfig(buildArgs); - maybeAddReachabilityMetadata(cliArgs); + maybeAddReachabilityMetadata(configFiles); if (configFiles != null && !configFiles.isEmpty()) { cliArgs.add("-H:ConfigurationFileDirectories=" + @@ -356,12 +365,10 @@ protected void populateClasspath() throws MojoExecutionException { 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)); @@ -387,7 +394,7 @@ protected void buildImage() throws MojoExecutionException { String commandString = String.join(" ", processBuilder.command()); logger.info("Executing: " + commandString); - if (System.getProperty(NATIVE_IMAGE_DRY_RUN) != null) { + if (dryRun) { logger.warn("Skipped native-image building due to `" + NATIVE_IMAGE_DRY_RUN + "` being specified."); return; } @@ -408,12 +415,15 @@ protected void maybeAddGeneratedResourcesConfig(List into) { Stream.concat(dirs == null ? Stream.empty() : Arrays.stream(dirs), agentResourceDirectory == null ? Stream.empty() : Stream.of(agentResourceDirectory).filter(File::isDirectory)); - into.add("-H:ConfigurationFileDirectories=" + configDirs.map(File::getAbsolutePath).collect(Collectors.joining(","))); - 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"); + 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"); + } } } } @@ -460,16 +470,15 @@ public boolean isArtifactExcludedFromMetadataRepository(Artifact dependency) { } } - protected void maybeAddReachabilityMetadata(List args) { + protected void maybeAddReachabilityMetadata(List configDirs) { if (isMetadataRepositoryEnabled() && !metadataRepositoryPaths.isEmpty()) { String arg = metadataRepositoryPaths.stream() .map(Path::toAbsolutePath) .map(Path::toFile) .map(File::getAbsolutePath) .collect(Collectors.joining(",")); - if (!arg.isEmpty()) { - args.add("-H:ConfigurationFileDirectories=" + arg); + configDirs.add(arg); } } } diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java index 93e2dc174..a8aba158b 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java @@ -42,7 +42,6 @@ import org.apache.maven.plugin.AbstractMojo; 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; @@ -75,8 +74,8 @@ public class MergeAgentFilesMojo extends AbstractMojo { protected String context; @Override - public void execute() throws MojoExecutionException, MojoFailureException { - String agentOutputDirectory = agentOutputDirectoryFor(target, NativeExtension.Context.valueOf(context)); + public void execute() throws MojoExecutionException { + String agentOutputDirectory = agentOutputDirectoryFor(target, NativeExtension.Context.valueOf(context)); File baseDir = new File(agentOutputDirectory); if (baseDir.exists()) { Path nativeImageExecutable = Utils.getNativeImage(); @@ -124,6 +123,10 @@ private void invokeMerge(File mergerExecutable, List inputDirectories, Fil return; } try { + if (inputDirectories.isEmpty()) { + getLog().warn("Skipping merging of agent files since there are no input directories."); + return; + } getLog().info("Merging agent " + inputDirectories.size() + " files into " + outputDirectory); List args = new ArrayList<>(inputDirectories.size() + 2); args.add("generate"); diff --git a/samples/java-application-with-reflection/pom.xml b/samples/java-application-with-reflection/pom.xml index 84e09f336..92a5e7580 100644 --- a/samples/java-application-with-reflection/pom.xml +++ b/samples/java-application-with-reflection/pom.xml @@ -140,9 +140,7 @@ ${imageName} - - --no-fallback - + false @@ -202,6 +200,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/samples/java-application-with-resources/pom.xml b/samples/java-application-with-resources/pom.xml index fee38be0c..05ca66eb7 100644 --- a/samples/java-application-with-resources/pom.xml +++ b/samples/java-application-with-resources/pom.xml @@ -106,9 +106,7 @@ - - --no-fallback - + false @@ -132,6 +130,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/samples/java-application-with-tests/pom.xml b/samples/java-application-with-tests/pom.xml index eab6b2f30..018f9bba3 100644 --- a/samples/java-application-with-tests/pom.xml +++ b/samples/java-application-with-tests/pom.xml @@ -103,9 +103,7 @@ false ${imageName} - - --no-fallback - + false @@ -207,9 +205,7 @@ false false ${imageName} - - --no-fallback - + false @@ -253,6 +249,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/samples/java-application/pom.xml b/samples/java-application/pom.xml index 0b9661545..58cdbbb25 100644 --- a/samples/java-application/pom.xml +++ b/samples/java-application/pom.xml @@ -87,9 +87,7 @@ false ${imageName} - - --no-fallback - + false @@ -137,9 +135,7 @@ false false ${imageName} - - --no-fallback - + false ${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar @@ -167,6 +163,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/samples/java-library/pom.xml b/samples/java-library/pom.xml index 8d2829039..f635f3cb2 100644 --- a/samples/java-library/pom.xml +++ b/samples/java-library/pom.xml @@ -87,10 +87,8 @@ false ${imageName} - - --no-fallback - --shared - + false + true @@ -114,6 +112,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true diff --git a/samples/native-config-integration/build.gradle b/samples/native-config-integration/build.gradle index 6a4eda85c..e2991f615 100644 --- a/samples/native-config-integration/build.gradle +++ b/samples/native-config-integration/build.gradle @@ -78,6 +78,7 @@ graalvmNative { binaries.all { verbose = true runtimeArgs.add("-DmessageClass=org.graalvm.internal.reflect.Message") + excludeConfig.put("org.graalvm.internal:library-with-reflection:1.5", ["*"]) } } diff --git a/samples/native-config-integration/pom.xml b/samples/native-config-integration/pom.xml index 6848b6628..13b4bfb08 100644 --- a/samples/native-config-integration/pom.xml +++ b/samples/native-config-integration/pom.xml @@ -92,10 +92,8 @@ false ${imageName} - - --no-fallback - -Ob - + false + true @@ -139,9 +137,7 @@ false false ${imageName} - - --no-fallback - + false ${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar @@ -362,6 +358,26 @@ + + excludeConfigTest + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin.version} + + + + dummy/path/to/file.jar + * + + + + + + + @@ -380,6 +396,14 @@ ${java.version} 1.8 + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + true From 1f2d92fe504ad3491ad98b8485405fb299c767be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Thu, 9 Jun 2022 16:25:11 +0200 Subject: [PATCH 3/5] Fix review feedback --- docs/src/docs/asciidoc/index.adoc | 3 ++- .../main/java/org/graalvm/buildtools/Utils.java | 14 +++++++------- .../buildtools/maven/AbstractNativeMojo.java | 11 +++++++++-- .../buildtools/maven/MergeAgentFilesMojo.java | 8 +++++++- .../buildtools/maven/NativeExtension.java | 16 ++++++++++++---- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index 3d7129042..77781af30 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -28,8 +28,9 @@ If you are interested in contributing, please refer to our https://github.com/gr * Added `quickBuild` configuration option. ==== Maven plugin +* Added support for GraalVM Reachability Metadata Repository. * Completely reworked Maven plugin (should fix many of previous issues and inconsistencies between main and test builds). -* Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig`, `quickBuild`, and `jvmArgs` properties in order to match those present in the Gradle plugin. + +* Added `classesDirectory`, `debug`, `fallback`, `verbose`, `sharedLibrary`, `configurationFileDirectories`, `excludeConfig`, `quickBuild`, and `jvmArgs` properties in order to match those present in the Gradle plugin. + See <> for more information. * `useArgFile` is now set to true by default only on Windows. diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java index 7536419ba..857aae419 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/Utils.java @@ -42,6 +42,7 @@ package org.graalvm.buildtools; import org.apache.maven.plugin.MojoExecutionException; +import org.codehaus.plexus.logging.Logger; import org.graalvm.buildtools.utils.SharedConstants; import java.io.File; @@ -59,10 +60,9 @@ public abstract class Utils implements SharedConstants { public static final String NATIVE_TESTS_EXE = "native-tests" + EXECUTABLE_EXTENSION; public static final String MAVEN_GROUP_ID = "org.graalvm.buildtools"; - public static Path nativeImageCache; - public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failFast) throws MojoExecutionException { + public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failFast, Logger logger) throws MojoExecutionException { String graalHome = System.getenv(javaHomeVariable); if (graalHome == null) { return null; @@ -98,7 +98,7 @@ public static Path getJavaHomeNativeImage(String javaHomeVariable, Boolean failF return null; } } - System.out.println("Found GraalVM installation from " + javaHomeVariable + " variable."); + logger.info("Found GraalVM installation from " + javaHomeVariable + " variable."); return graalExe; } @@ -110,21 +110,21 @@ public static Path getNativeImageFromPath() { return exePath.map(path -> path.resolve(NATIVE_IMAGE_EXE)).orElse(null); } - public static Path getNativeImage() throws MojoExecutionException { + public static Path getNativeImage(Logger logger) throws MojoExecutionException { if (nativeImageCache != null) { return nativeImageCache; } - Path nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", false); + Path nativeImage = getJavaHomeNativeImage("GRAALVM_HOME", false, logger); if (nativeImage == null) { - nativeImage = getJavaHomeNativeImage("JAVA_HOME", true); + nativeImage = getJavaHomeNativeImage("JAVA_HOME", true, logger); } if (nativeImage == null) { nativeImage = getNativeImageFromPath(); if (nativeImage != null) { - System.out.println("Found GraalVM installation from PATH variable."); + logger.info("Found GraalVM installation from PATH variable."); } } 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 4abdc56c6..c2730ba30 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 @@ -88,7 +88,6 @@ */ 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"; @@ -219,6 +218,14 @@ protected List getBuildArgs() throws MojoExecutionException { 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.info("Quick build environment variable is set."); + quickBuild = quickBuildEnv.isEmpty() || Boolean.parseBoolean(quickBuildEnv); + } + if (quickBuild) { cliArgs.add("-Ob"); } @@ -375,7 +382,7 @@ protected String getClasspath() throws MojoExecutionException { } protected void buildImage() throws MojoExecutionException { - Path nativeImageExecutable = Utils.getNativeImage(); + Path nativeImageExecutable = Utils.getNativeImage(logger); try { ProcessBuilder processBuilder = new ProcessBuilder(nativeImageExecutable.toString()); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java index a8aba158b..ad4707015 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/MergeAgentFilesMojo.java @@ -42,10 +42,12 @@ import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.util.FileUtils; import org.graalvm.buildtools.Utils; import org.graalvm.buildtools.utils.NativeImageUtils; @@ -73,12 +75,16 @@ public class MergeAgentFilesMojo extends AbstractMojo { @Parameter(property = "native.agent.merge.context", required = true) protected String context; + @Component + protected Logger logger; + + @Override public void execute() throws MojoExecutionException { String agentOutputDirectory = agentOutputDirectoryFor(target, NativeExtension.Context.valueOf(context)); File baseDir = new File(agentOutputDirectory); if (baseDir.exists()) { - Path nativeImageExecutable = Utils.getNativeImage(); + Path nativeImageExecutable = Utils.getNativeImage(logger); File mergerExecutable = tryInstall(nativeImageExecutable); List sessionDirectories = sessionDirectoriesFrom(baseDir.listFiles()).collect(Collectors.toList()); invokeMerge(mergerExecutable, sessionDirectories, baseDir); diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeExtension.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeExtension.java index 68ed32b57..1caf2a3d0 100644 --- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeExtension.java +++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/NativeExtension.java @@ -42,7 +42,6 @@ package org.graalvm.buildtools.maven; import org.apache.maven.AbstractMavenLifecycleParticipant; -import org.apache.maven.MavenExecutionException; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Build; import org.apache.maven.model.Plugin; @@ -50,6 +49,8 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.component.annotations.Component; +import org.codehaus.plexus.logging.LogEnabled; +import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.graalvm.buildtools.Utils; import org.graalvm.buildtools.utils.SharedConstants; @@ -66,12 +67,19 @@ * the JUnit Platform test listener and registering the native dependency transparently. */ @Component(role = AbstractMavenLifecycleParticipant.class, hint = "native-build-tools") -public class NativeExtension extends AbstractMavenLifecycleParticipant { +public class NativeExtension extends AbstractMavenLifecycleParticipant implements LogEnabled { private static final String JUNIT_PLATFORM_LISTENERS_UID_TRACKING_ENABLED = "junit.platform.listeners.uid.tracking.enabled"; private static final String JUNIT_PLATFORM_LISTENERS_UID_TRACKING_OUTPUT_DIR = "junit.platform.listeners.uid.tracking.output.dir"; private static final String NATIVEIMAGE_IMAGECODE = "org.graalvm.nativeimage.imagecode"; + private Logger logger; + + @Override + public void enableLogging(Logger logger) { + this.logger = logger; + } + /** * Enumeration of execution contexts. *

Enum constants are intentionally lowercase for use as directory names @@ -106,7 +114,7 @@ static String agentOutputDirectoryFor(String baseDir, Context context) { } @Override - public void afterProjectsRead(MavenSession session) throws MavenExecutionException { + public void afterProjectsRead(MavenSession session) { for (MavenProject project : session.getProjects()) { Build build = project.getBuild(); withPlugin(build, "native-maven-plugin", nativePlugin -> { @@ -153,7 +161,7 @@ public void afterProjectsRead(MavenSession session) throws MavenExecutionExcepti } Xpp3Dom executable = findOrAppend(config, "executable"); try { - executable.setValue(Utils.getNativeImage().getParent().resolve("java").toString()); + executable.setValue(Utils.getNativeImage(logger).getParent().resolve("java").toString()); } catch (MojoExecutionException e) { throw new RuntimeException(e); } From 0afe2d8bbcab52e33d361786909add238591fe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Fri, 10 Jun 2022 00:29:44 +0200 Subject: [PATCH 4/5] Clean up excludeConfig processing in Gradle plugin --- .../NativeImageCommandLineProvider.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java index be74da4eb..a5018ffa0 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java @@ -104,11 +104,8 @@ public Provider getClasspathJar() { return classpathJar; } - @Override - public List asArguments() { - NativeImageOptions options = getOptions().get(); - List cliArgs = new ArrayList<>(20); - + private List buildExcludeConfigArgs(NativeImageOptions options) { + List args = new ArrayList<>(); options.getExcludeConfig().get().forEach((dependency, listOfResourcePatterns) -> { // Resolve jar for this dependency. project.getConfigurations().getByName("runtimeClasspath").getIncoming().artifactView(view -> { @@ -125,12 +122,19 @@ public List asArguments() { return false; }); }).getFiles().forEach(jarPath -> listOfResourcePatterns.forEach(resourcePattern -> { - cliArgs.add("--exclude-config"); - cliArgs.add(jarPath.toPath().toAbsolutePath().toString()); - cliArgs.add(String.format("\"%s\"", resourcePattern)); + args.add("--exclude-config"); + args.add(jarPath.toPath().toAbsolutePath().toString()); + args.add(String.format("\"%s\"", resourcePattern)); })); }); + return args; + } + @Override + public List asArguments() { + NativeImageOptions options = getOptions().get(); + List cliArgs = new ArrayList<>(20); + cliArgs.addAll(buildExcludeConfigArgs(options)); cliArgs.add("-cp"); String classpathString = buildClasspathString(options); cliArgs.add(classpathString); From bed8b18e30db3f67472f8360d7cc40a5ad5acc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Fri, 10 Jun 2022 11:36:26 +0200 Subject: [PATCH 5/5] Fix review feedback --- .../workflows/test-native-gradle-plugin.yml | 2 +- docs/src/docs/asciidoc/maven-plugin.adoc | 3 +- .../docs/snippets/gradle/groovy/build.gradle | 3 +- .../snippets/gradle/kotlin/build.gradle.kts | 3 +- .../buildtools/gradle/NativeImagePlugin.java | 48 +++++++++++++++++++ .../gradle/dsl/NativeImageOptions.java | 5 +- .../internal/BaseNativeImageOptions.java | 6 +-- .../gradle/internal/NativeConfigurations.java | 1 + .../NativeImageCommandLineProvider.java | 36 ++------------ .../gradle/tasks/BuildNativeImageTask.java | 13 +++-- .../buildtools/maven/AbstractNativeMojo.java | 2 +- .../native-config-integration/build.gradle | 1 + 12 files changed, 77 insertions(+), 46 deletions(-) diff --git a/.github/workflows/test-native-gradle-plugin.yml b/.github/workflows/test-native-gradle-plugin.yml index 7b8aed887..2e6ceefdc 100644 --- a/.github/workflows/test-native-gradle-plugin.yml +++ b/.github/workflows/test-native-gradle-plugin.yml @@ -53,7 +53,7 @@ jobs: strategy: fail-fast: false matrix: - gradle-version: ["current", "7.3.3", "6.7.1"] + gradle-version: ["current", "7.3.3", "7.2", "7.1", "6.8.3", "6.7.1"] graalvm-version: [ dev ] java-version: [ 11 ] os: [ ubuntu-20.04 ] diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc index 86efbd296..55a81ef59 100644 --- a/docs/src/docs/asciidoc/maven-plugin.adoc +++ b/docs/src/docs/asciidoc/maven-plugin.adoc @@ -158,11 +158,12 @@ Build Configuration]. It is also possible to customize the plugin within a true ---- ``:: - If you want to build the image using https://blogs.oracle.com/java/post/graalvm-enterprise-221--faster-smarter-leaner[quick build mode], supply the following in the configuration of the plugin: + If you want to build the image using https://blogs.oracle.com/java/post/graalvm-enterprise-221--faster-smarter-leaner[quick build mode], supply the following in the configuration of the plugin (alternatively set the `GRAALVM_QUICK_BUILD` environment variable to `true`): [source,xml] ---- true ---- + ``:: In order to exclude configuration from present jar files, specify: [source,xml] diff --git a/docs/src/docs/snippets/gradle/groovy/build.gradle b/docs/src/docs/snippets/gradle/groovy/build.gradle index 69f7e21c2..e1e1a37bc 100644 --- a/docs/src/docs/snippets/gradle/groovy/build.gradle +++ b/docs/src/docs/snippets/gradle/groovy/build.gradle @@ -119,11 +119,12 @@ graalvmNative { verbose = true // Add verbose output, defaults to false fallback = true // Sets the fallback mode of native-image, defaults to false sharedLibrary = false // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included - quickBuild = false // Determines if image is being built in quick build mode + quickBuild = false // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable) systemProperties = [name1: 'value1', name2: 'value2'] // Sets the system properties to use for the native image builder configurationFileDirectories.from(file('src/my-config')) // Adds a native image configuration file directory, containing files like reflection configuration excludeConfig.put("org.example.test", ["META-INF/native-image/*", "config/*"]) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates. + excludeConfig.put(file("path/to/artifact.jar"), listOf("META-INF/native-image/*", "config/*")) // Advanced options buildArgs.add('-H:Extra') // Passes '-H:Extra' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension diff --git a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts index 2d001fde5..e3c7e5ce9 100644 --- a/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts +++ b/docs/src/docs/snippets/gradle/kotlin/build.gradle.kts @@ -77,11 +77,12 @@ graalvmNative { verbose.set(true) // Add verbose output, defaults to false fallback.set(true) // Sets the fallback mode of native-image, defaults to false sharedLibrary.set(false) // Determines if image is a shared library, defaults to false if `java-library` plugin isn't included - quickBuild.set(false) // Determines if image is being built in quick build mode + quickBuild.set(false) // Determines if image is being built in quick build mode (alternatively use GRAALVM_QUICK_BUILD environment variable) systemProperties.putAll(mapOf("name1" to "value1", "name2" to "value2")) // Sets the system properties to use for the native image builder configurationFileDirectories.from(file("src/my-config")) // Adds a native image configuration file directory, containing files like reflection configuration excludeConfig.put("org.example.test", listOf("META-INF/native-image/*", "config/*")) // Excludes configuration that matches one of given regexes from JAR of dependency with said coordinates. + excludeConfig.put(file("path/to/artifact.jar"), listOf("META-INF/native-image/*", "config/*")) // Advanced options buildArgs.add("-H:Extra") // Passes '-H:Extra' to the native image builder options. This can be used to pass parameters which are not directly supported by this extension 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 6673e0c23..d9cb26419 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 @@ -74,6 +74,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.file.ArchiveOperations; @@ -114,6 +115,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -572,10 +574,12 @@ private static NativeImageOptions createMainOptions(GraalVMExtension graalExtens "main", JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME ); + setupExtensionConfigExcludes(buildExtension, configs); buildExtension.getClasspath().from(configs.getImageClasspathConfiguration()); return buildExtension; } + private static NativeImageOptions createTestOptions(GraalVMExtension graalExtension, String binaryName, Project project, @@ -587,6 +591,8 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens binaryName, JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME ); + setupExtensionConfigExcludes(testExtension, configs); + testExtension.getMainClass().set("org.graalvm.junit.platform.NativeImageJUnitLauncher"); testExtension.getMainClass().finalizeValue(); testExtension.getImageName().convention(mainExtension.getImageName().map(name -> name + SharedConstants.NATIVE_TESTS_SUFFIX)); @@ -601,6 +607,48 @@ private static NativeImageOptions createTestOptions(GraalVMExtension graalExtens return testExtension; } + private static void addExcludeConfigArg(List args, Path jarPath, List listOfResourcePatterns) { + listOfResourcePatterns.forEach(resourcePattern -> { + args.add("--exclude-config"); + args.add(jarPath.toAbsolutePath().toString()); + args.add(String.format("\"%s\"", resourcePattern)); + }); + } + + private static void setupExtensionConfigExcludes(NativeImageOptions options, NativeConfigurations configs) { + options.getExcludeConfigArgs().set(options.getExcludeConfig().map(excludedConfig -> { + List args = new ArrayList<>(); + excludedConfig.forEach((entry, listOfResourcePatterns) -> { + if (entry instanceof File) { + addExcludeConfigArg(args, ((File) entry).toPath(), listOfResourcePatterns); + } else if (entry instanceof String) { + String dependency = (String) entry; + // Resolve jar for this dependency. + configs.getImageClasspathConfiguration().getIncoming().artifactView(view -> { + view.setLenient(true); + view.componentFilter(id -> { + if (id instanceof ModuleComponentIdentifier) { + ModuleComponentIdentifier mid = (ModuleComponentIdentifier) id; + String gav = String.format("%s:%s:%s", + mid.getGroup(), + mid.getModule(), + mid.getVersion() + ); + return gav.startsWith(dependency); + } + return false; + }); + }).getFiles().getFiles().stream() + .map(File::toPath) + .forEach(jarPath -> addExcludeConfigArg(args, jarPath, listOfResourcePatterns)); + } else { + throw new UnsupportedOperationException("Expected File or GAV coordinate for excludeConfig option, got " + entry.getClass()); + } + }); + return args; + })); + } + private static List agentSessionDirectories(Directory outputDirectory) { return Arrays.stream(outputDirectory.getAsFile().listFiles(file -> file.isDirectory() && file.getName().startsWith("session-"))).map(File::getAbsolutePath).collect(Collectors.toList()); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java index 85f4514b2..d026e3aba 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/dsl/NativeImageOptions.java @@ -196,7 +196,7 @@ public interface NativeImageOptions extends Named { * @return a map of filters for configuration exclusion */ @Input - MapProperty> getExcludeConfig(); + MapProperty> getExcludeConfig(); @Nested NativeResourcesOptions getResources(); @@ -292,4 +292,7 @@ public interface NativeImageOptions extends Named { DeprecatedAgentOptions getAgent(); void agent(Action spec); + + @Input + ListProperty getExcludeConfigArgs(); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java index 2900887b3..500db57d9 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/BaseNativeImageOptions.java @@ -205,13 +205,13 @@ public String getName() { /** * Returns the MapProperty that contains information about configuration that should be excluded - * during image building. It consists of a dependency coordinates and a list of - * regular expressions that match resources that should be excluded as a value. + * during image building. It consists of dependency coordinates and a list of + * regular expressions that match resources that should be excluded. * * @return a map of filters for configuration exclusion */ @Input - public abstract MapProperty> getExcludeConfig(); + public abstract MapProperty> getExcludeConfig(); @Nested public abstract NativeResourcesOptions getResources(); diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java index 924ea0b75..c611980fb 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeConfigurations.java @@ -58,4 +58,5 @@ public Configuration getImageCompileOnlyConfiguration() { public Configuration getImageClasspathConfiguration() { return imageClasspath; } + } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java index a5018ffa0..6012345bb 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/internal/NativeImageCommandLineProvider.java @@ -43,9 +43,7 @@ import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.graalvm.buildtools.utils.NativeImageUtils; -import org.gradle.api.Project; import org.gradle.api.Transformer; -import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.file.FileSystemLocation; import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; @@ -68,20 +66,17 @@ public class NativeImageCommandLineProvider implements CommandLineArgumentProvid private final Provider outputDirectory; private final Provider classpathJar; private final Provider useArgFile; - private final Project project; public NativeImageCommandLineProvider(Provider options, Provider executableName, Provider outputDirectory, Provider classpathJar, - Provider useArgFile, - Project project) { + Provider useArgFile) { this.options = options; this.executableName = executableName; this.outputDirectory = outputDirectory; this.classpathJar = classpathJar; this.useArgFile = useArgFile; - this.project = project; } @Nested @@ -104,37 +99,11 @@ public Provider getClasspathJar() { return classpathJar; } - private List buildExcludeConfigArgs(NativeImageOptions options) { - List args = new ArrayList<>(); - options.getExcludeConfig().get().forEach((dependency, listOfResourcePatterns) -> { - // Resolve jar for this dependency. - project.getConfigurations().getByName("runtimeClasspath").getIncoming().artifactView(view -> { - view.setLenient(true); - view.componentFilter(id -> { - if (id instanceof ModuleComponentIdentifier) { - ModuleComponentIdentifier mid = (ModuleComponentIdentifier) id; - String gav = String.format("%s:%s", - mid.getGroup(), - mid.getModule() - ); - return dependency.startsWith(gav); - } - return false; - }); - }).getFiles().forEach(jarPath -> listOfResourcePatterns.forEach(resourcePattern -> { - args.add("--exclude-config"); - args.add(jarPath.toPath().toAbsolutePath().toString()); - args.add(String.format("\"%s\"", resourcePattern)); - })); - }); - return args; - } - @Override public List asArguments() { NativeImageOptions options = getOptions().get(); List cliArgs = new ArrayList<>(20); - cliArgs.addAll(buildExcludeConfigArgs(options)); + cliArgs.addAll(options.getExcludeConfigArgs().get()); cliArgs.add("-cp"); String classpathString = buildClasspathString(options); cliArgs.add(classpathString); @@ -143,6 +112,7 @@ public List asArguments() { appendBooleanOption(cliArgs, options.getVerbose(), "--verbose"); appendBooleanOption(cliArgs, options.getSharedLibrary(), "--shared"); appendBooleanOption(cliArgs, options.getQuickBuild(), "-Ob"); + if (getOutputDirectory().isPresent()) { cliArgs.add("-H:Path=" + getOutputDirectory().get()); } diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java index ebeedfdc5..565c153cc 100644 --- a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java +++ b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/tasks/BuildNativeImageTask.java @@ -155,9 +155,7 @@ private List buildActualCommandLineArgs() { // a mapped value before the task was called, when we are actually calling it... getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()), getClasspathJar(), - getUseArgFile(), - getProject() - ).asArguments(); + getUseArgFile()).asArguments(); } // This property provides access to the service instance @@ -168,9 +166,16 @@ private List buildActualCommandLineArgs() { @TaskAction public void exec() { - List args = buildActualCommandLineArgs(); NativeImageOptions options = getOptions().get(); GraalVMLogger logger = GraalVMLogger.of(getLogger()); + + String quickBuildEnv = System.getenv("GRAALVM_QUICK_BUILD"); + if (quickBuildEnv != null) { + logger.warn("Quick build environment variable is set."); + options.getQuickBuild().set(quickBuildEnv.isEmpty() || Boolean.parseBoolean(quickBuildEnv)); + } + + List args = buildActualCommandLineArgs(); if (options.getVerbose().get()) { logger.lifecycle("Args are: " + args); } 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 c2730ba30..045e85285 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 @@ -222,7 +222,7 @@ protected List getBuildArgs() throws MojoExecutionException { // Let's allow user to specify environment option to toggle quick build. String quickBuildEnv = System.getenv("GRAALVM_QUICK_BUILD"); if (quickBuildEnv != null) { - logger.info("Quick build environment variable is set."); + logger.warn("Quick build environment variable is set."); quickBuild = quickBuildEnv.isEmpty() || Boolean.parseBoolean(quickBuildEnv); } diff --git a/samples/native-config-integration/build.gradle b/samples/native-config-integration/build.gradle index e2991f615..dc38d47af 100644 --- a/samples/native-config-integration/build.gradle +++ b/samples/native-config-integration/build.gradle @@ -79,6 +79,7 @@ graalvmNative { verbose = true runtimeArgs.add("-DmessageClass=org.graalvm.internal.reflect.Message") excludeConfig.put("org.graalvm.internal:library-with-reflection:1.5", ["*"]) + excludeConfig.put(file("test.jar"), ["META-INF/*"]) } }