From 61261070a7aa17986921598f723a39b59ef46505 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 18 Aug 2021 09:32:17 +0200 Subject: [PATCH 1/6] Use a classpath jar under Windows This commit introduces a classpath jar, a jar which contains no classes but only a Class-Path manifest entry, so that the classpath to `native-image` is passed this way instead of on the CLI. This would fix the issue of long classpath entries on Windows. Fixes #85 --- ...licationWithResourcesFunctionalTest.groovy | 5 ++++ .../buildtools/gradle/NativeImagePlugin.java | 28 ++++++++++++++++++- .../NativeImageCommandLineProvider.java | 18 ++++++++++-- .../gradle/tasks/BuildNativeImageTask.java | 10 +++++-- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithResourcesFunctionalTest.groovy b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithResourcesFunctionalTest.groovy index 83d1bd408..d6ba6a703 100644 --- a/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithResourcesFunctionalTest.groovy +++ b/native-gradle-plugin/src/functionalTest/groovy/org/graalvm/buildtools/gradle/JavaApplicationWithResourcesFunctionalTest.groovy @@ -53,6 +53,11 @@ class JavaApplicationWithResourcesFunctionalTest extends AbstractFunctionalTest withSample("java-application-with-resources") buildFile << config + buildFile << """ + nativeBuild { + verbose = true + } + """ when: run 'nativeBuild' 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 61cc5390c..f479a697c 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 @@ -47,10 +47,10 @@ import org.graalvm.buildtools.gradle.internal.GraalVMLogger; import org.graalvm.buildtools.gradle.internal.GradleUtils; import org.graalvm.buildtools.gradle.internal.ProcessGeneratedGraalResourceFiles; -import org.graalvm.buildtools.utils.SharedConstants; import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask; import org.graalvm.buildtools.gradle.tasks.GenerateResourcesConfigFile; import org.graalvm.buildtools.gradle.tasks.NativeRunTask; +import org.graalvm.buildtools.utils.SharedConstants; import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; @@ -59,6 +59,7 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemLocation; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; @@ -69,6 +70,8 @@ import org.gradle.api.tasks.TaskCollection; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.process.JavaForkOptions; @@ -76,8 +79,10 @@ import java.io.File; import java.util.Arrays; +import java.util.Locale; import java.util.concurrent.Callable; import java.util.function.Consumer; +import java.util.stream.Collectors; import static org.graalvm.buildtools.gradle.internal.GradleUtils.findConfiguration; import static org.graalvm.buildtools.gradle.internal.GradleUtils.findMainArtifacts; @@ -148,6 +153,7 @@ private void configureJavaProject(Project project, Provider task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get())); task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs()); }); + configureClasspathJarFor(tasks, buildExtension, imageBuilder); TaskProvider processAgentFiles = registerProcessAgentFilesTask(project, PROCESS_AGENT_RESOURCES_TASK_NAME); @@ -217,6 +223,7 @@ private void configureJavaProject(Project project, Provider testExtension.getClasspath().from(testList); task.getAgentEnabled().set(testAgent); }); + configureClasspathJarFor(tasks, testExtension, testImageBuilder); tasks.register(NATIVE_TEST_TASK_NAME, NativeRunTask.class, task -> { task.setDescription("Runs native-image compiled tests."); @@ -226,6 +233,25 @@ private void configureJavaProject(Project project, Provider }); } + private void configureClasspathJarFor(TaskContainer tasks, NativeImageOptions options, TaskProvider imageBuilder) { + String baseName = imageBuilder.getName(); + TaskProvider classpathJar = tasks.register(baseName + "ClasspathJar", Jar.class, jar -> { + jar.manifest(mn -> mn.getAttributes().put("Class-Path", options.getClasspath() + .getElements() + .map(cp -> cp.stream() + .map(FileSystemLocation::getAsFile) + .map(File::getAbsolutePath) + .collect(Collectors.joining(" ")) + ))); + jar.getArchiveBaseName().set(baseName.toLowerCase(Locale.ENGLISH) + "-classpath"); + }); + imageBuilder.configure(nit -> { + if (SharedConstants.IS_WINDOWS) { + nit.getClasspathJar().set(classpathJar.flatMap(AbstractArchiveTask::getArchiveFile)); + } + }); + } + private TaskProvider registerResourcesConfigTask(Provider generatedDir, NativeImageOptions options, TaskContainer tasks, 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 67e51f3fc..ef9691497 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 @@ -44,8 +44,10 @@ import org.graalvm.buildtools.gradle.dsl.NativeImageOptions; import org.gradle.api.Transformer; import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Nested; import org.gradle.process.CommandLineArgumentProvider; @@ -62,15 +64,18 @@ public class NativeImageCommandLineProvider implements CommandLineArgumentProvid private final Provider agentEnabled; private final Provider executableName; private final Provider outputDirectory; + private final Provider classpathJar; public NativeImageCommandLineProvider(Provider options, Provider agentEnabled, Provider executableName, - Provider outputDirectory) { + Provider outputDirectory, + Provider classpathJar) { this.options = options; this.agentEnabled = agentEnabled; this.executableName = executableName; this.outputDirectory = outputDirectory; + this.classpathJar = classpathJar; } @Nested @@ -93,13 +98,22 @@ public Provider getOutputDirectory() { return outputDirectory; } + @InputFile + public Provider getClasspathJar() { + return classpathJar; + } + @Override public List asArguments() { NativeImageOptions options = getOptions().get(); List cliArgs = new ArrayList<>(20); cliArgs.add("-cp"); - cliArgs.add(options.getClasspath().getAsPath()); + if (classpathJar.isPresent()) { + cliArgs.add(classpathJar.get().getAsFile().getAbsolutePath()); + } else { + cliArgs.add(options.getClasspath().getAsPath()); + } appendBooleanOption(cliArgs, options.getDebug(), "-H:GenerateDebugInfo=1"); appendBooleanOption(cliArgs, options.getFallback().map(NEGATE), "--no-fallback"); 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 51ffd49d3..f7ecd1b2f 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 @@ -49,10 +49,12 @@ import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; @@ -112,6 +114,10 @@ public Provider getOutputFile() { @Inject protected abstract ProviderFactory getProviders(); + @InputFile + @Optional + public abstract RegularFileProperty getClasspathJar(); + public BuildNativeImageTask() { DirectoryProperty buildDir = getProject().getLayout().getBuildDirectory(); Provider outputDir = buildDir.dir("native/" + getName()); @@ -132,8 +138,8 @@ private List buildActualCommandLineArgs() { getExecutableName(), // Can't use getOutputDirectory().map(...) because Gradle would complain that we use // a mapped value before the task was called, when we are actually calling it... - getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()) - ).asArguments(); + getProviders().provider(() -> getOutputDirectory().getAsFile().get().getAbsolutePath()), + getClasspathJar()).asArguments(); } // This property provides access to the service instance From 5fe687fcb3c47d3017f940a74189fb3101b70f50 Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 18 Aug 2021 11:14:51 +0200 Subject: [PATCH 2/6] Use a fatjar instead of a pathing jar This commit reworks the fix to use a fat jar instead of a pathing jar to fix the long classpath issue under Windows. The reason is that the `native-image` tool will nevertheless expand the pathing jar classpath on another CLI invocation. --- .../buildtools/gradle/NativeImagePlugin.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java b/native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java index f479a697c..c9c03dd6e 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 @@ -55,11 +55,12 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.ArchiveOperations; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.file.FileCollection; -import org.gradle.api.file.FileSystemLocation; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; @@ -77,6 +78,7 @@ import org.gradle.process.JavaForkOptions; import org.gradle.util.GFileUtils; +import javax.inject.Inject; import java.io.File; import java.util.Arrays; import java.util.Locale; @@ -117,6 +119,11 @@ public class NativeImagePlugin implements Plugin { private GraalVMLogger logger; + @Inject + public ArchiveOperations getArchiveOperations() { + throw new UnsupportedOperationException(); + } + @Override public void apply(Project project) { Provider nativeImageServiceProvider = NativeImageService.registerOn(project); @@ -236,13 +243,14 @@ private void configureJavaProject(Project project, Provider private void configureClasspathJarFor(TaskContainer tasks, NativeImageOptions options, TaskProvider imageBuilder) { String baseName = imageBuilder.getName(); TaskProvider classpathJar = tasks.register(baseName + "ClasspathJar", Jar.class, jar -> { - jar.manifest(mn -> mn.getAttributes().put("Class-Path", options.getClasspath() - .getElements() - .map(cp -> cp.stream() - .map(FileSystemLocation::getAsFile) - .map(File::getAbsolutePath) - .collect(Collectors.joining(" ")) - ))); + jar.from( + options.getClasspath() + .getElements() + .map(elems -> elems.stream() + .map(e -> getArchiveOperations().zipTree(e)) + .collect(Collectors.toList())) + ); + jar.setDuplicatesStrategy(DuplicatesStrategy.WARN); jar.getArchiveBaseName().set(baseName.toLowerCase(Locale.ENGLISH) + "-classpath"); }); imageBuilder.configure(nit -> { From ea0993fecc04dc8ccfb5d96e5954658213d1976e Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 18 Aug 2021 11:24:14 +0200 Subject: [PATCH 3/6] Make the fat jar a default under Windows, but configurable --- .../graalvm/buildtools/gradle/NativeImagePlugin.java | 2 +- .../buildtools/gradle/dsl/NativeImageOptions.java | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) 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 c9c03dd6e..4fca359f4 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 @@ -254,7 +254,7 @@ private void configureClasspathJarFor(TaskContainer tasks, NativeImageOptions op jar.getArchiveBaseName().set(baseName.toLowerCase(Locale.ENGLISH) + "-classpath"); }); imageBuilder.configure(nit -> { - if (SharedConstants.IS_WINDOWS) { + if (options.getUseFatJar().get()) { nit.getClasspathJar().set(classpathJar.flatMap(AbstractArchiveTask::getArchiveFile)); } }); 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 29f8e6e9b..839af3ff9 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 @@ -42,6 +42,7 @@ package org.graalvm.buildtools.gradle.dsl; import org.graalvm.buildtools.gradle.internal.GradleUtils; +import org.graalvm.buildtools.utils.SharedConstants; import org.gradle.api.Action; import org.gradle.api.JavaVersion; import org.gradle.api.Project; @@ -210,6 +211,7 @@ public NativeImageOptions(ObjectFactory objectFactory, getAgent().convention(false); getSharedLibrary().convention(false); getImageName().convention(defaultImageName); + getUseFatJar().convention(SharedConstants.IS_WINDOWS); getJavaLauncher().convention( toolchains.launcherFor(spec -> { spec.getLanguageVersion().set(JavaLanguageVersion.of(JavaVersion.current().getMajorVersion())); @@ -355,4 +357,14 @@ public NativeImageOptions runtimeArgs(Iterable arguments) { return this; } + /** + * If set to true, this will build a fat jar of the image classpath + * instead of passing each jar individually to the classpath. This + * option can be used in case the classpath is too long and that + * invoking native image fails, which can happen on Windows. + * + * @return true if a fatjar should be used. Defaults to true for Windows, and false otherwise. + */ + @Input + public abstract Property getUseFatJar(); } From a8e21d387a991df92d9bb106e72c6bee33f7c41a Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 18 Aug 2021 11:35:02 +0200 Subject: [PATCH 4/6] Document the classpath jar --- docs/src/docs/asciidoc/gradle-plugin.adoc | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/src/docs/asciidoc/gradle-plugin.adoc b/docs/src/docs/asciidoc/gradle-plugin.adoc index 01ba62cf5..c253beb64 100644 --- a/docs/src/docs/asciidoc/gradle-plugin.adoc +++ b/docs/src/docs/asciidoc/gradle-plugin.adoc @@ -159,6 +159,8 @@ nativeBuild { // Development options agent = true // Enables the reflection agent. Can be also set on command line using '-Pagent' + + useFatJar = true // Instead of passing each jar individually, builds a fat jar } ``` @@ -185,11 +187,55 @@ nativeBuild { // Development options agent.set(true) // Enables the reflection agent. Can be also set on command line using '-Pagent' + + + useFatJar.set(true) // Instead of passing each jar individually, builds a fat jar } ``` NOTE: For options that can be set using command-line, if both DSL and command-line options are present, command-line options take precedence. +==== Long classpath and fat jar support + +Under Windows, https://github.com/graalvm/native-build-tools/issues/85[it is possible that the length of the classpath exceeds what the operating system supports] when invoking the CLI to build a native image. +As a consequence, if you are running under Windows, the plugin will automatically shorten the classpath of your project by building a so called "fat jar", which includes all entries from the classpath automatically. + +In case this behavior is not required, you can disable the fat jar creation by calling: + +.Disabling the fat jar creation +[role="multi-language-sample"] +```groovy +nativeBuild { + useFatJar = false +} +``` + +[role="multi-language-sample"] +```kotlin +nativeBuild { + useFatJar.set(false) +} +``` + +Alternatively, it is possible to use your own fat jar (for example created using the https://imperceptiblethoughts.com/shadow/[Shadow plugin]) by setting the `classpathJar` property directly on the _task_: + +.Disabling the fat jar creation +[role="multi-language-sample"] +```groovy +tasks.named("nativeBuild") { + classpathJar = myFatJar +} +``` + +[role="multi-language-sample"] +```kotlin +tasks.named("nativeBuild") { + classpathJar.set(myFatJar) +} +``` + +When the `classpathJar` property is set, the `classpath` property is _ignored_. + [[testing]] === Testing support From f6b47e90ec6c5e67bf0d976be813b2d83624848d Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Wed, 18 Aug 2021 16:18:11 +0200 Subject: [PATCH 5/6] Add an option to use shading with the Maven plugin --- docs/src/docs/asciidoc/maven-plugin.adoc | 18 +++++++ .../JavaApplicationFunctionalTest.groovy | 11 ++++ .../buildtools/maven/NativeBuildMojo.java | 26 ++++++---- samples/java-application/pom.xml | 52 +++++++++++++++++++ 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc index f3f5180e5..de90f2b61 100644 --- a/docs/src/docs/asciidoc/maven-plugin.adoc +++ b/docs/src/docs/asciidoc/maven-plugin.adoc @@ -147,6 +147,24 @@ This step won't be needed as of JUnit 5.8 with a future release of native-maven- Running `mvn -Pnative -Dskip test` will then build and run native tests. +== Long classpath and shading support + +Under Windows, https://github.com/graalvm/native-build-tools/issues/85[it is possible that the length of the classpath exceeds what the operating system supports] when invoking the CLI to build a native image. + +If this happens, one option is to use a https://maven.apache.org/plugins/maven-shade-plugin[shaded jar] and use it instead of individual jars on classpath. + +First, you'll need to setup the https://maven.apache.org/plugins/maven-shade-plugin[Maven Shade plugin]: + +[source,xml,indent=0] +include::../../../../samples/java-application/pom.xml[tag=shade-plugin] + +Then the native plugin needs to be configured to use this jar instead of the full classpath: + +[source,xml,indent=0] +include::../../../../samples/java-application/pom.xml[tag=native-plugin] + +Please refer to the https://maven.apache.org/plugins/maven-shade-plugin[Maven Shade plugin documentation] for more details on how to configure shading. + == Javadocs In addition, you can consult the link:javadocs/native-maven-plugin/index.html[Javadocs of the plugin]. 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 88e60ff63..905c87b58 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 @@ -52,4 +52,15 @@ class JavaApplicationFunctionalTest extends AbstractGraalVMMavenFunctionalTest { buildSucceeded outputContains "Hello, native!" } + + def "can build and execute a native image with the Maven plugin and the shade plugin"() { + withSample("java-application") + + when: + mvn '-Pshaded', '-DskipTests', 'package', 'exec:exec@native' + + then: + buildSucceeded + outputContains "Hello, native!" + } } 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 dc13e63f7..6d5c121f7 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 @@ -63,6 +63,7 @@ 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; @@ -94,7 +95,10 @@ public class NativeBuildMojo extends AbstractNativeMojo { @Parameter(defaultValue = "${mojoExecution}") private MojoExecution mojoExecution; - private final List classpath = new ArrayList<>(); + @Parameter(property = "classpath") + private List classpath; + + private final List imageClasspath = new ArrayList<>(); private PluginParameterExpressionEvaluator evaluator; @@ -105,14 +109,18 @@ public void execute() throws MojoExecutionException { } evaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); - classpath.clear(); - List imageClasspathScopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME); - project.setArtifactFilter(artifact -> imageClasspathScopes.contains(artifact.getScope())); - for (Artifact dependency : project.getArtifacts()) { - addClasspath(dependency); + 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); + } + addClasspath(project.getArtifact()); } - addClasspath(project.getArtifact()); - String classpathStr = classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)); + String classpathStr = imageClasspath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)); Path nativeImageExecutable = Utils.getNativeImage(); @@ -170,7 +178,7 @@ private void addClasspath(Artifact artifact) throws MojoExecutionException { throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath", e); } - classpath.add(jarFilePath); + imageClasspath.add(jarFilePath); } private Path getWorkingDirectory() { diff --git a/samples/java-application/pom.xml b/samples/java-application/pom.xml index 000a1ce14..ededfa220 100644 --- a/samples/java-application/pom.xml +++ b/samples/java-application/pom.xml @@ -95,6 +95,58 @@ + + shaded + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + true + + + + + + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin.version} + + + build-native + + build + + package + + + + false + ${imageName} + + --no-fallback + + + ${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar + + + + + + + From 2c286150aae177a08359a8d8f21a952609ba7d7f Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Thu, 19 Aug 2021 12:38:23 +0200 Subject: [PATCH 6/6] Demonstrate how to run tests in shaded mode with Maven Unfortunately, due to different bugs(?) in Maven, we cannot use the shading plugin for tests, and we have to move the test execution to the integration test phase. See https://issues.apache.org/jira/browse/MSHADE-402 --- docs/src/docs/asciidoc/maven-plugin.adoc | 26 +++++- ...aApplicationWithTestsFunctionalTest.groovy | 25 ++++++ .../buildtools/maven/NativeTestMojo.java | 6 ++ samples/java-application-with-tests/pom.xml | 89 ++++++++++++++++++- .../assembly/test-jar-with-dependencies.xml | 71 +++++++++++++++ 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 samples/java-application-with-tests/src/assembly/test-jar-with-dependencies.xml diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc index de90f2b61..90406896f 100644 --- a/docs/src/docs/asciidoc/maven-plugin.adoc +++ b/docs/src/docs/asciidoc/maven-plugin.adoc @@ -127,6 +127,7 @@ In this case, the arguments that will be passed to the `native-image` executable --no-fallback --verbose ``` +[[testing-support]] == JUnit Testing support In order to use the recommended test listener mode, you need to add following dependency: @@ -163,7 +164,30 @@ Then the native plugin needs to be configured to use this jar instead of the ful [source,xml,indent=0] include::../../../../samples/java-application/pom.xml[tag=native-plugin] -Please refer to the https://maven.apache.org/plugins/maven-shade-plugin[Maven Shade plugin documentation] for more details on how to configure shading. +To be able to <>, you will need more setup: + +- Create a `src/assembly/test-jar-with-dependencies.xml` file with the following contents: + +[source,xml,indent=0] +include::../../../../samples/java-application-with-tests/src/assembly/test-jar-with-dependencies.xml[tag=assembly] + +- Add the assembly plugin to your `native` profile: + +[source,xml,indent=0] +include::../../../../samples/java-application-with-tests/pom.xml[tag=assembly-plugin] + +- Due to a limitation in Maven, you will need to move the tests execution to the "integration-test" phase: + +[source,xml,indent=0] +include::../../../../samples/java-application-with-tests/pom.xml[tag=native-plugin] + +Finally, you will need to execute tests using the `integration-test` phase instead of `test`: + +```bash +./mvn -Pnative integration-test +``` + +Please refer to the https://maven.apache.org/plugins/maven-shade-plugin[Maven Shade plugin documentation] for more details on how to configure shading and the https://maven.apache.org/plugins/maven-assembly-plugin[Maven Assembly plugin documentation] to tweak what to include in tests. == Javadocs diff --git a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationWithTestsFunctionalTest.groovy b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationWithTestsFunctionalTest.groovy index fc0d0569a..883ef47f6 100644 --- a/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationWithTestsFunctionalTest.groovy +++ b/native-maven-plugin/src/functionalTest/groovy/org/graalvm/buildtools/maven/JavaApplicationWithTestsFunctionalTest.groovy @@ -64,6 +64,31 @@ class JavaApplicationWithTestsFunctionalTest extends AbstractGraalVMMavenFunctio [ 0 tests aborted ] [ 6 tests successful ] [ 0 tests failed ] +""".trim() + } + + def "can run tests in a native image with the Maven plugin using shading"() { + withSample("java-application-with-tests") + + when: + mvn '-Pshaded', 'integration-test' + + then: + buildSucceeded + outputContains "[junit-platform-native] Running in 'test listener' mode" + outputContains """ +[ 3 containers found ] +[ 0 containers skipped ] +[ 3 containers started ] +[ 0 containers aborted ] +[ 3 containers successful ] +[ 0 containers failed ] +[ 6 tests found ] +[ 0 tests skipped ] +[ 6 tests started ] +[ 0 tests aborted ] +[ 6 tests successful ] +[ 0 tests failed ] """.trim() } } 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 e980242a9..0ac5e4b49 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 @@ -79,6 +79,9 @@ public class NativeTestMojo extends AbstractNativeMojo { @Parameter(property = "skipTests", defaultValue = "false") private boolean skipTests; + @Parameter(property = "classpath") + private List classpath; + @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skipTests) { @@ -182,6 +185,9 @@ private void runTests(Path targetFolder) throws MojoExecutionException { } 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")) diff --git a/samples/java-application-with-tests/pom.xml b/samples/java-application-with-tests/pom.xml index 6724b1bcc..c96affdb8 100644 --- a/samples/java-application-with-tests/pom.xml +++ b/samples/java-application-with-tests/pom.xml @@ -109,10 +109,97 @@ + + shaded + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + true + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4.1 + + + src/assembly/test-jar-with-dependencies.xml + + + + + make-test-jar + package + + single + + + + + + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin.version} + + + test-native + + test + + integration-test + + + ${project.build.directory}/${project.artifactId}-${project.version}-tests.jar + + + + + build-native + + build + + package + + + ${project.build.directory}/${project.artifactId}-${project.version}-shaded.jar + + + + + + false + ${imageName} + + --no-fallback + + + + + + + - ${project.artifactId} org.apache.maven.plugins diff --git a/samples/java-application-with-tests/src/assembly/test-jar-with-dependencies.xml b/samples/java-application-with-tests/src/assembly/test-jar-with-dependencies.xml new file mode 100644 index 000000000..319f48403 --- /dev/null +++ b/samples/java-application-with-tests/src/assembly/test-jar-with-dependencies.xml @@ -0,0 +1,71 @@ + + + + + + tests + + jar + + + + ${project.build.directory}/test-classes + / + + + ${project.build.outputDirectory} + / + + + false + + + / + true + true + test + + + +