All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.maven.plugin.jlink.AbstractJLinkMojo Maven / Gradle / Ivy

There is a newer version: 21.16.1.0
Show newest version
/*
 * 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.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.zip.ZipArchiver;
import org.codehaus.plexus.languages.java.jpms.LocationManager;

import org.tentackle.common.FileHelper;
import org.tentackle.common.StringHelper;
import org.tentackle.common.ToolRunner;
import org.tentackle.maven.AbstractTentackleMojo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;


/**
 * Base class for all mojos of the tentackle-jlink-maven-plugin.
 *
 * @author harald
 */
public abstract class AbstractJLinkMojo extends AbstractTentackleMojo {

  /**
   * Name of the destination subfolder containing the classpath artifacts.
   */
  public static final String DEST_CLASSPATH = "cp";

  /**
   * Name of the destination subfolder containing the modulepath artifacts.
   */
  public static final String DEST_MODULEPATH = "mp";

  /**
   * Name of the destination subfolder containing the resources.
   */
  public static final String DEST_RESOURCES = "conf";

  /**
   * File extension for ZIP files.
   */
  public static final String ZIP_EXTENSION = "zip";



  /**
   * The name of the module holding the main class.
* If missing, the plugin will build an application running on the classpath. */ @Parameter private String mainModule; /** * The name of the main class. */ @Parameter(required = true) private String mainClass; /** * Dependencies that should go to the classpath instead of modulepath. */ @Parameter private List classpathDependencies; /** * Extra directories to copy to the image. */ @Parameter private List extraDirectories; /** * Extra classpath elements.
* Pathnames are relative to the image root. */ @Parameter private List extraClasspathElements; /** * The directory holding the script templates. */ @Parameter(defaultValue = "${project.basedir}/templates") private File templateDir; /** * Explicit path to the jdeps tool.
* Only if toolchains cannot be used. * @see #jdkToolchain */ @Parameter(property = "jdepsTool") private File jdepsTool; /** * Explicit path to the jlink tool.
* Only if toolchains cannot be used. * @see #jdkToolchain */ @Parameter(property = "jlinkTool") private File jlinkTool; /** * The optional file holding the jlink options.
* Corresponds to the jlink option --save-opts. */ @Parameter private File saveOpts; /** * Enables the auto update feature. *

* For the jlink gaol an extra update script will be generated * via the {@link JLinkMojo#updateTemplate}. * The update script will be named bin/update.<extension> where * the extension is derived from the platform (cmd on Windows, * otherwise sh for unixes). *

* For the jpackage goal an extra ZIP-file is created holding * the jpackage image's files and an update script generated via * {@link JPackageMojo#packageUpdateTemplate}. *

* For an implementation of the auto-update feature see the Tentackle modules * tentackle-update and tentackle-fx-rdc-update. * Whereas tentackle-update can be used by any kind of application, * the frontend part tentackle-fx-rdc-update requires a Tentackle FX * application. */ @Parameter private boolean withUpdater; /** * List of full-blown modules that should be moved to the module path explicitly.
* By default, all full-blown modules are passed to the jlink tool. If a module is listed * for the module path only, the module it will be moved to the mp folder instead.
* This may become necessary, if a 3rd-party full-blown module (illegally!) requires automatic modules, which * would cause all artifacts to be moved to the module path by this plugin automatically, since the * jlink tool can only handle full-blown modules. *

* If all modules must be moved to the mp folder, the single values true, * all or * can be used as a shortcut. *

* Examples: *

   *    <modulePathOnly>
   *        <module>tentackle.log.log4j2v</module>
   *        <module>org.apache.logging.log4j</module>
   *    </modulePathOnly>
   * 
* or *
   *   <modulePathOnly>all</modulePathOnly>
   * 
*/ @Parameter private List modulePathOnly; /** * The directory where to store the ZIP files. */ @Parameter(defaultValue = "${project.build.directory}") private File zipDirectory; /** * Strip debug information.
* Corresponds to the jlink option --strip-debug. */ @Parameter(defaultValue = "false") private boolean stripDebug; /** * Compression of resources.
* Corresponds to the jlink option --compress. */ @Parameter private Integer compress; /** * Suppresses a fatal error when signed modular JARs are linked in the runtime image.
* Corresponds to the jlink option --ignore-signing-information */ @Parameter(defaultValue = "false") private boolean ignoreSigningInformation; /** * Suppress the includes directory.
* Corresponds to the jlink option --no-header-files. */ @Parameter(defaultValue = "false") private boolean noHeaderFiles; /** * Suppress the man directory.
* Corresponds to the jlink option --no-man-pages */ @Parameter(defaultValue = "false") private boolean noManPages; /** * The directory holding extra resources, such as logger configurations or database * credentials, that will be copied to the conf directory.
* Defaults to the maven-resources-plugin's destination directory, usually target/classes.
* If no resources must be copied, for example in server-images for production, set this to any * non-existent directory, for example "none". */ @Parameter(defaultValue = "${project.build.directory}/classes") private File resourcesDirectory; /** * Extra jre or jdk modules to add to the image.
* Useful to add the runtime localization module or extra tools in bin. *

* Example: * *

   * <addModules>
   *   <addModule>jdk.localedata</addModule>
   *   <addModule>jdk.jcmd</addModule>
   * </addModules>
   * 
*/ @Parameter private List addModules; /** * Modules to exclude.
* Necessary only if a dependency refers to removed api even if not used at all at runtime. * *
   * <excludeModules>
   *   <excludeModule>jdk8internals</excludeModule>
   * </excludeModules>
   * 
*/ @Parameter private List excludeModules; /** * Extra variables for template or artifact generation. *

* For example the subproject's basedir: *

   *  <variables>
   *    <project>${project.basedir}</project>
   *  </variables>
   * 
* And then use it in package-image.ftl for file paths, such as: *
   *   --icon ${project}/src/pkg/myapp.ico
   * 
*/ @Parameter private Map variables; /** * Defines the precedence of properties and variables.
* By default, system properties are overridden by maven properties which are overridden by the extra template variables. *

* The case-insensitive keywords are: *

    *
  • {@code system, sys}: for system properties
  • *
  • {@code maven, mvn, properties, props, pom}: maven properties
  • *
  • {@code variables, vars, var, extras, extra, template}: extra template variables
  • *
*/ @Parameter(defaultValue = "system, maven, variables", required = true) private String variablesPrecedence; /** * The directory created by jlink holding the image. */ @Parameter(defaultValue = "${project.build.directory}/jlink", required = true) private File imageDirectory; /** * Additional classifier to add to deployed artifacts. */ @Parameter private String extraClassifier; /** * The generated ZIP file name. */ @Parameter(defaultValue = "${project.build.finalName}", required = true, readonly = true) private String finalName; /** * JPMS support. */ @Component private LocationManager locationManager; /** * To attach artifacts with classifier. */ @Component private MavenProjectHelper projectHelper; /** * The archiver to zip the image. */ @Component(role = Archiver.class, hint = ZIP_EXTENSION) private ZipArchiver zipArchiver; /** * The java runtime version used by the application. */ private String javaRuntimeVersion; /** * The java major version as a comparable integer. */ private int javaMajorRuntimeVersion; /** * Gets the ZIP file's name without extension.
* Invoked by the {@link ArtifactCreator}. * * @return the final name */ public String getFinalName() { return finalName; } /** * Returns whether the update feature is enabled. * * @return true if generate artifacts with auto-update feature */ public boolean isWithUpdater() { return withUpdater; } /** * Returns whether all application modules should go to the module path and * not the jimage file. * * @return true if forced to mp folder */ public boolean isModulePathOnly() { return modulePathOnly != null && modulePathOnly.size() == 1 && ("true".equals(modulePathOnly.get(0)) || "all".equals(modulePathOnly.get(0)) || "*".equals(modulePathOnly.get(0))); } /** * Returns whether given module name should go to the module path and * not the jimage file. * * @param moduleName the name of the full-blown module * @return true if forced to mp folder */ public boolean isModulePathOnly(String moduleName) { return modulePathOnly != null && modulePathOnly.contains(moduleName); } /** * Gets the directory where to store the ZIP-file. * * @return the ZIP target folder */ public File getZipDirectory() { return zipDirectory; } /** * Gets the template directory. * * @return the directory holding the wizard templates */ public File getTemplateDir() { return templateDir; } /** * Gets the directory created by jlink holding the image. * * @return the image dir, never null */ public File getImageDirectory() { return imageDirectory; } /** * Gets the name of the module holding the main class. * * @return the main module, null or empty string to build an application running on the classpath */ public String getMainModule() { return mainModule; } /** * Gets the name of the main class. * * @return the main class, never null */ public String getMainClass() { return mainClass; } /** * Gets the location manager to extract module information. * * @return the JPMS manager */ public LocationManager getLocationManager() { return locationManager; } /** * Gets the project helper to attach artifacts. * * @return the project helper */ public MavenProjectHelper getProjectHelper() { return projectHelper; } /** * Gets the jdeps tool. * * @return the file, never null */ public File getJdepsTool() { return jdepsTool; } /** * Gets extra jre or jdk modules to add to the image. * * @return extra module names, never null */ public List getAddModules() { return addModules == null ? Collections.emptyList() : addModules; } /** * Gets the modules to exclude. * * @return excluded module names, never null */ public List getExcludeModules() { return excludeModules == null ? Collections.emptyList() : excludeModules; } /** * Gets the extra variables for templates or artifact generation. * * @return the variables, never null */ public Map getVariables() { return variables == null ? Collections.emptyMap() : variables; } /** * Gets the order how properties and variables override each other. * * @return default is system, maven, variables */ public String getVariablesPrecedence() { return variablesPrecedence; } /** * Gets the optional extra deployment classifier. * * @return the extra classifier */ public String getExtraClassifier() { return extraClassifier; } /** * Gets optional extra classpath elements.
* * @return pathnames relative to the image root, null if none */ public List getExtraClasspathElements() { return extraClasspathElements; } /** * Extra directories to copy to the image. * * @return the directories, null if none */ public List getExtraDirectories() { return extraDirectories; } /** * Gets the java runtime version used by the application. * * @return the java version */ public String getJavaRuntimeVersion() { return javaRuntimeVersion; } /** * Gets the java major runtime version as a comparable integer. * * @return the major version */ public int getJavaMajorRuntimeVersion() { return javaMajorRuntimeVersion; } @Override public void prepareExecute() throws MojoExecutionException, MojoFailureException { if (jdepsTool == null) { jdepsTool = getToolFinder().find("jdeps"); } if (jlinkTool == null) { jlinkTool = getToolFinder().find("jlink"); } } @Override public void executeImpl() throws MojoExecutionException, MojoFailureException { // analyze project, determine jlink strategy JLinkResolver.Result result = new JLinkResolver(this, getMavenProject().getArtifacts()).resolve(); // generate image createImage(result); // and the shell scripts generateFiles(result); } /** * The zip file will be installed and deployed with an additional classifier * showing the operating system and the platform. * * @return the classifier * @throws MojoExecutionException if no os.name */ public String getClassifier() throws MojoExecutionException { try { String classifier = StringHelper.getPlatform() + "-" + StringHelper.getArchitecture(); if (!StringHelper.isAllWhitespace(getExtraClassifier())) { classifier += "-" + getExtraClassifier(); } return classifier; } catch (RuntimeException ex) { throw new MojoExecutionException(ex.getMessage()); } } /** * Gets the prefix to the jars and dirs in the runtime image. * * @return the prefix, empty string if none (never null) */ public String getImagePathPrefix() { return ""; } /** * Creates the zip file.
* Invoked by the {@link ArtifactCreator}. * * @param dir the directory to create a zip file from * @param name the zip filename without extension * @return the created zip file * @throws MojoExecutionException if some archiver or IO errors occurred */ public File createZipFile(File dir, String name) throws MojoExecutionException { File zipFile = new File(zipDirectory, name + "." + ZIP_EXTENSION); zipArchiver.addDirectory(dir); zipArchiver.setDestFile(zipFile); try { zipArchiver.createArchive(); } catch (ArchiverException | IOException e) { throw new MojoExecutionException(e.getMessage(), e); } return zipFile; } /** * Returns whether an artifact should be moved to the classpath explicitly. * * @param artifact the artifact * @return true if move to classpath, else modulepath */ public boolean isClasspathDependency(Artifact artifact) { if (classpathDependencies != null) { for (ClasspathDependency classpathDependency: classpathDependencies) { if (Objects.equals(classpathDependency.getGroupId(), artifact.getGroupId()) && Objects.equals(classpathDependency.getArtifactId(), artifact.getArtifactId()) && Objects.equals(classpathDependency.getClassifier(), artifact.getClassifier())) { return true; } } } return false; } /** * Generates additional files such as shell scripts or command files. * * @param result the resolver result * @throws MojoExecutionException if generation failed */ abstract protected void generateFiles(JLinkResolver.Result result) throws MojoExecutionException; /** * Copies the templates to the template directory. * * @param overwrite true if overwrite existing templates, false if install only missing * @throws MojoExecutionException if installation failed */ abstract protected void installTemplates(boolean overwrite) throws MojoExecutionException; /** * Invokes the jlink tool and copies other artifacts and resources. * * @param result the resolver result */ protected void createImage(JLinkResolver.Result result) throws MojoFailureException, MojoExecutionException { // run jlink createJLinkImage(result); // copy artifacts not covered by jlink to modulepath and classpath subdirectories copyArtifacts(result); // copy resources such as backend.properties or logging config to the conf subdirectory copyResources(result); // extra directories, if any copyExtraDirectories(); } /** * Creates the jlink image. * * @param result the resolver info * @throws MojoExecutionException if building the JPMS info failed * @throws MojoFailureException if jlink returned an error code */ protected void createJLinkImage(JLinkResolver.Result result) throws MojoExecutionException, MojoFailureException { getLog().info("creating jlink image for a " + (result.isPlainModular() ? "plain " : "") + (result.isModular() ? "modular" : "classpath") + " application with Java " + javaRuntimeVersion); getLog().debug(result.toString()); ToolRunner jlinkRunner = new ToolRunner(jlinkTool); if (saveOpts != null) { jlinkRunner.arg("--save-opts").arg(saveOpts); } jlinkRunner.arg("--output").arg(getImageDirectory()); if (stripDebug) { jlinkRunner.arg("--strip-debug"); } if (compress != null) { jlinkRunner.arg("--compress=" + compress); } if (ignoreSigningInformation) { jlinkRunner.arg("--ignore-signing-information"); } if (noHeaderFiles) { jlinkRunner.arg("--no-header-files"); } if (noManPages) { jlinkRunner.arg("--no-man-pages"); // is this implemented in jlink at all??? } result.generateJlinkModulePath(jlinkRunner); result.generateJlinkModules(jlinkRunner); getLog().debug(jlinkRunner.toString()); int errCode = jlinkRunner.run().getErrCode(); if (errCode != 0) { throw new MojoFailureException("jlink failed: " + errCode + "\n" + jlinkRunner.getErrorsAsString() + "\n" + jlinkRunner.getOutputAsString()); } } /** * Copies artifacts not already in the created jlink image to module- or classpath. * * @param result the resolver info * @throws MojoExecutionException if copy failed */ protected void copyArtifacts(JLinkResolver.Result result) throws MojoExecutionException { try { // module path, if any if (result.isModular()) { List modulePath = result.getModulePath(); if (!modulePath.isEmpty()) { File mpDir = new File(getImageDirectory(), DEST_MODULEPATH); mpDir.mkdir(); for (ModularArtifact artifact : modulePath) { File dest = new File(mpDir, artifact.getFileName()); getLog().debug("copying " + artifact.getPath() + " to " + mpDir); FileHelper.copy(artifact.getFile(), dest); } } } // classpath, if any List classPath = result.getClassPath(); if (!classPath.isEmpty()) { File cpDir = new File(getImageDirectory(), DEST_CLASSPATH); cpDir.mkdir(); for (Artifact artifact : classPath) { File dest = new File(cpDir, artifact.getFile().getName()); getLog().debug("copying " + artifact.getFile() + " to " + cpDir); FileHelper.copy(artifact.getFile(), dest); } } } catch (IOException ex) { throw new MojoExecutionException("copying artifacts failed", ex); } } /** * Copies the resources to the conf directory. * * @param result the resolver info * @throws MojoExecutionException if copy failed */ protected void copyResources(JLinkResolver.Result result) throws MojoExecutionException { // copy resources such as backend.properties or logging config to the conf subdirectory if (resourcesDirectory != null && resourcesDirectory.isDirectory()) { int resCount = copyResources(resourcesDirectory, new File(getImageDirectory(), DEST_RESOURCES)); if (resCount > 0) { result.addToClasspath(DEST_RESOURCES); } } } /** * Copies optional extra directories to the image directory. * * @throws MojoExecutionException if failed */ protected void copyExtraDirectories() throws MojoExecutionException { // extra directories, if any try { if (getExtraDirectories() != null) { for (File fromDir : getExtraDirectories()) { File toDir = new File(getImageDirectory(), fromDir.getName()); FileHelper.copyDirectory(fromDir, toDir, true, null); } } } catch (IOException ex) { throw new MojoExecutionException("copying extra directories failed", ex); } } @Override protected boolean validate() throws MojoExecutionException { if (super.validate()) { if (jdepsTool == null) { throw new MojoExecutionException("jdeps tool not found"); } getLog().debug("using " + jdepsTool); javaRuntimeVersion = determineJavaToolVersion(jdepsTool); javaMajorRuntimeVersion = getMajorVersion(javaRuntimeVersion); if (jlinkTool == null) { throw new MojoExecutionException("jlink tool not found"); } getLog().debug("using " + jlinkTool); if (templateDir.exists()) { if (!templateDir.isDirectory()) { throw new MojoExecutionException(templateDir.getPath() + " is not a directory"); } } else { getLog().info("template directory created: " + templateDir.getPath()); } installTemplates(false); // install any missing template if (mainClass == null) { throw new MojoExecutionException("mainClass missing"); } return true; } return false; } /** * Installs the template with the given name. * * @param pluginTemplate the plugin's name of the template * @param projectTemplate the project's name of the template * @param overwrite true if overwrite existing template, false if leave unchanged * @throws MojoExecutionException if installation failed */ protected void installTemplate(String pluginTemplate, String projectTemplate, boolean overwrite) throws MojoExecutionException { templateDir.mkdirs(); File file = new File(templateDir, projectTemplate); if (overwrite || !file.exists()) { String path = "/templates/" + pluginTemplate; String text = loadResourceFileIntoString(path); try (PrintStream ps = new PrintStream(new FileOutputStream(file))) { ps.print(text); getLog().info("installed template " + projectTemplate); } catch (IOException e) { throw new MojoExecutionException("cannot install template " + path, e); } } } /** * Logs the toolrunner's output at info level. * * @param runner the toolrunner * @throws MojoExecutionException if runner not invoked so far */ protected void logToolOutput(ToolRunner runner) throws MojoExecutionException { String out = runner.getOutputAsString(); if (!out.isEmpty()) { getLog().info(out); } } /** * Copies the resources. * * @param srcDir the resource directory * @param dstDir the destination directory * @return number of resource files copied * @throws MojoExecutionException if copy failed */ private int copyResources(File srcDir, File dstDir) throws MojoExecutionException { try { return FileHelper.copyDirectory(srcDir, dstDir,true, null); } catch (IOException ex) { throw new MojoExecutionException("copying resources failed", ex); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy