diff --git a/.gitignore b/.gitignore index e903bb9..c81ee39 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,7 @@ target/* .settings/* .project .classpath +.factorypath /target .idea +.java-version diff --git a/.travis.yml b/.travis.yml index dff5f3a..3f90820 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,2 @@ language: java +sudo: false diff --git a/README.md b/README.md index 63c5d77..2af13eb 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,46 @@ Originally hosted at http://9stmaryrd.com/tools/launch4j-maven-plugin/ [![Build Status](https://travis-ci.org/lukaszlenart/launch4j-maven-plugin.svg)](https://travis-ci.org/lukaszlenart/launch4j-maven-plugin) +# Documentation + +Please check [this](src/main/resources/README.adoc) document for more detailed info on how to use the plugin. Please also check [Launch4j's Configuration file](http://launch4j.sourceforge.net/docs.html#Configuration_file) page. + +# Version Notes + +## Version notes 1.7.8 +- fixes issue with spaces in path to maven repository on non-Windows systems, see [#27](../../issues/27), [#28](../../issues/28) + +## Version notes 1.7.7 +- once again fixes problem with including dependencies in scope `runtime` (now it should be the final solution), see [#5](../../issues/5) +- adds support for `bundledJreAsFallback` and `bundledJre64Bit` properties, see [#23](../../issues/23) +- upgrades Launch4j to 3.8.0, see [#21](../../issues/21) + +## Version notes 1.7.6 +- fixes again problem with including dependencies in scope `runtime`, see [#5](../../issues/5) + +## Version notes 1.7.5 +- allows add custom headers and libraries to working dir [#22](../../pull/22) + +## Version notes 1.7.4 +- fixes type in default value for `outfile` parameter [#17](../../pull/17) + +## Version notes 1.7.3 +- uses Maven annotation instead of JavaDoc parameters [#15](../../pull/15) +- upgrades Maven plugins [#15](../../pull/15) +- converts tabs to spaces [5b0619](../../commit/5b0619) + +## Version notes 1.7.2 +- adds support for `restartOnCrash` Launch4j's option [#14](../../pull/14) + +## Version notes 1.7.1 +- launch4j's `abeille` dependency was excluded [#11](../../pull/11) +- versions of several plugins were updated [#11](../../pull/11) +- tabs were converted to spaces [#11](../../pull/11) + ## Version notes 1.7 - uses the latest version of Launch4j (3.5.0) - contains support for `runtimeBits`, see [#6](../../issues/6) -- fixes problem with including dependencies in scope `runtime`, see [#5](../../issues/5) +- ~~fixes problem with including dependencies in scope `runtime`, see [#5](../../issues/5)~~ ## Version notes 1.6 - dropped Launch4j source and based on artifacts from Maven Central, see [#8](../../issues/8) diff --git a/pom.xml b/pom.xml index a26eaa8..b35dc43 100644 --- a/pom.xml +++ b/pom.xml @@ -1,148 +1,230 @@ - - 4.0.0 + is free software; you can redistribute it and/or modify it under the terms + of the GNU General Public License as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. This + program is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along with + this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + St, Fifth Floor, Boston, MA 02110-1301 USA --> + + 4.0.0 org.sonatype.oss oss-parent - 7 + 9 com.akathist.maven.plugins.launch4j - launch4j-maven-plugin - maven-plugin - 1.8-SNAPSHOT + launch4j-maven-plugin + maven-plugin + 1.7.9-SNAPSHOT Maven Launch4j Plugin - This plugin creates Windows executables from Java jar files using the Launch4j utility. - http://9stmaryrd.com/tools/launch4j-maven-plugin/ + This plugin creates Windows executables from Java jar files using the Launch4j utility. + http://9stmaryrd.com/tools/launch4j-maven-plugin/ - 3.5.0 + 3.8.0 + 1.7 + 1.7 - - GNU General Public License - http://www.gnu.org/licenses/gpl.txt - repo - - + + GNU General Public License + http://www.gnu.org/licenses/gpl.txt + repo + + - - scm:git:git@github.com:lukaszlenart/launch4j-maven-plugin.git - HEAD - + + scm:git:git@github.com:lukaszlenart/launch4j-maven-plugin.git + HEAD + - - - net.sf.launch4j - launch4j - ${launch4j.version} - core + + + net.sf.launch4j + launch4j + ${launch4j.version} + core com.ibm.icu icu4j + + abeille + net.java.abeille + - - - org.apache.maven - maven-plugin-api - 3.0 - - - org.apache.maven - maven-artifact - 2.2.1 - - - org.apache.maven - maven-project - 2.2.1 - - - org.apache.maven - maven-plugin-descriptor - 2.2.0 - - - junit - junit - 4.8.2 - test - - + + + org.apache.maven + maven-plugin-api + 3.2.5 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.4 + provided + + + org.apache.maven + maven-artifact + 2.2.1 + + + org.apache.maven + maven-project + 2.2.1 + + + org.apache.maven + maven-plugin-descriptor + 2.2.1 + + + junit + junit + 4.12 + test + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.5.3 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + dist-src + package + + attached + + + + ${project.basedir}/src/main/assembly/src.xml + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.4 + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + - - - - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - - - - maven-assembly-plugin - - - dist-src - package - - attached - - - - src/main/assembly/src.xml - - - - - - - + + + eclipse + + + m2e.version + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + [3.4,) + + helpmojo + descriptor + + + + + + + + + + + + + + + - - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 2.3.1 - - - - index - summary - license - dependencies - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.7.2 - - - + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.8 + + + + index + summary + license + dependencies + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.1 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.18.1 + + + diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/ClassPath.java b/src/main/java/com/akathist/maven/plugins/launch4j/ClassPath.java index 07283a9..df4cd16 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/ClassPath.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/ClassPath.java @@ -19,6 +19,7 @@ package com.akathist.maven.plugins.launch4j; import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugins.annotations.Parameter; import java.util.ArrayList; import java.util.Arrays; @@ -27,89 +28,76 @@ public class ClassPath { - /** - * The main class to run. This is not required if you are wrapping an executable jar. - * - * @parameter - */ - String mainClass; - - /** - * The launch4j executable sets up a classpath before running your jar, but it must know what the - * classpath should be. If you set this property to true, the plugin will indicate a classpath - * based on all the dependencies your program will need at runtime. You can augment this classpath - * using the preCp and postCp properties. - * - * @parameter default-value=true - */ - boolean addDependencies = true; - - /** - * If you want maven to build the classpath from dependencies, you can optionally set the jarLocation, - * which is the location of the jars in your distro relative to the executable. So if your distro - * has the exe at the top level and all the jars in a lib directory, you could set this to "lib." - * This property does not affect preCp and postCp. - * - * @parameter - */ - String jarLocation; - - /** - * Part of the classpath that the executable should give to your application. - * Paths are relative to the executable and should be in Windows format (separated by a semicolon). - * You don't have to list all your dependencies here; the plugin will include them by default - * after this list. - * - * @parameter - */ - String preCp; - - /** - * Part of the classpath that the executable should give to your application. - * Paths are relative to the executable and should be in Windows format (separated by a semicolon). - * You don't have to list all your dependencies here; the plugin will include them by default - * before this list. - * - * @parameter - */ - String postCp; - - private void addToCp(List cp, String cpStr) { - cp.addAll(Arrays.asList(cpStr.split("\\s*;\\s*"))); - } - - net.sf.launch4j.config.ClassPath toL4j(Set dependencies, List runtimeDependencies) { - net.sf.launch4j.config.ClassPath ret = new net.sf.launch4j.config.ClassPath(); - ret.setMainClass(mainClass); - - List cp = new ArrayList(); - if (preCp != null) addToCp(cp, preCp); - - if (addDependencies) { - if (jarLocation == null) jarLocation = ""; - else if ( ! jarLocation.endsWith("/")) jarLocation += "/"; - - // Add all runtime dependencies as we need them to run the wrapped jar - dependencies.addAll(runtimeDependencies); - - for (Object dependency : dependencies) { - Artifact dep = (Artifact) dependency; - if (Artifact.SCOPE_COMPILE.equals(dep.getScope()) || - Artifact.SCOPE_RUNTIME.equals(dep.getScope())) { + /** + * The main class to run. This is not required if you are wrapping an executable jar. + */ + @Parameter + String mainClass; + + /** + * The launch4j executable sets up a classpath before running your jar, but it must know what the + * classpath should be. If you set this property to true, the plugin will indicate a classpath + * based on all the dependencies your program will need at runtime. You can augment this classpath + * using the preCp and postCp properties. + */ + @Parameter(defaultValue = "true") + boolean addDependencies = true; + + /** + * If you want maven to build the classpath from dependencies, you can optionally set the jarLocation, + * which is the location of the jars in your distro relative to the executable. So if your distro + * has the exe at the top level and all the jars in a lib directory, you could set this to "lib." + * This property does not affect preCp and postCp. + */ + @Parameter + String jarLocation; + + /** + * Part of the classpath that the executable should give to your application. + * Paths are relative to the executable and should be in Windows format (separated by a semicolon). + * You don't have to list all your dependencies here; the plugin will include them by default + * after this list. + */ + @Parameter + String preCp; + + /** + * Part of the classpath that the executable should give to your application. + * Paths are relative to the executable and should be in Windows format (separated by a semicolon). + * You don't have to list all your dependencies here; the plugin will include them by default + * before this list. + */ + @Parameter + String postCp; + + private void addToCp(List cp, String cpStr) { + cp.addAll(Arrays.asList(cpStr.split("\\s*;\\s*"))); + } + + net.sf.launch4j.config.ClassPath toL4j(Set dependencies) { + net.sf.launch4j.config.ClassPath ret = new net.sf.launch4j.config.ClassPath(); + ret.setMainClass(mainClass); + + List cp = new ArrayList(); + if (preCp != null) addToCp(cp, preCp); + if (addDependencies) { + if (jarLocation == null) jarLocation = ""; + else if (!jarLocation.endsWith("/")) jarLocation += "/"; + + for (Artifact dependency : dependencies) { String depFilename; - depFilename = dep.getFile().getName(); - // System.out.println("dep = " + depFilename); + depFilename = dependency.getFile().getName(); +// System.out.println("dependency = " + depFilename); cp.add(jarLocation + depFilename); - } } - } + } - if (postCp != null) addToCp(cp, postCp); - ret.setPaths(cp); + if (postCp != null) addToCp(cp, postCp); + ret.setPaths(cp); - return ret; - } + return ret; + } @Override public String toString() { diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/Jre.java b/src/main/java/com/akathist/maven/plugins/launch4j/Jre.java index bc0dab6..222e80d 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/Jre.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/Jre.java @@ -20,127 +20,141 @@ import java.util.List; +import org.apache.maven.plugins.annotations.Parameter; + /** * Details about which jre the executable should call. */ public class Jre { - /** - * Use this property when you are bundling a jre with your application. It holds the path to the jre. - * If relative, this path is from the executable. - *

- * If you specify path only and not minVersion, then the executable will show an error if the jre is not found. - *

- * If you specify path along with minVersion, then the executable will check the path first, and if no jre - * is found there, it will search the local system for a jre matching minVersion. If it still doesn't - * find anything, it will show the java download page. You may also specify maxVersion to further - * constrain the search. - */ - String path; - - /** - * Use this property if you want the executable to search the system for a jre. - * It names the minimum version acceptable, in x.x.x[_xx] format. - *

- * If you specify this property without giving a path, then the executable will search for a jre - * and, none is found, display the java download page. - *

- * If you include a path also, the executable will try that path before searching for jre matching minVersion. - *

- * In either case, you can also specify a maxVersion. - */ - String minVersion; - - /** - * If you specify minVersion, you can also use maxVersion to further constrain the search for a jre. - * This property should be in the format x.x.x[_xx]. - */ - String maxVersion; - - /** - * Allows you to specify a preference for a public JRE or a private JDK runtime. - *

- * Valid values are: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
jreOnlyAlways use a public JRE
preferJrePrefer a public JRE, but use a JDK private runtime if it is newer than the public JRE
preferJdkPrefer a JDK private runtime, but use a public JRE if it is newer than the JDK
jdkOnlyAlways use a private JDK runtime (fails if there is no JDK installed)
- * - * @parameter default-value="preferJre" - */ - String jdkPreference; - - /** - * Sets java's initial heap size in MB, like the -Xms flag. - */ - int initialHeapSize; - - /** - * Sets java's initial heap size in percent of free memory. - */ - int initialHeapPercent; - - /** - * Sets java's maximum heap size in MB, like the -Xmx flag. - */ - int maxHeapSize; - - /** - * Sets java's maximum heap size in percent of free memory. - */ - int maxHeapPercent; - - /** - * Use this to pass arbitrary options to the java/javaw program. - * For instance, you can say: - *

-	 * <opt>-Dlaunch4j.exedir="%EXEDIR%"</opt>
-	 * <opt>-Dlaunch4j.exefile="%EXEFILE%"</opt>
-	 * <opt>-Denv.path="%Path%"</opt>
-	 * <opt>-Dsettings="%HomeDrive%%HomePath%\\settings.ini"</opt>
-	 * 
- */ - List opts; + /** + * Use this property when you are bundling a jre with your application. It holds the path to the jre. + * If relative, this path is from the executable. + *

+ * If you specify path only and not minVersion, then the executable will show an error if the jre is not found. + *

+ * If you specify path along with minVersion, then the executable will check the path first, and if no jre + * is found there, it will search the local system for a jre matching minVersion. If it still doesn't + * find anything, it will show the java download page. You may also specify maxVersion to further + * constrain the search. + */ + String path; + + /** + * Sets jre's bundledJre64Bit flag + */ + @Parameter(defaultValue = "false") + boolean bundledJre64Bit; + + /** + * Sets jre's bundledJreAsFallback flag + */ + @Parameter(defaultValue = "false") + boolean bundledJreAsFallback; + + /** + * Use this property if you want the executable to search the system for a jre. + * It names the minimum version acceptable, in x.x.x[_xx] format. + *

+ * If you specify this property without giving a path, then the executable will search for a jre + * and, none is found, display the java download page. + *

+ * If you include a path also, the executable will try that path before searching for jre matching minVersion. + *

+ * In either case, you can also specify a maxVersion. + */ + String minVersion; + + /** + * If you specify minVersion, you can also use maxVersion to further constrain the search for a jre. + * This property should be in the format x.x.x[_xx]. + */ + String maxVersion; + + /** + * Allows you to specify a preference for a public JRE or a private JDK runtime. + *

+ * Valid values are: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
jreOnlyAlways use a public JRE
preferJrePrefer a public JRE, but use a JDK private runtime if it is newer than the public JRE
preferJdkPrefer a JDK private runtime, but use a public JRE if it is newer than the JDK
jdkOnlyAlways use a private JDK runtime (fails if there is no JDK installed)
+ */ + @Parameter(defaultValue = "preferJre") + String jdkPreference; + + /** + * Sets java's initial heap size in MB, like the -Xms flag. + */ + int initialHeapSize; + + /** + * Sets java's initial heap size in percent of free memory. + */ + int initialHeapPercent; + + /** + * Sets java's maximum heap size in MB, like the -Xmx flag. + */ + int maxHeapSize; + + /** + * Sets java's maximum heap size in percent of free memory. + */ + int maxHeapPercent; + + /** + * Use this to pass arbitrary options to the java/javaw program. + * For instance, you can say: + *

+     * <opt>-Dlaunch4j.exedir="%EXEDIR%"</opt>
+     * <opt>-Dlaunch4j.exefile="%EXEFILE%"</opt>
+     * <opt>-Denv.path="%Path%"</opt>
+     * <opt>-Dsettings="%HomeDrive%%HomePath%\\settings.ini"</opt>
+     * 
+ */ + List opts; /** * Sets JVM version to use: 32 bits, 64 bits or 64/32 bits * Possible values: 32, 64, 64/32 - it will fallback to default value if different option was used * Default value is: 64/32 - * - * @parameter default-value="64/32" */ + @Parameter(defaultValue = "64/32") String runtimeBits; - net.sf.launch4j.config.Jre toL4j() { - net.sf.launch4j.config.Jre ret = new net.sf.launch4j.config.Jre(); - - ret.setPath(path); - ret.setMinVersion(minVersion); - ret.setMaxVersion(maxVersion); - ret.setJdkPreference(jdkPreference); - ret.setInitialHeapSize(initialHeapSize); - ret.setInitialHeapPercent(initialHeapPercent); - ret.setMaxHeapSize(maxHeapSize); - ret.setMaxHeapPercent(maxHeapPercent); - ret.setOptions(opts); + net.sf.launch4j.config.Jre toL4j() { + net.sf.launch4j.config.Jre ret = new net.sf.launch4j.config.Jre(); + + ret.setPath(path); + ret.setBundledJre64Bit(bundledJre64Bit); + ret.setBundledJreAsFallback(bundledJreAsFallback); + ret.setMinVersion(minVersion); + ret.setMaxVersion(maxVersion); + ret.setJdkPreference(jdkPreference); + ret.setInitialHeapSize(initialHeapSize); + ret.setInitialHeapPercent(initialHeapPercent); + ret.setMaxHeapSize(maxHeapSize); + ret.setMaxHeapPercent(maxHeapPercent); + ret.setOptions(opts); ret.setRuntimeBits(runtimeBits); - return ret; - } + return ret; + } @Override public String toString() { diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/Launch4jMojo.java b/src/main/java/com/akathist/maven/plugins/launch4j/Launch4jMojo.java index a8ea5dc..12d3990 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/Launch4jMojo.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/Launch4jMojo.java @@ -22,6 +22,10 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.List; @@ -36,605 +40,612 @@ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactCollector; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; /** * Wraps a jar in a Windows executable. - * - * @goal launch4j - * @phase package - * @requiresDependencyResolution compile */ +@Mojo(name = "launch4j", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME) public class Launch4jMojo extends AbstractMojo { - private static final String LAUNCH4J_ARTIFACT_ID = "launch4j"; - - private static final String LAUNCH4J_GROUP_ID = "net.sf.launch4j"; - - /** - * The dependencies required by the project. - * - * @parameter default-value="${project.artifacts}" - * @required - * @readonly - */ - private Set dependencies; - - /** - * The user's current project. - * - * @parameter default-value="${project}" - * @required - * @readonly - */ - private MavenProject project; - - /** - * The user's plugins (including, I hope, this one). - * - * @parameter default-value="${project.build.plugins}" - * @required - * @readonly - */ - private List plugins; - - /** - * Used to look up Artifacts in the remote repository. - * - * @@@parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}" - * @component - * @required - * @readonly - */ - private ArtifactFactory factory; - - /** - * The user's local repository - * - * @parameter default-value="${localRepository}" - * @required - * @readonly - */ - private ArtifactRepository localRepository; - - /** - * The artifact resolver used to grab the binary bits that launch4j needs. - * - * @component - */ - private ArtifactResolver resolver; - - /** - * The dependencies of this plugin. - * Used to get the Launch4j artifact version. - * - * @parameter default-value="${plugin.artifacts}" */ - private java.util.List pluginArtifacts; - - /** - * The base of the current project. - * - * @parameter default-value="${basedir}" - * @required - * @readonly - */ - private File basedir; - - /** - * Whether you want a gui or console app. - * Valid values are "gui" and "console." - * If you say gui, then launch4j will run your app from javaw instead of java - * in order to avoid opening a DOS window. - * Choosing gui also enables other options like taskbar icon and a splash screen. - * - * @parameter - * @required - */ - private String headerType; - - /** - * The name of the executable you want launch4j to produce. - * The path, if relative, is relative to the pom.xml. - * - * @parameter default-value="${project.build.directory}/${project.artifactId}.exe" - */ - private File outfile; - - /** - * The jar to bundle inside the executable. - * The path, if relative, is relative to the pom.xml. - *

- * If you don't want to wrap the jar, then this value should be the runtime path - * to the jar relative to the executable. You should also set dontWrapJar to true. - *

- * You can only bundle a single jar. Therefore, you should either create a jar that contains - * your own code plus all your dependencies, or you should distribute your dependencies alongside - * the executable. - * - * @parameter default-value="${project.build.directory}/${project.build.finalName}.jar" - */ - private String jar; - - /** - * Whether the executable should wrap the jar or not. - * - * @parameter default-value=false - */ - private boolean dontWrapJar; - - /** - * The title of the error popup if something goes wrong trying to run your program, - * like if java can't be found. If this is a console app and not a gui, then this value - * is used to prefix any error messages, as in ${errTitle}: ${errorMessage}. - * - * @parameter - */ - private String errTitle; - - /** - * downloadUrl (?) - * - * @parameter - */ - private String downloadUrl; - - /** - * supportUrl (?) - * - * @parameter - */ - private String supportUrl; - - /** - * Constant command line arguments to pass to your program's main method. - * Actual command line arguments entered by the user will appear after these. - * - * @parameter - */ - private String cmdLine; - - /** - * Changes to the given directory, relative to the executable, before running your jar. - * If set to . the current directory will be where the executable is. - * If omitted, the directory will not be changed. - * - * @parameter - */ - private String chdir; - - /** - * Priority class of windows process. - * Valid values are "normal" (default), "idle" and "high". - * @see MSDN: Scheduling Priorities - * - * @parameter default-value="normal" - */ - private String priority; - - - /** - * If true, the executable waits for the java application to finish before returning its exit code. - * Defaults to false for gui applications. Has no effect for console applications, which always wait. - * - * @parameter default-value=false - */ - private boolean stayAlive; - - /** - * The icon to use in the taskbar. Must be in ico format. - * - * @parameter - */ - private File icon; - - /** - * Object files to include. Used for custom headers only. - * - * @parameter - */ - private List objs; - - /** - * Win32 libraries to include. Used for custom headers only. - * - * @parameter - */ - private List libs; - - /** - * Variables to set. - * - * @parameter - */ - private List vars; - - /** - * Details about the supported jres. - * - * @parameter - * @required - */ - private Jre jre; - - /** - * Details about the classpath your application should have. - * This is required if you are not wrapping a jar. - * - * @parameter - */ - private ClassPath classPath; - - /** - * Details about whether to run as a single instance. - * - * @parameter - */ - private SingleInstance singleInstance; - - /** - * Details about the splash screen. - * - * @parameter - */ - private Splash splash; - - /** - * Lots of information you can attach to the windows process. - * - * @parameter - */ - private VersionInfo versionInfo; - - /** - * Various messages you can display. - * - * @parameter - */ - private Messages messages; + private static final String LAUNCH4J_ARTIFACT_ID = "launch4j"; + + private static final String LAUNCH4J_GROUP_ID = "net.sf.launch4j"; /** - * Windows manifest file (a XML file) with the same name as .exe file (myapp.exe.manifest) + * The dependencies required by the project. + */ + @Parameter(defaultValue = "${project.artifacts}", required = true, readonly = true) + private Set dependencies; + + /** + * The user's current project. + */ + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + /** + * The user's plugins (including, I hope, this one). + */ + @Parameter(defaultValue = "${project.build.plugins}", required = true, readonly = true) + private List plugins; + + /** + * Used to look up Artifacts in the remote repository. + */ + @Component(role = ArtifactFactory.class) + private ArtifactFactory factory; + + /** + * The user's local repository. + */ + @Parameter(defaultValue = "${localRepository}", required = true, readonly = true) + private ArtifactRepository localRepository; + + /** + * The artifact resolver used to grab the binary bits that launch4j needs. + */ + @Component(role = ArtifactResolver.class) + private ArtifactResolver resolver; + + /** + * The dependencies of this plugin. + * Used to get the Launch4j artifact version. + */ + @Parameter(defaultValue = "${plugin.artifacts}") + private java.util.List pluginArtifacts; + + /** + * The base of the current project. + */ + @Parameter(defaultValue = "${project.basedir}", required = true, readonly = true) + private File basedir; + + /** + * Whether you want a gui or console app. + * Valid values are "gui" and "console." + * If you say gui, then launch4j will run your app from javaw instead of java + * in order to avoid opening a DOS window. + * Choosing gui also enables other options like taskbar icon and a splash screen. + */ + @Parameter(required = true) + private String headerType; + + /** + * The name of the executable you want launch4j to produce. + * The path, if relative, is relative to the pom.xml. + */ + @Parameter(defaultValue = "${project.build.directory}/${project.artifactId}.exe") + private File outfile; + + /** + * The jar to bundle inside the executable. + * The path, if relative, is relative to the pom.xml. + *

+ * If you don't want to wrap the jar, then this value should be the runtime path + * to the jar relative to the executable. You should also set dontWrapJar to true. + *

+ * You can only bundle a single jar. Therefore, you should either create a jar that contains + * your own code plus all your dependencies, or you should distribute your dependencies alongside + * the executable. + */ + @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.jar") + private String jar; + + /** + * Whether the executable should wrap the jar or not. + */ + @Parameter(defaultValue = "false") + private boolean dontWrapJar; + + /** + * The title of the error popup if something goes wrong trying to run your program, + * like if java can't be found. If this is a console app and not a gui, then this value + * is used to prefix any error messages, as in ${errTitle}: ${errorMessage}. + */ + @Parameter + private String errTitle; + + /** + * downloadUrl (?). + */ + @Parameter + private String downloadUrl; + + /** + * supportUrl (?). + */ + @Parameter + private String supportUrl; + + /** + * Constant command line arguments to pass to your program's main method. + * Actual command line arguments entered by the user will appear after these. + */ + @Parameter + private String cmdLine; + + /** + * Changes to the given directory, relative to the executable, before running your jar. + * If set to . the current directory will be where the executable is. + * If omitted, the directory will not be changed. + */ + @Parameter + private String chdir; + + /** + * Priority class of windows process. + * Valid values are "normal" (default), "idle" and "high". * - * @parameter + * @see MSDN: Scheduling Priorities + */ + @Parameter(defaultValue = "normal") + private String priority; + + + /** + * If true, the executable waits for the java application to finish before returning its exit code. + * Defaults to false for gui applications. Has no effect for console applications, which always wait. + */ + @Parameter(defaultValue = "false") + private boolean stayAlive; + + /** + * If true, when the application exits, any exit code other than 0 is considered a crash and + * the application will be started again. */ + @Parameter(defaultValue = "false") + private boolean restartOnCrash; + + /** + * The icon to use in the taskbar. Must be in ico format. + */ + @Parameter + private File icon; + + /** + * Object files to include. Used for custom headers only. + */ + @Parameter + private List objs; + + /** + * Win32 libraries to include. Used for custom headers only. + */ + @Parameter + private List libs; + + /** + * Variables to set. + */ + @Parameter + private List vars; + + /** + * Details about the supported jres. + */ + @Parameter(required = true) + private Jre jre; + + /** + * Details about the classpath your application should have. + * This is required if you are not wrapping a jar. + */ + @Parameter + private ClassPath classPath; + + /** + * Details about whether to run as a single instance. + */ + @Parameter + private SingleInstance singleInstance; + + /** + * Details about the splash screen. + */ + @Parameter + private Splash splash; + + /** + * Lots of information you can attach to the windows process. + */ + @Parameter + private VersionInfo versionInfo; + + /** + * Various messages you can display. + */ + @Parameter + private Messages messages; + + /** + * Windows manifest file (a XML file) with the same name as .exe file (myapp.exe.manifest) + */ + @Parameter private File manifest; private File getJar() { - return new File(jar); - } - - public void execute() throws MojoExecutionException { - if (getLog().isDebugEnabled()) printState(); - - Config c = new Config(); - - c.setHeaderType(headerType); - c.setOutfile(outfile); - c.setJar(getJar()); - c.setDontWrapJar(dontWrapJar); - c.setErrTitle(errTitle); - c.setDownloadUrl(downloadUrl); - c.setSupportUrl(supportUrl); - c.setCmdLine(cmdLine); - c.setChdir(chdir); - c.setPriority(priority); - c.setStayAlive(stayAlive); + return new File(jar); + } + + @Override + public void execute() throws MojoExecutionException { + if (getLog().isDebugEnabled()) { + printState(); + } + + File workdir = setupBuildEnvironment(); + + Config c = new Config(); + + c.setHeaderType(headerType); + c.setOutfile(outfile); + c.setJar(getJar()); + c.setDontWrapJar(dontWrapJar); + c.setErrTitle(errTitle); + c.setDownloadUrl(downloadUrl); + c.setSupportUrl(supportUrl); + c.setCmdLine(cmdLine); + c.setChdir(chdir); + c.setPriority(priority); + c.setStayAlive(stayAlive); + c.setRestartOnCrash(restartOnCrash); c.setManifest(manifest); - c.setIcon(icon); - c.setHeaderObjects(objs); - c.setLibs(libs); - c.setVariables(vars); - - if (classPath != null) { - c.setClassPath(classPath.toL4j(dependencies, project.getRuntimeArtifacts())); - } - if (jre != null) { - c.setJre(jre.toL4j()); - } - if (singleInstance != null) { - c.setSingleInstance(singleInstance.toL4j()); - } - if (splash != null) { - c.setSplash(splash.toL4j()); - } - if (versionInfo != null) { - c.setVersionInfo(versionInfo.toL4j()); - } - if (messages != null) { - c.setMessages(messages.toL4j()); - } - - ConfigPersister.getInstance().setAntConfig(c, getBaseDir()); - File workdir = setupBuildEnvironment(); - Builder b = new Builder(new MavenLog(getLog()), workdir); - - try { - b.build(); - } catch (BuilderException e) { - getLog().error(e); - throw new MojoExecutionException("Failed to build the executable; please verify your configuration.", e); - } - } - - /** - * Prepares a little directory for launch4j to do its thing. Launch4j needs a bunch of object files - * (in the w32api and head directories) and the ld and windres binaries (in the bin directory). - * The tricky part is that launch4j picks this directory based on where its own jar is sitting. - * In our case, the jar is going to be sitting in the user's ~/.m2 repository. That's okay: we know - * maven is allowed to write there. So we'll just add our things to that directory. - *

- * This approach is not without flaws. - * It risks two processes writing to the directory at the same time. - * But fortunately, once the binary bits are in place, we don't do any more writing there, - * and launch4j doesn't write there either. - * Usually ~/.m2 will only be one system or another. - * But if it's an NFS mount shared by several system types, this approach will break. - *

- * Okay, so here is a better proposal: package the plugin without these varying binary files, - * and put each set of binaries in its own tarball. Download the tarball you need to ~/.m2 and - * unpack it. Then different systems won't contend for the same space. But then I'll need to hack - * the l4j code so it permits passing in a work directory and doesn't always base it on - * the location of its own jarfile. - * - * @return the work directory. - */ - private File setupBuildEnvironment() throws MojoExecutionException { - Artifact binaryBits = chooseBinaryBits(); - retrieveBinaryBits(binaryBits); - return unpackWorkDir(binaryBits); - } - - /** - * Unzips the given artifact in-place and returns the newly-unzipped top-level directory. - * Writes a marker file to prevent unzipping more than once. - */ - private File unpackWorkDir(Artifact a) throws MojoExecutionException { - String version = a.getVersion(); - File platJar = a.getFile(); - File dest = platJar.getParentFile(); - File marker = new File(dest, platJar.getName() + ".unpacked"); - - // If the artifact is a SNAPSHOT, then a.getVersion() will report the long timestamp, - // but getFile() will be 1.1-SNAPSHOT. - // Since getFile() doesn't use the timestamp, all timestamps wind up in the same place. - // Therefore we need to expand the jar every time, if the marker file is stale. - if (marker.exists() && marker.lastModified() > platJar.lastModified()) { - // if (marker.exists() && marker.platJar.getName().indexOf("SNAPSHOT") == -1) { - getLog().info("Platform-specific work directory already exists: " + dest.getAbsolutePath()); - } else { - JarFile jf = null; - try { - // trying to use plexus-archiver here is a miserable waste of time: - jf = new JarFile(platJar); - Enumeration en = jf.entries(); - while (en.hasMoreElements()) { - JarEntry je = (JarEntry)en.nextElement(); - File outFile = new File(dest, je.getName()); - File parent = outFile.getParentFile(); - if (parent != null) parent.mkdirs(); - if (je.isDirectory()) { - outFile.mkdirs(); - } else { - InputStream in = jf.getInputStream(je); - byte[] buf = new byte[1024]; - int len; - FileOutputStream fout = null; - try { - fout = new FileOutputStream(outFile); - while ((len = in.read(buf)) >= 0) { - fout.write(buf, 0, len); - } - fout.close(); - fout = null; - } finally { - if (fout != null) { - try { - fout.close(); - } catch (IOException e2) {} // ignore - } - } - outFile.setLastModified(je.getTime()); - } - } - } catch (IOException e) { - throw new MojoExecutionException("Error unarchiving " + platJar, e); - } finally { - try { - if (jf != null) jf.close(); - } catch (IOException e) {} // ignore - } - - try { - marker.createNewFile(); - marker.setLastModified(new Date().getTime()); - } catch (IOException e) { - getLog().warn("Trouble creating marker file " + marker, e); - } - } - - String n = platJar.getName(); - File workdir = new File(dest, n.substring(0, n.length() - 4)); - setPermissions(workdir); - return workdir; - } - - /** - * Chmods the helper executables ld and windres on systems where that is necessary. - */ - private void setPermissions(File workdir) throws MojoExecutionException { - if ( ! System.getProperty("os.name").startsWith("Windows")) { - Runtime r = Runtime.getRuntime(); - try { - r.exec("chmod 755 " + workdir + "/bin/ld").waitFor(); - r.exec("chmod 755 " + workdir + "/bin/windres").waitFor(); - } catch (InterruptedException e) { - getLog().warn("Interrupted while chmodding platform-specific binaries", e); - } catch (IOException e) { - getLog().warn("Unable to set platform-specific binaries to 755", e); - } - } - } - - /** - * Downloads the platform-specific parts, if necessary. - */ - private void retrieveBinaryBits(Artifact a) throws MojoExecutionException { - try { - resolver.resolve(a, project.getRemoteArtifactRepositories(), localRepository); - - } catch (ArtifactNotFoundException e) { - throw new MojoExecutionException("Can't find platform-specific components", e); - } catch (ArtifactResolutionException e) { - throw new MojoExecutionException("Can't retrieve platform-specific components", e); - } - } - - /** - * Decides which platform-specific bundle we need, based on the current operating system. - */ - private Artifact chooseBinaryBits() throws MojoExecutionException { - String plat; - String os = System.getProperty("os.name"); - getLog().debug("OS = " + os); - - // See here for possible values of os.name: - // http://lopica.sourceforge.net/os.html - if (os.startsWith("Windows")) { - plat = "win32"; - } else if ("Linux".equals(os)) { - plat = "linux"; - } else if ("Solaris".equals(os) || "SunOS".equals(os)) { - plat = "solaris"; - } else if ("Mac OS X".equals(os) || "Darwin".equals(os)) { - plat = "mac"; - } else { - throw new MojoExecutionException("Sorry, Launch4j doesn't support the '" + os + "' OS."); - } - - return factory.createArtifactWithClassifier(LAUNCH4J_GROUP_ID, LAUNCH4J_ARTIFACT_ID, - getLaunch4jVersion(), "jar", "workdir-" + plat); - } - - - private File getBaseDir() { - return basedir; - } - - /** - * Just prints out how we were configured. - */ - private void printState() { - Log log = getLog(); - - log.debug("headerType = " + headerType); - log.debug("outfile = " + outfile); - log.debug("jar = " + jar); - log.debug("dontWrapJar = " + dontWrapJar); - log.debug("errTitle = " + errTitle); - log.debug("downloadUrl = " + downloadUrl); - log.debug("supportUrl = " + supportUrl); - log.debug("cmdLine = " + cmdLine); - log.debug("chdir = " + chdir); - log.debug("priority = " + priority); - log.debug("stayAlive = " + stayAlive); - log.debug("icon = " + icon); - log.debug("objs = " + objs); - log.debug("libs = " + libs); - log.debug("vars = " + vars); - if (singleInstance != null) { - log.debug("singleInstance.mutexName = " + singleInstance.mutexName); - log.debug("singleInstance.windowTitle = " + singleInstance.windowTitle); - } else { - log.debug("singleInstance = null"); - } - if (jre != null) { - log.debug("jre.path = " + jre.path); - log.debug("jre.minVersion = " + jre.minVersion); - log.debug("jre.maxVersion = " + jre.maxVersion); - log.debug("jre.jdkPreference = " + jre.jdkPreference); - log.debug("jre.initialHeapSize = " + jre.initialHeapSize); - log.debug("jre.initialHeapPercent = " + jre.initialHeapPercent); - log.debug("jre.maxHeapSize = " + jre.maxHeapSize); - log.debug("jre.maxHeapPercent = " + jre.maxHeapPercent); - log.debug("jre.opts = "+ jre.opts); - } else { - log.debug("jre = null"); - } - if (classPath != null) { - log.debug("classPath.mainClass = " + classPath.mainClass); - log.debug("classPath.addDependencies = " + classPath.addDependencies); - log.debug("classPath.jarLocation = " + classPath.jarLocation); - log.debug("classPath.preCp = " + classPath.preCp); - log.debug("classPath.postCp = " + classPath.postCp); - } else { - log.info("classpath = null"); - } - if (splash != null) { - log.debug("splash.file = " + splash.file); - log.debug("splash.waitForWindow = " + splash.waitForWindow); - log.debug("splash.timeout = " + splash.timeout); - log.debug("splash.timoutErr = " + splash.timeoutErr); - } else { - log.debug("splash = null"); - } - if (versionInfo != null) { - log.debug("versionInfo.fileVersion = " + versionInfo.fileVersion); - log.debug("versionInfo.txtFileVersion = " + versionInfo.txtFileVersion); - log.debug("versionInfo.fileDescription = " + versionInfo.fileDescription); - log.debug("versionInfo.copyright = " + versionInfo.copyright); - log.debug("versionInfo.productVersion = " + versionInfo.productVersion); - log.debug("versionInfo.txtProductVersion = " + versionInfo.txtProductVersion); - log.debug("versionInfo.productName = " + versionInfo.productName); - log.debug("versionInfo.companyName = " + versionInfo.companyName); - log.debug("versionInfo.internalName = " + versionInfo.internalName); - log.debug("versionInfo.originalFilename = " + versionInfo.originalFilename); - } else { - log.debug("versionInfo = null"); - } - if (messages != null) { - log.debug("messages.startupErr = " + messages.startupErr); - log.debug("messages.bundledJreErr = " + messages.bundledJreErr); - log.debug("messages.jreVersionErr = " + messages.jreVersionErr); - log.debug("messages.launcherErr = " + messages.launcherErr); - log.debug("messages.instanceAlreadyExistsMsg = " + messages.instanceAlreadyExistsMsg); - } else { - log.debug("messages = null"); - } - } - - /** - * The Launch4j version used by the plugin. - * We want to download the platform-specific bundle whose version matches the Launch4j version, - * so we have to figure out what version the plugin is using. - * - * @return - * @throws MojoExecutionException - */ - private String getLaunch4jVersion() throws MojoExecutionException{ - String version = null; - - for(Artifact artifact: pluginArtifacts){ - if(LAUNCH4J_GROUP_ID.equals(artifact.getGroupId()) && - LAUNCH4J_ARTIFACT_ID.equals(artifact.getArtifactId()) - && "core".equals(artifact.getClassifier()) ){ - - version = artifact.getVersion(); - getLog().debug("Found launch4j version " + version); - break; - } - } - - if(version==null){ - throw new MojoExecutionException("Impossible to find which Launch4j version to use"); - } - - return version; - } + c.setIcon(icon); + c.setHeaderObjects(relativizeAndCopy(workdir, objs)); + c.setLibs(relativizeAndCopy(workdir, libs)); + c.setVariables(vars); + + if (classPath != null) { + c.setClassPath(classPath.toL4j(dependencies)); + } + if (jre != null) { + c.setJre(jre.toL4j()); + } + if (singleInstance != null) { + c.setSingleInstance(singleInstance.toL4j()); + } + if (splash != null) { + c.setSplash(splash.toL4j()); + } + if (versionInfo != null) { + c.setVersionInfo(versionInfo.toL4j()); + } + if (messages != null) { + c.setMessages(messages.toL4j()); + } + + ConfigPersister.getInstance().setAntConfig(c, getBaseDir()); + Builder b = new Builder(new MavenLog(getLog()), workdir); + + try { + b.build(); + } catch (BuilderException e) { + getLog().error(e); + throw new MojoExecutionException("Failed to build the executable; please verify your configuration.", e); + } + } + + /** + * Prepares a little directory for launch4j to do its thing. Launch4j needs a bunch of object files + * (in the w32api and head directories) and the ld and windres binaries (in the bin directory). + * The tricky part is that launch4j picks this directory based on where its own jar is sitting. + * In our case, the jar is going to be sitting in the user's ~/.m2 repository. That's okay: we know + * maven is allowed to write there. So we'll just add our things to that directory. + *

+ * This approach is not without flaws. + * It risks two processes writing to the directory at the same time. + * But fortunately, once the binary bits are in place, we don't do any more writing there, + * and launch4j doesn't write there either. + * Usually ~/.m2 will only be one system or another. + * But if it's an NFS mount shared by several system types, this approach will break. + *

+ * Okay, so here is a better proposal: package the plugin without these varying binary files, + * and put each set of binaries in its own tarball. Download the tarball you need to ~/.m2 and + * unpack it. Then different systems won't contend for the same space. But then I'll need to hack + * the l4j code so it permits passing in a work directory and doesn't always base it on + * the location of its own jarfile. + * + * @return the work directory. + */ + private File setupBuildEnvironment() throws MojoExecutionException { + Artifact binaryBits = chooseBinaryBits(); + retrieveBinaryBits(binaryBits); + return unpackWorkDir(binaryBits); + } + + /** + * Unzips the given artifact in-place and returns the newly-unzipped top-level directory. + * Writes a marker file to prevent unzipping more than once. + */ + private File unpackWorkDir(Artifact a) throws MojoExecutionException { + File platJar = a.getFile(); + File dest = platJar.getParentFile(); + File marker = new File(dest, platJar.getName() + ".unpacked"); + + // If the artifact is a SNAPSHOT, then a.getVersion() will report the long timestamp, + // but getFile() will be 1.1-SNAPSHOT. + // Since getFile() doesn't use the timestamp, all timestamps wind up in the same place. + // Therefore we need to expand the jar every time, if the marker file is stale. + if (marker.exists() && marker.lastModified() > platJar.lastModified()) { + // if (marker.exists() && marker.platJar.getName().indexOf("SNAPSHOT") == -1) { + getLog().info("Platform-specific work directory already exists: " + dest.getAbsolutePath()); + } else { + JarFile jf = null; + try { + // trying to use plexus-archiver here is a miserable waste of time: + jf = new JarFile(platJar); + Enumeration en = jf.entries(); + while (en.hasMoreElements()) { + JarEntry je = en.nextElement(); + File outFile = new File(dest, je.getName()); + File parent = outFile.getParentFile(); + if (parent != null) parent.mkdirs(); + if (je.isDirectory()) { + outFile.mkdirs(); + } else { + InputStream in = jf.getInputStream(je); + byte[] buf = new byte[1024]; + int len; + FileOutputStream fout = null; + try { + fout = new FileOutputStream(outFile); + while ((len = in.read(buf)) >= 0) { + fout.write(buf, 0, len); + } + in.close(); + fout.close(); + fout = null; + } finally { + if (fout != null) { + try { + fout.close(); + } catch (IOException e2) { + // ignore + } + } + } + outFile.setLastModified(je.getTime()); + } + } + } catch (IOException e) { + throw new MojoExecutionException("Error unarchiving " + platJar, e); + } finally { + try { + if (jf != null) jf.close(); + } catch (IOException e) { + // ignore + } + } + + try { + marker.createNewFile(); + marker.setLastModified(new Date().getTime()); + } catch (IOException e) { + getLog().warn("Trouble creating marker file " + marker, e); + } + } + + String n = platJar.getName(); + File workdir = new File(dest, n.substring(0, n.length() - 4)); + setPermissions(workdir); + return workdir; + } + + /** + * Chmods the helper executables ld and windres on systems where that is necessary. + */ + private void setPermissions(File workdir) { + if (!System.getProperty("os.name").startsWith("Windows")) { + try { + new ProcessBuilder("chmod", "755", workdir + "/bin/ld").start().waitFor(); + new ProcessBuilder("chmod", "755", workdir + "/bin/windres").start().waitFor(); + } catch (InterruptedException e) { + getLog().warn("Interrupted while chmodding platform-specific binaries", e); + } catch (IOException e) { + getLog().warn("Unable to set platform-specific binaries to 755", e); + } + } + } + + /** + * If custom header objects or libraries shall be linked, they need to sit inside the launch4j working dir. + */ + private List relativizeAndCopy(File workdir, List paths) throws MojoExecutionException { + if(paths == null) return null; + + List result = new ArrayList<>(); + for(String path : paths) { + Path source = basedir.toPath().resolve(path); + Path dest = workdir.toPath().resolve(basedir.toPath().relativize(source)); + + if(!source.startsWith(basedir.toPath())) { + throw new MojoExecutionException("File must reside in the project directory: " + path); + } + + if (Files.exists(source)) { + try { + Path target = Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING); + result.add(workdir.toPath().relativize(target).toString()); + } catch (IOException e) { + throw new MojoExecutionException("Can't copy file to workdir", e); + } + } else { + result.add(path); + } + } + + return result; + } + + /** + * Downloads the platform-specific parts, if necessary. + */ + private void retrieveBinaryBits(Artifact a) throws MojoExecutionException { + try { + resolver.resolve(a, project.getRemoteArtifactRepositories(), localRepository); + + } catch (ArtifactNotFoundException e) { + throw new MojoExecutionException("Can't find platform-specific components", e); + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Can't retrieve platform-specific components", e); + } + } + + /** + * Decides which platform-specific bundle we need, based on the current operating system. + */ + private Artifact chooseBinaryBits() throws MojoExecutionException { + String plat; + String os = System.getProperty("os.name"); + getLog().debug("OS = " + os); + + // See here for possible values of os.name: + // http://lopica.sourceforge.net/os.html + if (os.startsWith("Windows")) { + plat = "win32"; + } else if ("Linux".equals(os)) { + plat = "linux"; + } else if ("Solaris".equals(os) || "SunOS".equals(os)) { + plat = "solaris"; + } else if ("Mac OS X".equals(os) || "Darwin".equals(os)) { + plat = "mac"; + } else { + throw new MojoExecutionException("Sorry, Launch4j doesn't support the '" + os + "' OS."); + } + + return factory.createArtifactWithClassifier(LAUNCH4J_GROUP_ID, LAUNCH4J_ARTIFACT_ID, + getLaunch4jVersion(), "jar", "workdir-" + plat); + } + + + private File getBaseDir() { + return basedir; + } + + /** + * Just prints out how we were configured. + */ + private void printState() { + Log log = getLog(); + + log.debug("headerType = " + headerType); + log.debug("outfile = " + outfile); + log.debug("jar = " + jar); + log.debug("dontWrapJar = " + dontWrapJar); + log.debug("errTitle = " + errTitle); + log.debug("downloadUrl = " + downloadUrl); + log.debug("supportUrl = " + supportUrl); + log.debug("cmdLine = " + cmdLine); + log.debug("chdir = " + chdir); + log.debug("priority = " + priority); + log.debug("stayAlive = " + stayAlive); + log.debug("restartOnCrash = " + restartOnCrash); + log.debug("icon = " + icon); + log.debug("objs = " + objs); + log.debug("libs = " + libs); + log.debug("vars = " + vars); + if (singleInstance != null) { + log.debug("singleInstance.mutexName = " + singleInstance.mutexName); + log.debug("singleInstance.windowTitle = " + singleInstance.windowTitle); + } else { + log.debug("singleInstance = null"); + } + if (jre != null) { + log.debug("jre.path = " + jre.path); + log.debug("jre.minVersion = " + jre.minVersion); + log.debug("jre.maxVersion = " + jre.maxVersion); + log.debug("jre.jdkPreference = " + jre.jdkPreference); + log.debug("jre.initialHeapSize = " + jre.initialHeapSize); + log.debug("jre.initialHeapPercent = " + jre.initialHeapPercent); + log.debug("jre.maxHeapSize = " + jre.maxHeapSize); + log.debug("jre.maxHeapPercent = " + jre.maxHeapPercent); + log.debug("jre.opts = " + jre.opts); + } else { + log.debug("jre = null"); + } + if (classPath != null) { + log.debug("classPath.mainClass = " + classPath.mainClass); + log.debug("classPath.addDependencies = " + classPath.addDependencies); + log.debug("classPath.jarLocation = " + classPath.jarLocation); + log.debug("classPath.preCp = " + classPath.preCp); + log.debug("classPath.postCp = " + classPath.postCp); + } else { + log.info("classpath = null"); + } + if (splash != null) { + log.debug("splash.file = " + splash.file); + log.debug("splash.waitForWindow = " + splash.waitForWindow); + log.debug("splash.timeout = " + splash.timeout); + log.debug("splash.timoutErr = " + splash.timeoutErr); + } else { + log.debug("splash = null"); + } + if (versionInfo != null) { + log.debug("versionInfo.fileVersion = " + versionInfo.fileVersion); + log.debug("versionInfo.txtFileVersion = " + versionInfo.txtFileVersion); + log.debug("versionInfo.fileDescription = " + versionInfo.fileDescription); + log.debug("versionInfo.copyright = " + versionInfo.copyright); + log.debug("versionInfo.productVersion = " + versionInfo.productVersion); + log.debug("versionInfo.txtProductVersion = " + versionInfo.txtProductVersion); + log.debug("versionInfo.productName = " + versionInfo.productName); + log.debug("versionInfo.companyName = " + versionInfo.companyName); + log.debug("versionInfo.internalName = " + versionInfo.internalName); + log.debug("versionInfo.originalFilename = " + versionInfo.originalFilename); + } else { + log.debug("versionInfo = null"); + } + if (messages != null) { + log.debug("messages.startupErr = " + messages.startupErr); + log.debug("messages.bundledJreErr = " + messages.bundledJreErr); + log.debug("messages.jreVersionErr = " + messages.jreVersionErr); + log.debug("messages.launcherErr = " + messages.launcherErr); + log.debug("messages.instanceAlreadyExistsMsg = " + messages.instanceAlreadyExistsMsg); + } else { + log.debug("messages = null"); + } + } + + /** + * The Launch4j version used by the plugin. + * We want to download the platform-specific bundle whose version matches the Launch4j version, + * so we have to figure out what version the plugin is using. + * + * @return + * @throws MojoExecutionException + */ + private String getLaunch4jVersion() throws MojoExecutionException { + String version = null; + + for (Artifact artifact : pluginArtifacts) { + if (LAUNCH4J_GROUP_ID.equals(artifact.getGroupId()) && + LAUNCH4J_ARTIFACT_ID.equals(artifact.getArtifactId()) + && "core".equals(artifact.getClassifier())) { + + version = artifact.getVersion(); + getLog().debug("Found launch4j version " + version); + break; + } + } + + if (version == null) { + throw new MojoExecutionException("Impossible to find which Launch4j version to use"); + } + + return version; + } } diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/MavenLog.java b/src/main/java/com/akathist/maven/plugins/launch4j/MavenLog.java index a07ec66..1110e2d 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/MavenLog.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/MavenLog.java @@ -18,20 +18,24 @@ */ package com.akathist.maven.plugins.launch4j; +import org.apache.maven.plugin.logging.Log; + public class MavenLog extends net.sf.launch4j.Log { - org.apache.maven.plugin.logging.Log _log; + Log _log; - public MavenLog(org.apache.maven.plugin.logging.Log log) { - _log = log; - } + public MavenLog(Log log) { + _log = log; + } - public void clear() { - _log.info(""); - } + @Override + public void clear() { + _log.info(""); + } - public void append(String line) { - _log.info("launch4j: " + line); - } + @Override + public void append(String line) { + _log.info("launch4j: " + line); + } } diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/Messages.java b/src/main/java/com/akathist/maven/plugins/launch4j/Messages.java index ef1e6c2..99aecc7 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/Messages.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/Messages.java @@ -18,33 +18,35 @@ */ package com.akathist.maven.plugins.launch4j; +import net.sf.launch4j.config.Msg; + /** * Details about messages you can pass. */ public class Messages { - String startupErr; + String startupErr; + + String bundledJreErr; + + String jreVersionErr; - String bundledJreErr; + String launcherErr; - String jreVersionErr; - - String launcherErr; - - String instanceAlreadyExistsMsg; + String instanceAlreadyExistsMsg; - net.sf.launch4j.config.Msg toL4j() { - net.sf.launch4j.config.Msg ret = new net.sf.launch4j.config.Msg(); + Msg toL4j() { + Msg ret = new Msg(); - ret.setStartupErr(startupErr); - ret.setBundledJreErr(bundledJreErr); - ret.setJreVersionErr(jreVersionErr); - ret.setLauncherErr(launcherErr); - ret.setInstanceAlreadyExistsMsg(instanceAlreadyExistsMsg); + ret.setStartupErr(startupErr); + ret.setBundledJreErr(bundledJreErr); + ret.setJreVersionErr(jreVersionErr); + ret.setLauncherErr(launcherErr); + ret.setInstanceAlreadyExistsMsg(instanceAlreadyExistsMsg); - return ret; - } + return ret; + } } diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/SingleInstance.java b/src/main/java/com/akathist/maven/plugins/launch4j/SingleInstance.java index 23dcab9..a52cc40 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/SingleInstance.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/SingleInstance.java @@ -24,17 +24,17 @@ */ public class SingleInstance { - String mutexName; + String mutexName; - String windowTitle; + String windowTitle; - net.sf.launch4j.config.SingleInstance toL4j() { - net.sf.launch4j.config.SingleInstance ret = new net.sf.launch4j.config.SingleInstance(); + net.sf.launch4j.config.SingleInstance toL4j() { + net.sf.launch4j.config.SingleInstance ret = new net.sf.launch4j.config.SingleInstance(); - ret.setMutexName(mutexName); - ret.setWindowTitle(windowTitle); + ret.setMutexName(mutexName); + ret.setWindowTitle(windowTitle); - return ret; - } + return ret; + } } diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/Splash.java b/src/main/java/com/akathist/maven/plugins/launch4j/Splash.java index 3aa4337..a25c567 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/Splash.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/Splash.java @@ -20,46 +20,45 @@ import java.io.*; +import org.apache.maven.plugins.annotations.Parameter; + public class Splash { - /** - * The path (relative to the executable when distributed) to the splash page image. - */ - File file; + /** + * The path (relative to the executable when distributed) to the splash page image. + */ + File file; - /** - * If true, the splash screen will close automatically as soon as an error window or java window appears. - * If false, the splash screen will not close until {@link #timeout} sections. Defaults to true. - * - * @parameter default-value=true; - */ - boolean waitForWindow; + /** + * If true, the splash screen will close automatically as soon as an error window or java window appears. + * If false, the splash screen will not close until {@link #timeout} sections. Defaults to true. + */ + @Parameter(defaultValue = "true") + boolean waitForWindow; - /** - * The number of seconds to keep the splash screen open before automatically closing it. - * Defaults to 60. - * - * @parameter default-value=60 - */ - int timeout; + /** + * The number of seconds to keep the splash screen open before automatically closing it. + * Defaults to 60. + */ + @Parameter(defaultValue = "60") + int timeout; - /** - * If true, an error message will appear if the app hasn't started in {@link #timeout} seconds. - * If false, the splash screen will close quietly. Defaults to true. - * - * @parameter default-value=true - */ - boolean timeoutErr; + /** + * If true, an error message will appear if the app hasn't started in {@link #timeout} seconds. + * If false, the splash screen will close quietly. Defaults to true. + */ + @Parameter(defaultValue = "true") + boolean timeoutErr; - net.sf.launch4j.config.Splash toL4j() { - net.sf.launch4j.config.Splash ret = new net.sf.launch4j.config.Splash(); + net.sf.launch4j.config.Splash toL4j() { + net.sf.launch4j.config.Splash ret = new net.sf.launch4j.config.Splash(); - ret.setFile(file); - ret.setWaitForWindow(waitForWindow); - ret.setTimeout(timeout); - ret.setTimeoutErr(timeoutErr); + ret.setFile(file); + ret.setWaitForWindow(waitForWindow); + ret.setTimeout(timeout); + ret.setTimeoutErr(timeoutErr); - return ret; - } + return ret; + } } diff --git a/src/main/java/com/akathist/maven/plugins/launch4j/VersionInfo.java b/src/main/java/com/akathist/maven/plugins/launch4j/VersionInfo.java index 36ca7b2..dd85342 100644 --- a/src/main/java/com/akathist/maven/plugins/launch4j/VersionInfo.java +++ b/src/main/java/com/akathist/maven/plugins/launch4j/VersionInfo.java @@ -23,72 +23,72 @@ */ public class VersionInfo { - /** - * Version number in x.x.x.x format. - */ - String fileVersion; + /** + * Version number in x.x.x.x format. + */ + String fileVersion; - /** - * Free-form version number, like "1.20.RC1." - */ - String txtFileVersion; + /** + * Free-form version number, like "1.20.RC1." + */ + String txtFileVersion; - /** - * File description shown to the user. - */ - String fileDescription; + /** + * File description shown to the user. + */ + String fileDescription; - /** - * Legal copyright. - */ - String copyright; + /** + * Legal copyright. + */ + String copyright; - /** - * Version number in x.x.x.x format. - */ - String productVersion; + /** + * Version number in x.x.x.x format. + */ + String productVersion; - /** - * Free-form version number, like "1.20.RC1." - */ - String txtProductVersion; + /** + * Free-form version number, like "1.20.RC1." + */ + String txtProductVersion; - /** - * The product name. - */ - String productName; + /** + * The product name. + */ + String productName; - /** - * The company name. - */ - String companyName; + /** + * The company name. + */ + String companyName; - /** - * The internal name. For instance, you could use the filename without extension or the module name. - */ - String internalName; + /** + * The internal name. For instance, you could use the filename without extension or the module name. + */ + String internalName; - /** - * The original filename without path. Setting this lets you determine whether a user has renamed the file. - */ - String originalFilename; + /** + * The original filename without path. Setting this lets you determine whether a user has renamed the file. + */ + String originalFilename; - net.sf.launch4j.config.VersionInfo toL4j() { - net.sf.launch4j.config.VersionInfo ret = new net.sf.launch4j.config.VersionInfo(); + net.sf.launch4j.config.VersionInfo toL4j() { + net.sf.launch4j.config.VersionInfo ret = new net.sf.launch4j.config.VersionInfo(); - ret.setFileVersion(fileVersion); - ret.setTxtFileVersion(txtFileVersion); - ret.setFileDescription(fileDescription); - ret.setCopyright(copyright); - ret.setProductVersion(productVersion); - ret.setTxtProductVersion(txtProductVersion); - ret.setProductName(productName); - ret.setCompanyName(companyName); - ret.setInternalName(internalName); - ret.setOriginalFilename(originalFilename); + ret.setFileVersion(fileVersion); + ret.setTxtFileVersion(txtFileVersion); + ret.setFileDescription(fileDescription); + ret.setCopyright(copyright); + ret.setProductVersion(productVersion); + ret.setTxtProductVersion(txtProductVersion); + ret.setProductName(productName); + ret.setCompanyName(companyName); + ret.setInternalName(internalName); + ret.setOriginalFilename(originalFilename); - return ret; - } + return ret; + } @Override public String toString() { diff --git a/src/main/resources/README.adoc b/src/main/resources/README.adoc new file mode 100644 index 0000000..020112d --- /dev/null +++ b/src/main/resources/README.adoc @@ -0,0 +1,281 @@ += Maven Launch4j Plugin + +Copyright (C) 2006 Paul Jungwirth with additional changes by Lukasz Lenart +version 1.7.4, 13 February 2015 + +:toc: + +== Description + +http://launch4j.sourceforge.net/[Launch4j] by Grzegorz Kowal wraps a jar file +in a Windows executable to ease deployment of Java desktop applications. You +can either bundle a JRE or tell Launch4j to search the hard drive for an +existing one. If none is found, then Launch4j will show the user a download +page. Launch4j has many features. You can create either GUI or console +applications. You can show a splash screen while the JRE loads, give your +application a custom icon in the Windows task bar, set a more descriptive +process name (other than "java"), and set a variety of other process attributes. + +You can run Launch4j on Windows, Linux, Solaris, or OS X. You specify the +configuration through an XML file. Please see the Launch4j site for more +information on this file. You can also set your configuration via the Launch4j +GUI. + +The Maven plugin for Launch4j lets you generate the Launch4j executable as part +of the Maven build process. It supports Maven 2.0.4 and Launch4j 3.x. +Depending on your operating system, the plugin will download an additional artifact +containing platform-specific binaries that Launch4j uses to create the +executable. This artifact is treated like any other Maven dependency, so it is +only downloaded the first time you need it. + +== Adding plugin to pom.xml + +Using the Maven plugin, you specify the Launch4j configuration in your POM. I +hope to add support for external configuration files, but right now you have to +use the POM. The format of this configuration is very similar to the standard +Launch4j XML format. There are two main differences. First, any lists of +like-named elements must appear in a wrapper element. For example, you can't +say: + +[source,xml] +---- + logo.bin + this=that + foo=bar + blep=blurp +---- + +You must say: + +[source,xml] +---- + logo.bin + + this=that + foo=bar + blep=blurp + +---- + +Likewise for `` and `` elements. + +Second, the sub-elements of the `` element are a little different. +This is so you can set the classpath based on your dependencies. `` +still takes a `` element, but it does not take ``. Instead, it +supports these children: + + * `` - If you set this to "true," the plugin will build your + classpath based on all dependencies in the runtime and + compile scopes. This is on by default. + + * `` - If you are using the addDependencies feature, you can + use this option to add a prefix before each jar's name. + This is useful if you are bundling your app with the + executable alongside a lib directory that contains all + your jars. If that's what you're doing, you would specify + `lib/`. + + * `` - Use this to add classpath entries before the automatically- + generated list. This element functions whether you have + enabled `` or not. Entries in the list should + be separated by semicolons, as in a Windows-style `CLASSPATH` + variable. + + * `` - Use this to add classpath entries after the automatically- + generated list. This element functions whether you have + enabled `` or not. Entries in the list should + be separated by semicolons, as in a Windows-style `CLASSPATH` + variable. + +Other than these changes, the XML format is just like Launch4j's standard +format. + +== Examples + +=== Single-module project + +By default, the Launch4j plugin is bound to the package phase. Suppose you have +a single-module project named encc. It is a console application, not a GUI. It +is packaged as a jar, so the jarring runs automatically during the package +phase before anything else. You want to use launch4j to create an executable +and then use the assembly plugin to bundle everything up. You could bind both +launch4j and assembly to the package phase with a POM like this: + + +[source,xml] +---- + + . . . + com.akathist.encc + encc + jar + . . . + + + + com.akathist.maven.plugins.launch4j + launch4j-maven-plugin + + + l4j-clui + package + launch4j + + console + target/encc.exe + target/encc-1.0.jar + encc + + com.akathist.encc.Clui + false + anything + + + 1.5.0 + + -Djava.endorsed.dirs=./endorsed + + + + 1.2.3.4 + txt file version? + a description + my copyright + 4.3.2.1 + txt product version + E-N-C-C + ccne + original.exe + + + + + + + maven-assembly-plugin + + + assembly + package + attached + + + assembly.xml + + + + + + + + . . . + +---- + +Note that when you bind the assembly plugin to a phase, you must use +`assembly:attached`, not `assembly:assembly`, to prevent its forking a parallel +lifecycle and running everything twice. + +=== GUI and console mode + +Or suppose your application can run in either GUI or console mode, and you want +to create separate executables for each. Then your POM would look like this: + +[source,xml] +---- + + . . . + com.akathist.encc + encc + jar + . . . + + + + com.akathist.maven.plugins.launch4j + launch4j-maven-plugin + + + l4j-clui + package + launch4j + + console + target/encc.exe + target/encc-1.0.jar + encc + + com.akathist.encc.Clui + false + anything + + + 1.5.0 + + + 1.2.3.4 + txt file version? + a description + my copyright + 4.3.2.1 + txt product version + E-N-C-C + ccne + original.exe + + + + + l4j-gui + package + launch4j + + gui + target/enccg.exe + target/encc-1.0.jar + enccg + + com.akathist.encc.Gui + + + 1.5.0 + + + 1.2.3.4 + txt file version? + a description + my copyright + 4.3.2.1 + txt product version + E-N-C-C + ccne + original.exe + + + + + + + maven-assembly-plugin + + + assembly + package + attached + + + assembly.xml + + + + + + + + . . . + +---- + +If you have any questions, please register a ticket! + +Enjoy!