Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/src/docs/asciidoc/gradle-plugin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```

Expand All @@ -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<BuildNativeImageTask>("nativeBuild") {
classpathJar.set(myFatJar)
}
```

When the `classpathJar` property is set, the `classpath` property is _ignored_.

[[testing]]
=== Testing support

Expand Down
42 changes: 42 additions & 0 deletions docs/src/docs/asciidoc/maven-plugin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -147,6 +148,47 @@ 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]

To be able to <<testing-support,execute tests in native mode>>, 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

In addition, you can consult the link:javadocs/native-maven-plugin/index.html[Javadocs of the plugin].
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class JavaApplicationWithResourcesFunctionalTest extends AbstractFunctionalTest
withSample("java-application-with-resources")

buildFile << config
buildFile << """
nativeBuild {
verbose = true
}
"""

when:
run 'nativeBuild'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@
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;
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.plugins.ApplicationPlugin;
import org.gradle.api.plugins.JavaApplication;
Expand All @@ -69,15 +71,20 @@
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;
import org.gradle.util.GFileUtils;

import javax.inject.Inject;
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;
Expand Down Expand Up @@ -112,6 +119,11 @@ public class NativeImagePlugin implements Plugin<Project> {

private GraalVMLogger logger;

@Inject
public ArchiveOperations getArchiveOperations() {
throw new UnsupportedOperationException();
}

@Override
public void apply(Project project) {
Provider<NativeImageService> nativeImageServiceProvider = NativeImageService.registerOn(project);
Expand Down Expand Up @@ -148,6 +160,7 @@ private void configureJavaProject(Project project, Provider<NativeImageService>
task.getImage().convention(imageBuilder.map(t -> t.getOutputFile().get()));
task.getRuntimeArgs().convention(buildExtension.getRuntimeArgs());
});
configureClasspathJarFor(tasks, buildExtension, imageBuilder);

TaskProvider<ProcessGeneratedGraalResourceFiles> processAgentFiles = registerProcessAgentFilesTask(project, PROCESS_AGENT_RESOURCES_TASK_NAME);

Expand Down Expand Up @@ -217,6 +230,7 @@ private void configureJavaProject(Project project, Provider<NativeImageService>
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.");
Expand All @@ -226,6 +240,26 @@ private void configureJavaProject(Project project, Provider<NativeImageService>
});
}

private void configureClasspathJarFor(TaskContainer tasks, NativeImageOptions options, TaskProvider<BuildNativeImageTask> imageBuilder) {
String baseName = imageBuilder.getName();
TaskProvider<Jar> classpathJar = tasks.register(baseName + "ClasspathJar", Jar.class, jar -> {
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 -> {
if (options.getUseFatJar().get()) {
nit.getClasspathJar().set(classpathJar.flatMap(AbstractArchiveTask::getArchiveFile));
}
});
}

private TaskProvider<GenerateResourcesConfigFile> registerResourcesConfigTask(Provider<Directory> generatedDir,
NativeImageOptions options,
TaskContainer tasks,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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<Boolean> getUseFatJar();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -62,15 +64,18 @@ public class NativeImageCommandLineProvider implements CommandLineArgumentProvid
private final Provider<Boolean> agentEnabled;
private final Provider<String> executableName;
private final Provider<String> outputDirectory;
private final Provider<RegularFile> classpathJar;

public NativeImageCommandLineProvider(Provider<NativeImageOptions> options,
Provider<Boolean> agentEnabled,
Provider<String> executableName,
Provider<String> outputDirectory) {
Provider<String> outputDirectory,
Provider<RegularFile> classpathJar) {
this.options = options;
this.agentEnabled = agentEnabled;
this.executableName = executableName;
this.outputDirectory = outputDirectory;
this.classpathJar = classpathJar;
}

@Nested
Expand All @@ -93,13 +98,22 @@ public Provider<String> getOutputDirectory() {
return outputDirectory;
}

@InputFile
public Provider<RegularFile> getClasspathJar() {
return classpathJar;
}

@Override
public List<String> asArguments() {
NativeImageOptions options = getOptions().get();
List<String> 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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -112,6 +114,10 @@ public Provider<RegularFile> getOutputFile() {
@Inject
protected abstract ProviderFactory getProviders();

@InputFile
@Optional
public abstract RegularFileProperty getClasspathJar();

public BuildNativeImageTask() {
DirectoryProperty buildDir = getProject().getLayout().getBuildDirectory();
Provider<Directory> outputDir = buildDir.dir("native/" + getName());
Expand All @@ -132,8 +138,8 @@ private List<String> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Loading