org.apache.commons
commons-exec
diff --git a/src/main/java/org/openjfx/JavaFXBaseMojo.java b/src/main/java/org/openjfx/JavaFXBaseMojo.java
index a43d4fd..3a48012 100644
--- a/src/main/java/org/openjfx/JavaFXBaseMojo.java
+++ b/src/main/java/org/openjfx/JavaFXBaseMojo.java
@@ -89,7 +89,7 @@ abstract class JavaFXBaseMojo extends AbstractMojo {
/**
* The current working directory. Optional. If not specified, basedir will be used.
*/
- @Parameter(property = "javafx.workingdir")
+ @Parameter(property = "javafx.workingDirectory")
File workingDirectory;
@Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true)
diff --git a/src/main/java/org/openjfx/JavaFXJLinkMojo.java b/src/main/java/org/openjfx/JavaFXJLinkMojo.java
new file mode 100644
index 0000000..dd3e044
--- /dev/null
+++ b/src/main/java/org/openjfx/JavaFXJLinkMojo.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2019 Gluon
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openjfx;
+
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteException;
+import org.apache.commons.exec.Executor;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+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.archiver.Archiver;
+import org.codehaus.plexus.archiver.ArchiverException;
+import org.codehaus.plexus.archiver.zip.ZipArchiver;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.StringUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@Mojo(name = "jlink", requiresDependencyResolution = ResolutionScope.RUNTIME)
+public class JavaFXJLinkMojo extends JavaFXBaseMojo {
+
+ /**
+ * Strips debug information out, equivalent to -G, --strip-debug
,
+ * default false
+ */
+ @Parameter(property = "javafx.stripDebug", defaultValue = "false")
+ private boolean stripDebug;
+
+ /**
+ * Compression level of the resources being used, equivalent to:
+ * -c, --compress=level
. Valid values: 0, 1, 2
,
+ * default 2
+ */
+ @Parameter(property = "javafx.compress", defaultValue = "2")
+ private Integer compress;
+
+ /**
+ * Remove the includes
directory in the resulting runtime image,
+ * equivalent to: --no-header-files
, default false
+ */
+ @Parameter(property = "javafx.noHeaderFiles", defaultValue = "false")
+ private boolean noHeaderFiles;
+
+ /**
+ * Remove the man
directory in the resulting Java runtime image,
+ * equivalent to: --no-man-pages
, default false
+ */
+ @Parameter(property = "javafx.noManPages", defaultValue = "false")
+ private boolean noManPages;
+
+ /**
+ * Add the option --bind-services
or not, default false.
+ */
+ @Parameter(property = "javafx.bindServices", defaultValue = "false")
+ private boolean bindServices;
+
+ /**
+ * --ignore-signing-information
, default false
+ */
+ @Parameter(property = "javafx.ignoreSigningInformation", defaultValue = "false")
+ private boolean ignoreSigningInformation;
+
+ /**
+ * Turn on verbose mode, equivalent to: --verbose
, default false
+ */
+ @Parameter(property = "javafx.jlinkVerbose", defaultValue = "false")
+ private boolean jlinkVerbose;
+
+ /**
+ * Add a launcher script, equivalent to:
+ * --launcher <name>=<module>[/<mainclass>]
.
+ */
+ @Parameter(property = "javafx.launcher")
+ private String launcher;
+
+ /**
+ * The name of the folder with the resulting runtime image,
+ * equivalent to --output <path>
+ */
+ @Parameter(property = "javafx.jlinkImageName", defaultValue = "image")
+ private String jlinkImageName;
+
+ /**
+ * When set, creates a zip of the resulting runtime image.
+ */
+ @Parameter(property = "javafx.jlinkZipName")
+ private String jlinkZipName;
+
+ /**
+ *
+ * The executable. Can be a full path or the name of the executable.
+ * In the latter case, the executable must be in the PATH for the execution to work.
+ *
+ */
+ @Parameter(property = "javafx.jlinkExecutable", defaultValue = "jlink")
+ private String jlinkExecutable;
+
+ /**
+ * The JAR archiver needed for archiving the environments.
+ */
+ @Component(role = Archiver.class, hint = "zip")
+ private ZipArchiver zipArchiver;
+
+ public void execute() throws MojoExecutionException {
+ if (skip) {
+ getLog().info( "skipping execute as per configuration" );
+ return;
+ }
+
+ if (jlinkExecutable == null) {
+ throw new MojoExecutionException("The parameter 'jlinkExecutable' is missing or invalid");
+ }
+
+ if (basedir == null) {
+ throw new IllegalStateException( "basedir is null. Should not be possible." );
+ }
+
+ try {
+ handleWorkingDirectory();
+
+ List commandArguments = new ArrayList<>();
+ handleArguments(commandArguments);
+
+ Map enviro = handleSystemEnvVariables();
+ CommandLine commandLine = getExecutablePath(jlinkExecutable, enviro, workingDirectory);
+ String[] args = commandArguments.toArray(new String[commandArguments.size()]);
+ commandLine.addArguments(args, false);
+ getLog().debug("Executing command line: " + commandLine);
+
+ Executor exec = new DefaultExecutor();
+ exec.setWorkingDirectory(workingDirectory);
+
+ try {
+ int resultCode;
+ if (outputFile != null) {
+ if ( !outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
+ getLog().warn( "Could not create non existing parent directories for log file: " + outputFile );
+ }
+
+ FileOutputStream outputStream = null;
+ try {
+ outputStream = new FileOutputStream(outputFile);
+ resultCode = executeCommandLine(exec, commandLine, enviro, outputStream);
+ } finally {
+ IOUtil.close(outputStream);
+ }
+ } else {
+ resultCode = executeCommandLine(exec, commandLine, enviro, System.out, System.err);
+ }
+
+ if (resultCode != 0) {
+ String message = "Result of " + commandLine.toString() + " execution is: '" + resultCode + "'.";
+ getLog().error(message);
+ throw new MojoExecutionException(message);
+ }
+
+ if (jlinkZipName != null && ! jlinkZipName.isEmpty()) {
+ getLog().debug("Creating zip of runtime image");
+ File createZipArchiveFromImage = createZipArchiveFromImage();
+ project.getArtifact().setFile(createZipArchiveFromImage);
+ }
+
+ } catch (ExecuteException e) {
+ getLog().error("Command execution failed.", e);
+ e.printStackTrace();
+ throw new MojoExecutionException("Command execution failed.", e);
+ } catch (IOException e) {
+ getLog().error("Command execution failed.", e);
+ throw new MojoExecutionException("Command execution failed.", e);
+ }
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error", e);
+ }
+
+ }
+
+ private void handleArguments(List commandArguments) throws MojoExecutionException, MojoFailureException {
+ preparePaths();
+
+ if (options != null) {
+ options.stream()
+ .filter(Objects::nonNull)
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .forEach(commandArguments::add);
+ }
+
+ if (modulepathElements != null && !modulepathElements.isEmpty()) {
+ commandArguments.add(" --module-path");
+ String modulePath = StringUtils.join(modulepathElements.iterator(), File.pathSeparator);
+ commandArguments.add(modulePath);
+
+ commandArguments.add(" --add-modules");
+ if (moduleDescriptor != null) {
+ commandArguments.add(" " + moduleDescriptor.name());
+ } else {
+ throw new MojoExecutionException("jlink requires a module descriptor");
+ }
+ }
+
+ commandArguments.add(" --output");
+ File image = new File(builddir, jlinkImageName);
+ getLog().debug("image output: " + image.getAbsolutePath());
+ if (image.exists()) {
+ try {
+ Files.walk(image.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ } catch (IOException e) {
+ throw new MojoExecutionException("Image can't be removed " + image.getAbsolutePath(), e);
+ }
+ }
+ commandArguments.add(" " + image.getAbsolutePath());
+
+ if (stripDebug) {
+ commandArguments.add(" --strip-debug");
+ }
+ if (bindServices) {
+ commandArguments.add(" --bind-services");
+ }
+ if (ignoreSigningInformation) {
+ commandArguments.add(" --ignore-signing-information");
+ }
+ if (compress != null) {
+ commandArguments.add(" --compress");
+ if (compress < 0 || compress > 2) {
+ throw new MojoFailureException("The given compress parameters " + compress + " is not in the valid value range from 0..2");
+ }
+ commandArguments.add(" " + compress);
+ }
+ if (noHeaderFiles) {
+ commandArguments.add(" --no-header-files");
+ }
+ if (noManPages) {
+ commandArguments.add(" --no-man-pages");
+ }
+ if (jlinkVerbose) {
+ commandArguments.add(" --verbose");
+ }
+
+ if (launcher != null && ! launcher.isEmpty()) {
+ commandArguments.add(" --launcher");
+ String moduleMainClass;
+ if (mainClass.contains("/")) {
+ moduleMainClass = mainClass;
+ } else {
+ moduleMainClass = moduleDescriptor.name() + "/" + mainClass;
+ }
+ commandArguments.add(" " + launcher + "=" + moduleMainClass);
+ }
+ }
+
+ private File createZipArchiveFromImage() throws MojoExecutionException {
+ File imageArchive = new File(builddir, jlinkImageName);
+ zipArchiver.addDirectory(imageArchive);
+
+ File resultArchive = new File(builddir, jlinkZipName + ".zip");
+ zipArchiver.setDestFile(resultArchive);
+ try {
+ zipArchiver.createArchive();
+ } catch (ArchiverException | IOException e) {
+ throw new MojoExecutionException(e.getMessage(), e);
+ }
+ return resultArchive;
+ }
+
+ // for tests
+}