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

de.ntcomputer.executablepacker.mavenplugin.PackExecutableJarMojo Maven / Gradle / Ivy

package de.ntcomputer.executablepacker.mavenplugin;

import java.io.File;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.archiver.MavenArchiveConfiguration;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
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.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;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.Archiver;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.JarArchiver;

import de.ntcomputer.executablepacker.runtime.ExecutableLauncher;
import net.sf.corn.cps.CPScanner;
import net.sf.corn.cps.ClassFilter;

/**
 * Builds an executable JAR file from the current project, containing all dependency JAR files.
 * Supports most parameters that the maven-jar-plugin supports too.
 *
 * @author Nikolaus Thuemmel
 */
@Mojo(name = "pack-executable-jar", requiresDependencyResolution = ResolutionScope.RUNTIME, defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true)
public class PackExecutableJarMojo extends AbstractMojo {
	private static final String[] DEFAULT_EXCLUDES = new String[] { "**/package.html" };
	private static final String[] DEFAULT_INCLUDES = new String[] { "**/**" };

	/**
	 * List of files to include from the classesDirectory. Specified as fileset patterns which are relative to the input directory whose contents
	 * is being packaged into the JAR.
	 */
	@Parameter
	private String[] includes;

	/**
	 * List of files to exclude from the classesDirectory. Specified as fileset patterns which are relative to the input directory whose contents
	 * is being packaged into the JAR.
	 */
	@Parameter
	private String[] excludes;

	/**
	 * Directory where the generated JAR should be saved in.
	 */
	@Parameter(defaultValue = "${project.build.directory}", required = true)
	private File outputDirectory;
	
	/**
     * Classifier to add to the artifact generated.
     * For example, if the classifier is "pkg", the artifact will be named "(ProjectNameAndVersion)-pkg.jar".
     * The artifact will be attached as a supplemental artifact.
     */
    @Parameter(defaultValue = "pkg", required = true)
    private String classifier;

	/**
	 * Name of the generated JAR (the classifier and .jar extension will be added to it, though).
	 */
	@Parameter(defaultValue = "${project.build.finalName}", required = true, readonly = true)
	private String finalName;

	/**
	 * The Jar archiver.
	 */
	@Component(role = Archiver.class, hint = "jar")
	private JarArchiver jarArchiver;

	/**
	 * The {@link {MavenProject}.
	 */
	@Parameter(defaultValue = "${project}", readonly = true, required = true)
	private MavenProject project;
	
	@Component
    private MavenProjectHelper projectHelper;

	/**
	 * The {@link MavenSession}.
	 */
	@Parameter(defaultValue = "${session}", readonly = true, required = true)
	private MavenSession session;

	/**
	 * The archive configuration to use. See Maven
	 * Archiver Reference.
	 */
	@Parameter
	private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();

