Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

334 - Fix module path and image classpath for native image generation #607

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions docs/src/docs/asciidoc/maven-plugin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ Build Configuration]. It is also possible to customize the plugin within a
----
<verbose>true</verbose>
----
`<experimentalInferModulePath>`::
If you want to enable module path inference during native-image building supply the following in the configuration of the plugin:
[source,xml]
----
<experimentalInferModulePath>true</experimentalInferModulePath>
----
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.
`<sharedLibrary>`::
If you want to build image as a shared library supply the following in the configuration of the plugin:
[source,xml]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -112,13 +114,21 @@ public abstract class AbstractNativeImageMojo extends AbstractNativeMojo {
@Parameter(property = "classpath")
protected List<String> classpath;

@Parameter(property = "module-path")
protected List<String> modulepath;

@Parameter(property = "classesDirectory")
protected File classesDirectory;

@Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
protected File defaultClassesDirectory;

protected final List<Path> imageClasspath;
protected final List<Path> imageModulepath;

// Experimental feature
@Parameter(property = "experimentalInferModulePath", defaultValue = "false")
protected boolean experimentalInferModulePath;

@Parameter(property = "debug", defaultValue = "false")
protected boolean debug;
Expand Down Expand Up @@ -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<String> getBuildArgs() throws MojoExecutionException {
final List<String> cliArgs = new ArrayList<>();

Expand All @@ -191,6 +220,12 @@ protected List<String> 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");
}
Expand Down Expand Up @@ -262,11 +297,11 @@ protected List<String> 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) {
Expand All @@ -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 {
Expand All @@ -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<Path> stream = Files.walk(nativeImageMetaInfBase)) {
Expand All @@ -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<String> getDependencyScopes();
Expand All @@ -354,24 +400,33 @@ 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();
}
}
}

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 {
Expand All @@ -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();
Expand All @@ -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);
Expand Down
Loading