diff --git a/docs/src/docs/asciidoc/maven-plugin.adoc b/docs/src/docs/asciidoc/maven-plugin.adoc
index 67d6eaf89..1b591411e 100644
--- a/docs/src/docs/asciidoc/maven-plugin.adoc
+++ b/docs/src/docs/asciidoc/maven-plugin.adoc
@@ -160,6 +160,14 @@ Build Configuration]. It is also possible to customize the plugin within a
----
true
----
+``::
+If you want to enable module path inference during native-image building supply the following in the configuration of the plugin:
+[source,xml]
+----
+true
+----
+Module path inference will detect which dependencies are modules and configure the module path accordingly. The identified modules will then be removed from the usual classpath.
+NOTE: This is an experimental feature.
``::
If you want to build image as a shared library supply the following in the configuration of the plugin:
[source,xml]
diff --git a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
index fa5e563bd..f0b32c7e7 100644
--- a/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
+++ b/native-maven-plugin/src/main/java/org/graalvm/buildtools/maven/AbstractNativeImageMojo.java
@@ -55,24 +55,24 @@
import org.graalvm.buildtools.utils.SharedConstants;
import javax.inject.Inject;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.IOException;
import java.io.InputStream;
-import java.io.BufferedReader;
import java.io.InputStreamReader;
-import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
import java.nio.file.FileSystemAlreadyExistsException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
-import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -82,11 +82,13 @@
/**
* @author Sebastien Deleuze
+ * @author Loic Lefevre
*/
public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
protected static final String NATIVE_IMAGE_META_INF = "META-INF/native-image";
protected static final String NATIVE_IMAGE_PROPERTIES_FILENAME = "native-image.properties";
protected static final String NATIVE_IMAGE_DRY_RUN = "nativeDryRun";
+ protected static final String MODULE_INFO_CLASS = "module-info.class";
@Parameter(defaultValue = "${plugin}", readonly = true) // Maven 3 only
protected PluginDescriptor plugin;
@@ -112,6 +114,9 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
@Parameter(property = "classpath")
protected List classpath;
+ @Parameter(property = "module-path")
+ protected List modulepath;
+
@Parameter(property = "classesDirectory")
protected File classesDirectory;
@@ -119,6 +124,11 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
protected File defaultClassesDirectory;
protected final List imageClasspath;
+ protected final List imageModulepath;
+
+ // Experimental feature
+ @Parameter(property = "experimentalInferModulePath", defaultValue = "false")
+ protected boolean experimentalInferModulePath;
@Parameter(property = "debug", defaultValue = "false")
protected boolean debug;
@@ -174,9 +184,28 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
@Inject
protected AbstractNativeImageMojo() {
imageClasspath = new ArrayList<>();
+ imageModulepath = new ArrayList<>();
useArgFile = SharedConstants.IS_WINDOWS;
}
+ static class PathItem {
+ private final Path path;
+ private final boolean module;
+
+ public PathItem(Path path, boolean module) {
+ this.path = path;
+ this.module = module;
+ }
+
+ public Path getPath() {
+ return path;
+ }
+
+ public boolean isModule() {
+ return module;
+ }
+ }
+
protected List getBuildArgs() throws MojoExecutionException {
final List cliArgs = new ArrayList<>();
@@ -191,6 +220,12 @@ protected List getBuildArgs() throws MojoExecutionException {
cliArgs.add("-cp");
cliArgs.add(getClasspath());
+ if(experimentalInferModulePath) {
+ logger.info("Module path inference: enabled");
+ cliArgs.add("--module-path");
+ cliArgs.add(getModulepath());
+ }
+
if (debug) {
cliArgs.add("-g");
}
@@ -262,11 +297,11 @@ protected List getBuildArgs() throws MojoExecutionException {
return Collections.unmodifiableList(actualCliArgs);
}
- protected Path processSupportedArtifacts(Artifact artifact) throws MojoExecutionException {
+ protected PathItem processSupportedArtifacts(Artifact artifact) throws MojoExecutionException {
return processArtifact(artifact, "jar", "test-jar", "war");
}
- protected Path processArtifact(Artifact artifact, String... artifactTypes) throws MojoExecutionException {
+ protected PathItem processArtifact(Artifact artifact, String... artifactTypes) throws MojoExecutionException {
File artifactFile = artifact.getFile();
if (artifactFile == null) {
@@ -286,12 +321,17 @@ protected Path processArtifact(Artifact artifact, String... artifactTypes) throw
Path jarFilePath = artifactFile.toPath();
logger.debug("ImageClasspath Entry: " + artifact + " (" + jarFilePath.toUri() + ")");
- warnIfWrongMetaInfLayout(jarFilePath, artifact);
- return jarFilePath;
+ return warnIfWrongMetaInfLayout(jarFilePath, artifact);
}
protected void addArtifactToClasspath(Artifact artifact) throws MojoExecutionException {
- Optional.ofNullable(processSupportedArtifacts(artifact)).ifPresent(imageClasspath::add);
+ Optional.ofNullable(processSupportedArtifacts(artifact)).ifPresent(pathItem -> {
+ if(pathItem.isModule()) {
+ imageModulepath.add(pathItem.getPath());
+ } else {
+ imageClasspath.add(pathItem.getPath());
+ }
+ });
}
private static FileSystem openFileSystem(URI uri) throws IOException {
@@ -304,13 +344,17 @@ private static FileSystem openFileSystem(URI uri) throws IOException {
return fs;
}
- protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException {
+ protected PathItem warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) throws MojoExecutionException {
if (jarFilePath.toFile().isDirectory()) {
logger.debug("Artifact `" + jarFilePath + "` is a directory.");
- return;
+ return null;
}
URI jarFileURI = URI.create("jar:" + jarFilePath.toUri());
+ Boolean module = null;
try (FileSystem jarFS = openFileSystem(jarFileURI)) {
+ // check if this is a module!
+ Path moduleInfoClass = jarFS.getPath("/" + MODULE_INFO_CLASS);
+ module = experimentalInferModulePath && Files.exists(moduleInfoClass);
Path nativeImageMetaInfBase = jarFS.getPath("/" + NATIVE_IMAGE_META_INF);
if (Files.isDirectory(nativeImageMetaInfBase)) {
try (Stream stream = Files.walk(nativeImageMetaInfBase)) {
@@ -330,8 +374,10 @@ protected void warnIfWrongMetaInfLayout(Path jarFilePath, Artifact artifact) thr
}
}
} catch (IOException e) {
- throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath", e);
+ throw new MojoExecutionException("Artifact " + artifact + "cannot be added to image classpath or module path", e);
}
+
+ return new PathItem(jarFilePath, module);
}
protected abstract List getDependencyScopes();
@@ -354,16 +400,21 @@ protected void addDependenciesToClasspath() throws MojoExecutionException {
/**
* Returns path to where application classes are stored, or jar artifact if it is produced.
+ * @param forModulePath denotes if the build path should be added to the image classpath or image module path
* @return Path to application classes
* @throws MojoExecutionException failed getting main build path
*/
- protected Path getMainBuildPath() throws MojoExecutionException {
+ protected Path getMainBuildPath(boolean forModulePath) throws MojoExecutionException {
if (classesDirectory != null) {
- return classesDirectory.toPath();
+ if(!forModulePath) {
+ return classesDirectory.toPath();
+ } else {
+ return defaultClassesDirectory.toPath();
+ }
} else {
- Path artifactPath = processArtifact(project.getArtifact(), project.getPackaging());
- if (artifactPath != null) {
- return artifactPath;
+ PathItem artifactPath = processArtifact(project.getArtifact(), project.getPackaging());
+ if (artifactPath != null && forModulePath == artifactPath.isModule()) {
+ return artifactPath.getPath();
} else {
return defaultClassesDirectory.toPath();
}
@@ -371,7 +422,11 @@ protected Path getMainBuildPath() throws MojoExecutionException {
}
protected void populateApplicationClasspath() throws MojoExecutionException {
- imageClasspath.add(getMainBuildPath());
+ imageClasspath.add(getMainBuildPath(false));
+ }
+
+ protected void populateApplicationModulepath() throws MojoExecutionException {
+ imageModulepath.add(getMainBuildPath(true));
}
protected void populateClasspath() throws MojoExecutionException {
@@ -387,6 +442,19 @@ protected void populateClasspath() throws MojoExecutionException {
}
imageClasspath.removeIf(entry -> !entry.toFile().exists());
}
+ protected void populateModulepath() throws MojoExecutionException {
+ if (modulepath != null && !modulepath.isEmpty()) {
+ imageModulepath.addAll(modulepath.stream()
+ .map(Paths::get)
+ .map(Path::toAbsolutePath)
+ .collect(Collectors.toSet())
+ );
+ } else {
+ populateApplicationModulepath();
+ addDependenciesToClasspath();
+ }
+ imageModulepath.removeIf(entry -> !entry.toFile().exists());
+ }
protected String getClasspath() throws MojoExecutionException {
populateClasspath();
@@ -399,6 +467,17 @@ protected String getClasspath() throws MojoExecutionException {
.collect(Collectors.joining(File.pathSeparator));
}
+ protected String getModulepath() throws MojoExecutionException {
+ populateModulepath();
+ if (imageModulepath.isEmpty()) {
+ throw new MojoExecutionException("Image module path is empty. " +
+ "Check if your module path configuration is correct.");
+ }
+ return imageModulepath.stream()
+ .map(Path::toString)
+ .collect(Collectors.joining(File.pathSeparator));
+ }
+
protected void buildImage() throws MojoExecutionException {
checkRequiredVersionIfNeeded();
Path nativeImageExecutable = NativeImageConfigurationUtils.getNativeImage(logger);