	/**
	 * Require the jar plugin to build a new JAR even if none of the contents appear to have changed. By default, this
	 * plugin looks to see if the output jar exists and inputs have not changed. If these conditions are true, the
	 * plugin skips creation of the jar. This does not work when other plugins, like the maven-shade-plugin, are
	 * configured to post-process the jar. This plugin can not detect the post-processing, and so leaves the
	 * post-processed jar in place. This can lead to failures when those plugins do not expect to find their own output
	 * as an input. Set this parameter to true to avoid these problems by forcing this plugin to recreate the
	 * jar every time.
*/ @Parameter(property = "maven.jar.forceCreation", defaultValue = "false") private boolean forceCreation; /** * Directory containing the classes and resource files that should be packaged into the JAR. */ @Parameter(defaultValue = "${project.build.outputDirectory}", required = true) private File classesDirectory; /** * An (optional) subdirectory to put the libraries in. By default, "lib" is used. * If the parameter is empty, the libraries will be packed into the root of the final JAR file. * Nested subdirectories may be specified in the usual unix syntax (e.g. "dependencies/libs") */ @Parameter(defaultValue = "lib", required = false) private String libPath; /** * The class containing the main method to be run when executing the final JAR file. */ @Parameter(required = true) private String mainClass; private String[] getIncludes() { if (includes != null && includes.length > 0) { return includes; } return DEFAULT_INCLUDES; } private String[] getExcludes() { if (excludes != null && excludes.length > 0) { return excludes; } return DEFAULT_EXCLUDES; } public void execute() throws MojoExecutionException, MojoFailureException { // determine output file File outputJarFile = new File(outputDirectory, finalName + "-" + classifier + ".jar"); getLog().debug("Creating JAR file at '" + outputJarFile.getAbsolutePath() + "'"); // normalize library path String realLibPath = Paths.get(libPath).normalize().toString() + "/"; getLog().info("Dependency JAR files will be placed in '" + realLibPath + "'"); // build a list of all relevant dependency JAR files List dependencyJarFiles = new ArrayList(); StringBuilder dependencyJarFileMetaStringBuilder = new StringBuilder(); boolean dependencyJarFileFirst = true; for (Artifact dependencyArtifact: project.getArtifacts()) { // include only compile-time and run-time dependencies if (Artifact.SCOPE_COMPILE.equals(dependencyArtifact.getScope()) || Artifact.SCOPE_RUNTIME.equals(dependencyArtifact.getScope())) { // include only JAR files if ("jar".equals(dependencyArtifact.getType())) { File dependencyFile = dependencyArtifact.getFile(); getLog().info("Including dependency " + dependencyFile.getName()); dependencyJarFiles.add(dependencyFile); if(dependencyJarFileFirst) { dependencyJarFileFirst = false; } else { dependencyJarFileMetaStringBuilder.append("/"); } dependencyJarFileMetaStringBuilder.append(dependencyFile.getName()); } } } // create the archiver, set configuration (including dependencies and both launcher and application main class) MavenArchiver archiver = new MavenArchiver(); archiver.setArchiver(jarArchiver); archiver.setOutputFile(outputJarFile); archive.setForced(forceCreation); archive.addManifestEntry("Main-Class", ExecutableLauncher.class.getName()); archive.addManifestEntry(ExecutableLauncher.MANIFEST_APPLICATION_MAIN_CLASS, mainClass); archive.addManifestEntry(ExecutableLauncher.MANIFEST_DEPENDENCY_LIBPATH, realLibPath); archive.addManifestEntry(ExecutableLauncher.MANIFEST_DEPENDENCY_JARS, dependencyJarFileMetaStringBuilder.toString()); try { // include all built classes if(classesDirectory.exists()) { getLog().debug("Including classes directory '" + classesDirectory.getAbsolutePath() + "'"); archiver.getArchiver().addDirectory(classesDirectory, getIncludes(), getExcludes()); } else { getLog().debug("Classes directory '" + classesDirectory.getAbsolutePath() + "' does not exist, not including in output JAR file"); } // include all dependency JAR files for(File dependencyJarFile: dependencyJarFiles) { String destinationFilePath = realLibPath + dependencyJarFile.getName(); getLog().debug("Including dependency JAR file '" + dependencyJarFile.getAbsolutePath() + "' as '" + destinationFilePath + "'"); archiver.getArchiver().addFile(dependencyJarFile, destinationFilePath); } // include the executable launcher classes containing the classloader code List> launcherRuntimeClasses = CPScanner.scanClasses(new ClassFilter().packageName(ExecutableLauncher.class.getPackage().getName())); for(Class runtimeClass: launcherRuntimeClasses) { try { String classFilePath = runtimeClass.getName().replace(".", "/") + ".class"; URL classUrl = ExecutableLauncher.class.getClassLoader().getResource(classFilePath); if(classUrl==null) { throw new ArchiverException("Failed to resolve class file path '" + classFilePath + "' to a URL"); } getLog().debug("Including launcher runtime class '" + classUrl + "' as '" + classFilePath + "'"); URLResource resource = URLResource.create(classUrl); archiver.getArchiver().addResource(resource, classFilePath, archiver.getArchiver().getDefaultFileMode()); } catch(Exception e) { throw new ArchiverException("Failed to include launcher class '" + runtimeClass.getName() + "' to the executable JAR file", e); } } // create JAR archiver.createArchive(session, project, archive); } catch (Exception e) { throw new MojoExecutionException("Error packing executable JAR file '" + outputJarFile.getAbsolutePath() + "'", e); } // attach built JAR to the project projectHelper.attachArtifact(project, "jar", classifier, outputJarFile); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy