![JAR search and dependency download from the Maven repository](/logo.png)
org.tentackle.maven.plugin.jlink.JPackageMojo Maven / Gradle / Ivy
Show all versions of tentackle-jlink-maven-plugin Show documentation
/*
* Tentackle - https://tentackle.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.tentackle.maven.plugin.jlink;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
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.toolchain.Toolchain;
import org.tentackle.common.FileHelper;
import org.tentackle.common.Settings;
import org.tentackle.common.StringHelper;
import org.tentackle.common.ToolRunner;
import org.tentackle.maven.AbstractTentackleMojo;
import org.tentackle.maven.JavaToolFinder;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* Creates a java application installer with the jpackage
tool.
*
* The mojo works in 4 phases:
*
* - Invokes the
jlink
tool as described in {@link JLinkMojo}. This will generate
* a directory holding the runtime image. However, no run or update scripts and no zip file will be created.
* - Invokes the
jpackage
tool to generate the application image from the previously
* created runtime image. Application- and platform specific options can be configured via the {@link #packageImageTemplate}.
* - If the runtime image contains extra classpath- or modulepath-elements, the generated config files
* will be patched. This is especially necessary to provide the correct
* classpath order according to the maven/module dependency tree, which usually differs
* from the one determined by
jpackage
, because jpackage
has no idea about the maven project structure
* and does its own guess according to the packages referenced from within the jars.
* This may become an issue if the classpath order is critical, such as configurations overridden in META-INF.
* - Finally, the installer will be generated from the application image. The {@link #packageInstallerTemplate}
* is used to provide additional options to the
jpackage
tool.
*
*
* The minimum plugin configuration is very simple:
*
*
* ...
* <packaging>jpackage</packaging>
* ...
* <plugin>
* <groupId>org.tentackle</groupId>
* <artifactId>tentackle-jlink-maven-plugin</artifactId>
* <version>${tentackle.version}</version>
* <extensions>true</extensions>
* <configuration>
* <mainModule>com.example</mainModule>
* <mainClass>com.example.MyApp</mainClass>
* </configuration>
* </plugin>
*
*
* The freemarker templates are copied to the project's template folder, if missing.
* They become part of the project and can be changed easily according to project specific needs (for example by adding runtime arguments).
* To install and edit the templates before running jpackage (or jlink, see {@link JLinkMojo}), use {@link InitMojo} first.
* In addition to the template variables defined by the {@link JLinkMojo}, the variable runtimeDir
is provided
* pointing to the runtime image directory (which is platform specific).
*
* If the application is built with Tentackle's update feature, please keep in mind that
* applications deployed by an installer are maintained by a platform specific
* package manager. If the installation is system-wide, the installation files cannot be changed by
* a regular user.
* Some platforms, however, also provide per-user installations that can be updated. For Windows, the jpackage
* tool provides the option --win-per-user-install
. MacOS allows the user to decide whether to install
* system-wide or for the current user only. See {@link AbstractJLinkMojo#isWithUpdater()} for more details.
*
* If both jlink zip-files and jpackage installers are required, change the packaging type to jar
* and add executions, like this:
*
* <executions>
* <execution>
* <id>both</id>
* <goals>
* <goal>jlink</goal>
* <goal>jpackage</goal>
* </goals>
* </execution>
* </executions>
*
* The jpackage goal will then re-use the previously created jlink image.
*
* The contents of the application image and attachment of the artifacts for installation and deployment can be customized by an
* application-specific implementation.
* To do so, provide a plugin dependency that contains a class annotated with {@code @Service(}{@link ArtifactCreator}).
*
* Important: the jpackage
tool is available since Java 14.
*
* Notice that you can create an image for a different java version than the one used by the maven build process
* via {@link AbstractTentackleMojo#getToolchain()}. Furthermore, you can select the jpackage
tool explicitly from another JDK
* via {@link #jpackageToolchain} or {@link #jpackageTool}.
*/
@Mojo(name = "jpackage",
requiresDependencyResolution = ResolutionScope.RUNTIME,
defaultPhase = LifecyclePhase.PACKAGE)
public class JPackageMojo extends AbstractJLinkMojo {
/** filename of the options template to create the application image. */
public static final String PACKAGE_IMAGE_TEMPLATE = "package-image.ftl";
/** filename of the options template to create the installer. */
public static final String PACKAGE_INSTALLER_TEMPLATE = "package-installer.ftl";
/** filename of the template to create the package updater script. */
public static final String PACKAGE_UPDATE_TEMPLATE = "package-update.ftl";
/** filename of the generated jpackage options to create the application image. */
public static final String OPTIONS_IMAGE = "jpackage-image.options";
/** filename of the generated jpackage options to create the installer. */
public static final String OPTIONS_INSTALLER = "jpackage-installer.options";
/** extension of the config files (platform independent). */
private static final String CONFIG_EXTENSION = ".cfg";
/** extension added by macOS to the application folder's name. */
private static final String MAC_APPNAME_EXTENSION = ".app";
/** config variable for the classpath. */
private static final String APP_CLASSPATH = "app.classpath=";
/** config variable for the main jar. */
private static final String APP_MAINJAR = "app.mainjar=";
/** config section for the java options. */
private static final String JAVA_OPTIONS = "[JavaOptions]";
/** name of the subdir jpackage >= 15 puts the modulpath jars. */
private static final String MODS_DIR = "mods";
/** config for the module path (>= java 15). */
private static final String APPDIR_MODS = "java-options=$APPDIR/" + MODS_DIR;
/** config for the module path (>= java 15 on windows) */
private static final String APPDIR_MODS_WINDOWS = "java-options=$APPDIR\\" + MODS_DIR;
/**
* Explicit path to the jpackage
tool.
* Only if toolchains cannot be used.
* @see #jpackageToolchain
*/
@Parameter(property = "jpackageTool")
private File jpackageTool;
/**
* Toolchain for invocation of the jpackage
tool only.
* Allows to create installers for older java runtime versions not supporting the jpackage tool yet.
*
* Example:
*
* <jpackageToolchain>
* <version>14</version>
* </jpackageToolchain>
*
* To deselect the toolchain configured by the maven-toolchain-plugin:
*
* <jpackageToolchain></jpackageToolchain>
*
*/
@Parameter
private Map jpackageToolchain;
/**
* The filename of the jpackage options template to create the application image.
*/
@Parameter(defaultValue = PACKAGE_IMAGE_TEMPLATE)
private String packageImageTemplate;
/**
* The filename of the jpackage options template to create the installer.
*/
@Parameter(defaultValue = PACKAGE_INSTALLER_TEMPLATE)
private String packageInstallerTemplate;
/**
* The name of the update script template.
*/
@Parameter(defaultValue = PACKAGE_UPDATE_TEMPLATE)
private String packageUpdateTemplate;
/**
* The directory where to create the installers.
*/
@Parameter(defaultValue = "${project.build.directory}/jpackage")
private File packageDirectory;
/**
* The name of the main jar holding the main class.
* Only necessary for classpath applications and only if the top-level artifact of the maven dependency tree is not the main jar.
* A unique substring is sufficient, e.g. "myapp-server".
*/
@Parameter
private String mainJar;
/**
* Major version of the jpackage tool.
*/
private int jpackageMajorRuntimeVersion;
/**
* Lazily created image path prefix according to the platform.
*/
private String imagePathPrefix;
@Override
public String getImagePathPrefix() {
if (imagePathPrefix == null) {
String platform = StringHelper.getPlatform();
if (platform.contains("win")) {
imagePathPrefix = "$ROOTDIR/runtime"; // no backslashes!
}
else if (platform.contains("mac")) {
imagePathPrefix = "$ROOTDIR/runtime/Contents/Home";
}
else { // linux
if (jpackageMajorRuntimeVersion >= 15) {
// since 15 the ROOTDIR is no more defined (see linux/native/applauncher/Package.cpp).
// However, if we move the installation to another directory, so that dpkg can't find it anymore,
// ROOTDIR will be set properly. Bug or feature?
// anyway... -> using APPDIR instead fixes this issue
// TODO: check whether fixed in future versions (JDK 15 RC2 at time of writing)
imagePathPrefix = "$APPDIR/../runtime";
}
else {
imagePathPrefix = "$ROOTDIR/lib/runtime";
}
}
}
return imagePathPrefix;
}
/**
* Gets the name of the package template to create the application image.
*
* @return the template file name
*/
public String getPackageImageTemplate() {
return packageImageTemplate;
}
/**
* Gets the name of the package template to create the installer.
*
* @return the template file name
*/
public String getPackageInstallerTemplate() {
return packageInstallerTemplate;
}
/**
* Gets the name of the package updater template.
*
* @return the template file name, null if update feature disabled
*/
public String getPackageUpdateTemplate() {
return isWithUpdater() ? packageUpdateTemplate : null;
}
/**
* Gets the directory where to create the installers.
*
* @return the installer target directory
*/
public File getPackageDirectory() {
return packageDirectory;
}
@Override
public void prepareExecute() throws MojoExecutionException, MojoFailureException {
super.prepareExecute();
if (jpackageTool == null) {
JavaToolFinder toolFinder;
if (jpackageToolchain == null) {
toolFinder = getToolFinder();
}
else {
if (jpackageToolchain.isEmpty()) {
toolFinder = new JavaToolFinder(); // explicitly no toolchain!
}
else {
Toolchain toolchain = getToolchain(JDK_TOOLCHAIN, jpackageToolchain);
if (toolchain == null) {
throw new MojoExecutionException("no matching toolchain for the jpackage tool: " + jpackageToolchain);
}
toolFinder = new JavaToolFinder(toolchain);
}
}
jpackageTool = toolFinder.find("jpackage");
}
}
@Override
public void executeImpl() throws MojoExecutionException, MojoFailureException {
long startTime = System.currentTimeMillis(); // to detect generated installers for deployment
super.executeImpl();
ArtifactCreator.getInstance().attachInstallers(this, startTime);
}
@Override
protected void createImage(JLinkResolver.Result result) throws MojoFailureException, MojoExecutionException {
if (getImageDirectory().exists()) {
getLog().info("using already existing jlink image in " + getImageDirectory().getPath());
copyResources(result); // to add the "conf" directory to the classpath, if resources found
}
else {
super.createImage(result);
}
}
@Override
protected void generateFiles(JLinkResolver.Result result) throws MojoExecutionException {
// generate the options file for the jpackage tool
JPackageGenerator generator = new JPackageGenerator(this, result);
generator.generateOptions();
String mainJarName = null; // only for classpath apps
File cpDir = null; // the classpath artifact directory (only for classpath apps)
// create app-image from the runtime-image previously generated by jlink
ToolRunner jpackageRunner = new ToolRunner(jpackageTool);
jpackageRunner.arg("--type").arg("app-image")
.arg("-d").arg(packageDirectory)
.arg("--runtime-image").arg(getImageDirectory());
if (result.isModular()) {
ModularArtifact mainArtifact = result.getModuleArtifact(getMainModule()); // just check before
if (mainArtifact == null) {
throw new MojoExecutionException("no such main module: " + getMainModule());
}
if (result.isModulePathRequired() && jpackageMajorRuntimeVersion >= 15) {
// jpackage >= 15 needs a module path if built from with --runtime-image
// we will remove the artifacts copied to the app directory below...
jpackageRunner.arg("-p").arg(getImageDirectory() + "/" + DEST_MODULEPATH);
}
jpackageRunner.arg("-m").arg(getMainModule() + "/" + getMainClass());
}
else {
mainJarName = StringHelper.isAllWhitespace(mainJar) ? determineMainJar(result) : mainJar;
cpDir = new File(getImageDirectory(), DEST_CLASSPATH);
jpackageRunner.arg("--main-jar").arg(mainJarName)
.arg("--main-class").arg(getMainClass())
.arg("--input").arg(cpDir);
}
jpackageRunner.arg("--java-options").arg("-DROOTDIR=$ROOTDIR")
.arg("--java-options").arg("-DAPPDIR=$APPDIR")
.arg("--java-options").arg("-DRUNTIMEDIR=" + getImagePathPrefix())
.arg("@" + getMavenProject().getBuild().getDirectory() + File.separator + OPTIONS_IMAGE);
if (verbosityLevel.isDebug()) {
jpackageRunner.arg("--verbose");
}
getLog().debug(jpackageRunner.toString());
getLog().info("creating application image");
int errCode = jpackageRunner.run().getErrCode();
if (errCode != 0) {
throw new MojoExecutionException("creating application image with jpackage failed: " + errCode + "\n" + jpackageRunner.getErrorsAsString() +
"\n" + jpackageRunner.getOutputAsString());
}
logToolOutput(jpackageRunner);
String[] imageDirNames = getPackageDirectory().list();
if (imageDirNames == null || imageDirNames.length != 1) {
throw new MojoExecutionException("cannot locate application image directory in " + getPackageDirectory().getPath());
}
String applicationName = imageDirNames[0];
File appImageDir = new File(getPackageDirectory(), applicationName);
getLog().debug("application image found in " + appImageDir.getPath());
ArtifactCreator.getInstance().processApplicationImage(this, appImageDir);
File configDir = locateAppDirectory(appImageDir);
if (cpDir != null) { // classpath app
// remove all jars in the classpath (cpDir) directory from the "app" directory.
// They have been copied from cpDir to appDir (see --input above)
String[] names = cpDir.list();
if (names != null) {
for (String name : names) {
new File(configDir, name).delete();
}
}
}
if (configDir != null && (result.isClassPathRequired() || result.isModulePathRequired())) {
// cfg file must be modified.
// Notice that we cannot add the classpath via -cp in [JavaOptions] section, since it is ignored there -- for whatever reason.
// Instead, we must set app.classpath in the [Application] section. Notice further that -p works and there
// is no app.modulepath.
List configFiles = loadConfigFiles(configDir);
if (configFiles.isEmpty()) {
throw new MojoExecutionException("no application configuration files found");
}
for (File configFile : configFiles) {
patchConfigFile(configFile, result, mainJarName);
}
if (jpackageMajorRuntimeVersion >= 15) {
// remove duplicate jars (see above)
File modsDir = new File(configDir, MODS_DIR);
if (modsDir.isDirectory()) {
try {
getLog().debug("removing " + modsDir);
FileHelper.removeDirectory(modsDir.getPath());
}
catch (IOException e) {
throw new MojoExecutionException("cannot remove obsolete module directory " + modsDir);
}
}
}
}
// run jpackage again to create the installer(s) from the app-image
jpackageRunner = new ToolRunner(jpackageTool);
// remove ".app" from application name, if on MAC
if (applicationName.endsWith(MAC_APPNAME_EXTENSION)) {
applicationName = applicationName.substring(0, applicationName.length() - MAC_APPNAME_EXTENSION.length());
}
jpackageRunner.arg("-n").arg(applicationName) // can be overridden via options file below
.arg("--app-image").arg(appImageDir);
// version must only contain digits and dots (else error "Version ... contains invalid component ...", at least on windows)
StringBuilder appVersion = new StringBuilder();
String projectVersion = getMavenProject().getVersion();
for (int i=0; i < projectVersion.length(); i++) {
char c = projectVersion.charAt(i);
if (c == '.' || Character.isDigit(c)) {
appVersion.append(c);
}
else {
break;
}
}
if (!appVersion.isEmpty()) {
jpackageRunner.arg("--app-version").arg(appVersion); // can be overridden via options file below
}
jpackageRunner.arg("-d").arg(getMavenProject().getBuild().getDirectory())
.arg("@" + getMavenProject().getBuild().getDirectory() + File.separator + OPTIONS_INSTALLER);
if (verbosityLevel.isDebug()) {
jpackageRunner.arg("--verbose");
}
getLog().debug(jpackageRunner.toString());
getLog().info("creating installer");
errCode = jpackageRunner.run().getErrCode();
if (errCode != 0) {
throw new MojoExecutionException("creating installer with jpackage failed: " + errCode + "\n" + jpackageRunner.getErrorsAsString() +
"\n" + jpackageRunner.getOutputAsString());
}
logToolOutput(jpackageRunner);
if (isWithUpdater()) {
getLog().info("creating updater");
generator.generateUpdateScript(appImageDir);
ArtifactCreator.getInstance().createAndAttachArtifact(this, appImageDir);
}
}
@Override
protected boolean validate() throws MojoExecutionException {
if (super.validate()) {
if (jpackageTool == null) {
throw new MojoExecutionException("jpackageTool tool not found");
}
getLog().debug("using " + jpackageTool);
jpackageMajorRuntimeVersion = getMajorVersion(determineJavaToolVersion(jpackageTool));
getLog().debug("major version of jpackage is " + jpackageMajorRuntimeVersion);
if (jpackageMajorRuntimeVersion != getJavaMajorRuntimeVersion()) {
getLog().warn("jpackage tool's major version " + jpackageMajorRuntimeVersion + " differs from jlink/jdeps version " + getJavaMajorRuntimeVersion());
}
if (packageImageTemplate == null) {
throw new MojoExecutionException("packageImageTemplate missing");
}
if (packageInstallerTemplate == null) {
throw new MojoExecutionException("packageInstallerTemplate missing");
}
if (isWithUpdater() && StringHelper.isAllWhitespace(packageUpdateTemplate)) {
throw new MojoExecutionException("packageUpdateTemplate missing");
}
return true;
}
return false;
}
@Override
protected void installTemplates(boolean overwrite) throws MojoExecutionException {
installTemplate(PACKAGE_IMAGE_TEMPLATE, packageImageTemplate, overwrite);
installTemplate(PACKAGE_INSTALLER_TEMPLATE, packageInstallerTemplate, overwrite);
if (getPackageUpdateTemplate() != null) {
installTemplate(PACKAGE_UPDATE_TEMPLATE, packageUpdateTemplate, overwrite);
}
}
/**
* Patches a configuration file.
* For modular apps it adds the modulepath via the [JavaOptions]
section
* and sets the classpath via the app.classpath
variable.
* For non-modular apps the mainjar and the classpath are replaced in order to
* point to the runtime image and not the app directory and to provide the correct
* classpath order according to the maven/module dependency tree. The order usually differs
* from the one determined by jpackage, because jpackage has no idea about the maven project structure
* (which may become an issue when classpath order is critical, such as configuration overrides in META-INF).
*
* @param configFile the config file
* @param result the resolver result
* @param mainJarName the name of the mainjar (only if classpath application)
* @throws MojoExecutionException if patch failed
*/
private void patchConfigFile(File configFile, JLinkResolver.Result result, String mainJarName) throws MojoExecutionException {
final boolean patchClassPath = result.isClassPathRequired();
boolean classPathPatched = false;
final boolean patchModulePath = result.isModulePathRequired();
boolean modulePathPatched = false;
final boolean patchMainJar = !result.isModular();
boolean mainJarPatched = false;
getLog().debug("patching configfile " + configFile);
StringBuilder buf = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(configFile), Settings.getEncodingCharset()))) {
String line;
while ((line = reader.readLine()) != null) {
buf.append(line);
if (patchClassPath && line.startsWith(APP_CLASSPATH)) {
if (classPathPatched && jpackageMajorRuntimeVersion >= 15) {
// starting with Java 15 app.classpath is used once per artifact: just ignore succeeding lines
getLog().debug("removed line: " + line);
buf.setLength(buf.length() - line.length());
continue;
}
// replace the classpath
getLog().debug("old classpath = " + line.substring(APP_CLASSPATH.length()));
buf.setLength(buf.length() - line.length() + APP_CLASSPATH.length());
buf.append(result.generateClassPath());
classPathPatched = true;
}
if (patchMainJar && line.startsWith(APP_MAINJAR)) {
if (mainJarPatched) {
throw new MojoExecutionException("more than one '" + APP_MAINJAR + "' found in " + configFile);
}
// replace mainjar
getLog().debug("old mainjar = " + line.substring(APP_MAINJAR.length()));
buf.setLength(buf.length() - line.length() + APP_MAINJAR.length());
buf.append(getImagePathPrefix()).append('/').append(DEST_CLASSPATH).append('/').append(mainJarName);
mainJarPatched = true;
}
if (patchModulePath) {
if (line.equals(JAVA_OPTIONS)) {
if (modulePathPatched) {
throw new MojoExecutionException("more than one '" + JAVA_OPTIONS + "' found in " + configFile);
}
if (jpackageMajorRuntimeVersion >= 15) {
if (patchClassPath && !classPathPatched) {
// missing app.classname (beginning with java 15)
buf.setLength(buf.length() - JAVA_OPTIONS.length() - 2);
buf.append('\n').append(APP_CLASSPATH).append(result.generateClassPath())
.append("\n\n").append(JAVA_OPTIONS);
classPathPatched = true;
}
}
else {
// insert the module path
buf.append("\n-p\n").append(result.generateModulePath());
modulePathPatched = true;
}
}
else if ((line.equals(APPDIR_MODS) || line.equals(APPDIR_MODS_WINDOWS)) && jpackageMajorRuntimeVersion >= 15) {
if (modulePathPatched) {
throw new MojoExecutionException("more than one '" + APPDIR_MODS + "' found in " + configFile);
}
// java options end with module path: replace it
buf.setLength(buf.length() - APPDIR_MODS.length());
buf.append("java-options=").append(result.generateModulePath()).append('\n');
modulePathPatched = true;
}
}
buf.append('\n');
}
}
catch (IOException e) {
throw new MojoExecutionException("cannot read from config file " + configFile, e);
}
if (patchClassPath && !classPathPatched) {
throw new MojoExecutionException("could not patch classpath");
}
if (patchModulePath && !modulePathPatched) {
throw new MojoExecutionException("could not patch modulepath");
}
try (Writer configWriter = Files.newBufferedWriter(configFile.toPath(), Settings.getEncodingCharset(),
StandardOpenOption.TRUNCATE_EXISTING)) {
configWriter.append(buf);
}
catch (IOException e) {
throw new MojoExecutionException("could not patch config file " + configFile, e);
}
}
/**
* Loads the config files from the given directory.
*
* @param configDir the directory holding the config files
* @return the list of files, never null
*/
private List loadConfigFiles(File configDir) {
List configFiles = new ArrayList<>();
// the cfg files are located in a subdirectory called "app". Depending on the platform, it can be
// found in lib, Contents or without a further subdir.
String[] configNames = configDir.list((d, n) -> n.toLowerCase(Locale.ROOT).endsWith(CONFIG_EXTENSION));
if (configNames != null) {
for (String configName : configNames) {
configFiles.add(new File(configDir, configName));
}
}
return configFiles;
}
/**
* Locates the app directory within the application image directory.
* Tries the different locations of the known platforms.
*
* @param appImageDir the application image directory
* @return the app directory, null if not found
*/
private File locateAppDirectory(File appImageDir) {
File appDir = loadAppDirectory(appImageDir); // windoze?
if (appDir == null) {
// linux or mac
appDir = loadAppDirectory(new File(appImageDir, "lib"));
if (appDir == null) {
// mac?
appDir = loadAppDirectory(new File(appImageDir, "Contents"));
}
}
return appDir;
}
/**
* Loads the "app" directory if it is a subdir of given directory.
*
* @param dir the directory to look for the app directory
* @return the app directory, null if not in dir
*/
private File loadAppDirectory(File dir) {
// the cfg files are located in a subdirectory called "app". Depending on the platform, it can be
// found in lib, Contents or without a further subdir.
String[] names = dir.list();
if (names != null) {
for (String name : names) {
if ("app".equalsIgnoreCase(name)) {
return new File(dir, name);
}
}
}
return null;
}
/**
* Determines the main jar of the application.
*
* @param result the resolver result
* @return the name of the main jar, never null
* @throws MojoExecutionException if main jar could not be determined
*/
private String determineMainJar(JLinkResolver.Result result) throws MojoExecutionException {
String jarName = null;
if (mainJar == null) {
jarName = result.getClassPath().getFirst().getFile().getName();
}
else {
for (Artifact artifact : result.getClassPath()) {
if (artifact.getFile().getName().contains(mainJar)) {
jarName = artifact.getFile().getName();
break;
}
}
}
if (jarName == null) {
throw new MojoExecutionException("cannot determine main jar");
}
return jarName;
}
}