com.github.christokios.CapsuleMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of capsule-maven-plugin Show documentation
Show all versions of capsule-maven-plugin Show documentation
The maven plugin to build capsules of your jars.
package com.github.christokios;
import javafx.util.Pair;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.*;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import java.io.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;
@Mojo(name = "capsule", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyCollection = ResolutionScope.RUNTIME)
public class CapsuleMojo extends AbstractMojo {
public static final String CAPSULE_GROUP = "co.paralleluniverse";
public static final String DEFAULT_CAPSULE_NAME = "Capsule";
public static final String DEFAULT_CAPSULE_CLASS = DEFAULT_CAPSULE_NAME + ".class";
public static enum Type {
empty,
thin,
fat
}
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject mavenProject;
/**
* * AETHER REPO LINK **
*/
@Component
private RepositorySystem repoSystem;
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
private RepositorySystemSession repoSession;
@Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true)
private List remoteRepos;
@Parameter(defaultValue = "${project.build.finalName}", readonly = true)
private String finalName;
/**
* * REQUIRED VARIABLES **
*/
@Parameter(property = "capsule.appClass", required = true)
private String appClass;
/**
* * OPTIONAL VARIABLES **
*/
@Parameter(property = "capsule.version", defaultValue = "0.6.0-SNAPSHOT")
private String capsuleVersion;
@Parameter(property = "capsule.outputDir", defaultValue = "${project.build.directory}")
private File outputDir;
@Parameter(property = "capsule.buildExec", defaultValue = "false")
private String buildExec;
@Parameter
private Properties properties; // System-Properties for the app
@Parameter
private Properties manifest; // additional manifest entries
private String mainClass = DEFAULT_CAPSULE_NAME;
/**
* * DEPENDENCIES **
*/
@Parameter(defaultValue = "${project.artifacts}") // will only contain scope of compile+runtime
private Collection artifacts;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// check for custom capsule main class
if (manifest.getProperty(Attributes.Name.MAIN_CLASS.toString()) != null)
mainClass = (String) manifest.getProperty(Attributes.Name.MAIN_CLASS.toString());
getLog().info("[Capsule] Capsule Version: " + capsuleVersion.toString());
getLog().info("[Capsule] Output Directory: " + outputDir.toString());
getLog().info("[Capsule] Application-Class: " + appClass);
getLog().info("[Capsule] Main-Class: " + mainClass);
if (manifest != null) {
getLog().info("[Capsule] Manifest Entries: ");
for (final Map.Entry property : manifest.entrySet()) getLog().info("\t\t\\--" + property.getKey() + ": " + property.getValue());
}
if (properties != null) {
getLog().info("[Capsule] System Properties: ");
for (final Map.Entry property : properties.entrySet()) getLog().info("\t\t\\--" + property.getKey() + "=" + property.getValue());
}
getLog().info("[Capsule] Dependencies: ");
for (final Dependency dependency : (List) mavenProject.getDependencies()) {
if (dependency.getScope().equals("compile") || dependency.getScope().equals("runtime")) {
if (dependency.getExclusions().size() == 0)
getLog().info("\t\t\\--" + dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion());
else {
final StringBuilder exclusionsList = new StringBuilder();
for (final Exclusion exclusion : dependency.getExclusions()) {
if (dependency.getExclusions().size() > 1) exclusionsList.append(",");
exclusionsList.append(exclusion.getGroupId() + ":" + exclusion.getArtifactId());
}
getLog().info("\t\t\\--" + dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion() + "(" + exclusionsList + ")");
}
}
}
try {
buildEmpty();
buildThin();
buildFat();
} catch (final IOException e) {
e.printStackTrace();
throw new MojoFailureException(e.getMessage());
}
}
/**
* Build the empty version of the capsule, i.e the the app and its dependencies will be downloaded at runtime.
*/
public final void buildEmpty() throws IOException {
final Pair jar = openJar(Type.empty);
final JarOutputStream jarStream = jar.getValue();
// add manifest (plus Application)
final Map additionalAttributes = new HashMap();
additionalAttributes.put("Application", mavenProject.getGroupId() + ":" + mavenProject.getArtifactId() + ":" + mavenProject.getVersion());
deployManifestToJar(jarStream, additionalAttributes, Type.empty);
// add Capsule classes
final Map otherCapsuleClasses = getAllCapsuleClasses();
for (final Map.Entry entry : otherCapsuleClasses.entrySet())
addToJar(entry.getKey(), new ByteArrayInputStream(entry.getValue()), jarStream);
// add custom capsule class (if exists)
if (!mainClass.equals(DEFAULT_CAPSULE_CLASS)) addCustomCapsuleClass(jarStream);
IOUtil.close(jarStream);
this.createExecCopy(jar.getKey());
}
/**
* Build the thin version of the capsule (i.e no dependencies). The dependencies will be resolved at runtime.
*/
public final void buildThin() throws IOException {
final Pair jar = openJar(Type.thin);
final JarOutputStream jarStream = jar.getValue();
// add manifest (with Dependencies list)
final Map additionalAttributes = new HashMap();
final StringBuilder dependenciesList = new StringBuilder();
for (final Dependency dependency : (List) mavenProject.getDependencies()) {
if (dependency.getScope().equals("compile") || dependency.getScope().equals("runtime")) {
dependenciesList.append(dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion());
if (dependency.getExclusions().size() > 0) {
final StringBuilder exclusionsList = new StringBuilder();
for (final Exclusion exclusion : dependency.getExclusions()) {
if (dependency.getExclusions().size() > 1) exclusionsList.append(",");
exclusionsList.append(exclusion.getGroupId() + ":" + exclusion.getArtifactId());
}
dependenciesList.append("(" + exclusionsList.toString() + ")");
}
dependenciesList.append(" ");
}
}
additionalAttributes.put("Dependencies", dependenciesList.toString());
deployManifestToJar(jarStream, additionalAttributes, Type.thin);
// add compiled project classes
final File classesDir = new File(outputDir, "classes");
Files.walkFileTree(classesDir.toPath(), new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException {
if (!attrs.isDirectory())
addToJar(path.toString().substring(path.toString().indexOf("classes") + 8), new FileInputStream(path.toFile()), jarStream);
return FileVisitResult.CONTINUE;
}
});
// add Capsule classes
final Map capsuleClasses = getAllCapsuleClasses();
for (final Map.Entry entry : capsuleClasses.entrySet())
addToJar(entry.getKey(), new ByteArrayInputStream(entry.getValue()), jarStream);
IOUtil.close(jarStream);
this.createExecCopy(jar.getKey());
}
/**
* Build the fat version of the capsule which includes the dependencies embedded.
*/
public final void buildFat() throws IOException {
final Pair jar = openJar(Type.fat);
final JarOutputStream jarStream = jar.getValue();
// add manifest
deployManifestToJar(jarStream, null, Type.fat);
// add main jar
final File mainJarFile = new File(outputDir, finalName + ".jar");
addToJar(mainJarFile.getName(), new FileInputStream(mainJarFile), jarStream);
// add dependencies
for (final Artifact artifact : artifacts)
addToJar(artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jarStream);
// add Capsule.class
this.addToJar(DEFAULT_CAPSULE_CLASS, new ByteArrayInputStream(getCapsuleClass()), jarStream);
// add custom capsule class (if exists)
if (!mainClass.equals(DEFAULT_CAPSULE_CLASS)) addCustomCapsuleClass(jarStream);
IOUtil.close(jarStream);
this.createExecCopy(jar.getKey());
}
/**
* UTILS
*/
private JarOutputStream deployManifestToJar(final JarOutputStream jar, final Map additionalAttributes, final Type type) throws IOException {
final Manifest manifestBuild = new Manifest();
final Attributes attributes = manifestBuild.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
attributes.put(Attributes.Name.MAIN_CLASS, mainClass);
attributes.put(new Attributes.Name("Application-Class"), this.appClass);
attributes.put(new Attributes.Name("Application-Name"), this.finalName + "-capsule-" + type);
if (this.properties != null) {
final StringBuilder propertiesList = new StringBuilder();
for (final Map.Entry