com.github.stormbit.packer.Packer Maven / Gradle / Ivy
The newest version!
package com.github.stormbit.packer;
import com.lexicalscope.jewel.cli.ArgumentValidationException;
import com.lexicalscope.jewel.cli.CliFactory;
import com.lexicalscope.jewel.cli.ValidationFailure;
import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.commons.IOUtils;
import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
public class Packer {
private PackerConfig config;
private Predicate removePlatformLibsFileFilter;
public Packer() {
this.removePlatformLibsFileFilter = (f -> false);
}
public Packer setRemovePlatformLibsFileFilter(final Predicate filter) {
this.removePlatformLibsFileFilter = filter;
return this;
}
public void pack(final PackerConfig config) throws IOException {
config.validate();
this.config = config;
PackerOutput output = new PackerOutput(config.outDir, config.outDir);
this.cleanOrCreateOutputFolder(output);
output = this.buildMacBundle(output);
this.copyExecutableAndClasspath(output);
this.writeConfig(output);
this.copyAndMinimizeJRE(output, config);
this.copyResources(output);
PackerReduce.removePlatformLibs(output, config, this.removePlatformLibsFileFilter);
System.out.println("Done!");
}
private void cleanOrCreateOutputFolder(final PackerOutput output) throws IOException {
final File folder = output.executableFolder;
if (folder.exists()) {
System.out.println("Cleaning output directory '" + folder.getAbsolutePath() + "' ...");
PackerFileUtils.deleteDirectory(folder);
}
PackerFileUtils.mkdirs(folder);
}
private PackerOutput buildMacBundle(final PackerOutput output) throws IOException {
if (this.config.platform != PackerConfig.Platform.MacOS) {
return output;
}
final Map values = new HashMap<>();
values.put("${executable}", this.config.executable);
if (this.config.bundleIdentifier != null) {
values.put("${bundleIdentifier}", this.config.bundleIdentifier);
} else {
values.put("${bundleIdentifier}", this.config.mainClass.substring(0, this.config.mainClass.lastIndexOf(46)));
}
final File root = output.executableFolder;
PackerFileUtils.mkdirs(new File(root, "Contents"));
try (final FileWriter info = new FileWriter(new File(root, "Contents/Info.plist"))) {
final String plist = this.readResourceAsString("/Info.plist", values);
info.write(plist);
}
final File target = new File(root, "Contents/MacOS");
PackerFileUtils.mkdirs(target);
final File resources = new File(root, "Contents/Resources");
PackerFileUtils.mkdirs(resources);
if (this.config.iconResource != null && this.config.iconResource.exists()) {
PackerFileUtils.copyFile(this.config.iconResource, new File(resources, "icons.icns"));
}
return new PackerOutput(target, resources);
}
private void copyExecutableAndClasspath(final PackerOutput output) throws IOException {
byte[] exe = null;
String extension = "";
switch (this.config.platform) {
case Windows32: {
exe = this.readResource("/packr-windows.exe");
extension = ".exe";
break;
}
case Windows64: {
exe = this.readResource("/packr-windows-x64.exe");
extension = ".exe";
break;
}
case Linux32: {
exe = this.readResource("/packr-linux");
break;
}
case Linux64: {
exe = this.readResource("/packr-linux-x64");
break;
}
case MacOS: {
exe = this.readResource("/packr-mac");
break;
}
}
System.out.println("Copying executable ...");
try (final OutputStream writer = new FileOutputStream(new File(output.executableFolder, this.config.executable + extension))) {
writer.write(exe);
}
PackerFileUtils.chmodX(new File(output.executableFolder, this.config.executable + extension));
System.out.println("Copying classpath(s) ...");
for (final String file : this.config.classpath) {
final File cpSrc = new File(file);
final File cpDst = new File(output.resourcesFolder, new File(file).getName());
if (cpSrc.isFile()) {
PackerFileUtils.copyFile(cpSrc, cpDst);
} else if (cpSrc.isDirectory()) {
PackerFileUtils.copyDirectory(cpSrc, cpDst);
} else {
System.err.println("Warning! Classpath not found: " + cpSrc);
}
}
}
private void writeConfig(final PackerOutput output) throws IOException {
final StringBuilder builder = new StringBuilder();
builder.append("{\n");
builder.append(" \"classPath\": [");
String delim = "\n";
for (final String f : this.config.classpath) {
builder.append(delim).append(" \"").append(new File(f).getName()).append("\"");
delim = ",\n";
}
builder.append("\n ],\n");
builder.append(" \"mainClass\": \"").append(this.config.mainClass).append("\",\n");
builder.append(" \"vmArgs\": [\n");
for (int i = 0; i < this.config.vmArgs.size(); ++i) {
final String vmArg = this.config.vmArgs.get(i);
builder.append(" \"");
if (!vmArg.startsWith("-")) {
builder.append("-");
}
builder.append(vmArg).append("\"");
if (i < this.config.vmArgs.size() - 1) {
builder.append(",");
}
builder.append("\n");
}
builder.append(" ]\n");
builder.append("}");
try (final Writer writer = new FileWriter(new File(output.resourcesFolder, "config.json"))) {
writer.write(builder.toString());
}
}
private void copyAndMinimizeJRE(final PackerOutput output, final PackerConfig config) throws IOException {
final boolean extractToCache = config.cacheJre != null;
boolean skipExtractToCache = false;
if (extractToCache && config.cacheJre.exists()) {
if (!config.cacheJre.isDirectory()) {
throw new IOException(config.cacheJre + " must be a directory");
}
final String[] files = config.cacheJre.list();
skipExtractToCache = (files != null && files.length > 0);
}
final File jreStoragePath = extractToCache ? config.cacheJre : output.resourcesFolder;
if (skipExtractToCache) {
System.out.println("Using cached JRE in '" + config.cacheJre + "' ...");
} else {
final boolean fetchFromRemote = config.jdk.startsWith("http://") || config.jdk.startsWith("https://");
final File jdkFile = fetchFromRemote ? new File(jreStoragePath, "jdk.zip") : new File(config.jdk);
if (fetchFromRemote) {
System.out.println("Downloading JDK from '" + config.jdk + "' ...");
try (final InputStream remote = new URL(config.jdk).openStream();
final OutputStream outJdk = new FileOutputStream(jdkFile)) {
IOUtils.copy(remote, outJdk);
}
}
System.out.println("Unpacking JRE ...");
final File tmp = new File(jreStoragePath, "tmp");
if (tmp.exists()) {
PackerFileUtils.deleteDirectory(tmp);
}
PackerFileUtils.mkdirs(tmp);
if (jdkFile.isDirectory()) {
PackerFileUtils.copyDirectory(jdkFile, tmp);
} else {
ZipUtil.unpack(jdkFile, tmp);
}
final File jre;
if (config.jdk_version >= 11) {
jre = new File(tmp, "jre");
if (jre.exists()) {
PackerFileUtils.deleteDirectory(tmp);
}
PackerFileUtils.mkdirs(jre);
this.createLibAndBin(tmp, jre);
} else {
jre = this.searchJre(tmp);
}
if (jre == null) {
throw new IOException("Couldn't find JRE in JDK, see '" + tmp.getAbsolutePath() + "'");
}
PackerFileUtils.copyDirectory(jre, new File(jreStoragePath, "jre"));
PackerFileUtils.deleteDirectory(tmp);
if (fetchFromRemote) {
PackerFileUtils.delete(jdkFile);
}
PackerReduce.minimizeJre(jreStoragePath, config);
}
if (extractToCache) {
PackerFileUtils.copyDirectory(jreStoragePath, output.resourcesFolder);
}
}
private void createLibAndBin(final File tmp, final File jre) {
final File[] childs = tmp.listFiles();
if (childs != null) {
for (final File child : childs) {
if (child.isDirectory()) {
if (child.getName().equals("bin") || child.getName().equals("lib")) {
try {
Files.move(child.toPath(), jre.toPath().resolve(child.getName()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
private File searchJre(final File tmp) {
if (tmp.getName().equals("jre") && tmp.isDirectory() && (new File(tmp, "bin/java").exists() || new File(tmp, "bin/java.exe").exists())) {
return tmp;
}
final File[] childs = tmp.listFiles();
if (childs != null) {
for (final File child : childs) {
if (child.isDirectory()) {
final File found = this.searchJre(child);
if (found != null) {
return found;
}
}
}
}
return null;
}
private void copyResources(final PackerOutput output) throws IOException {
if (this.config.resources != null) {
System.out.println("Copying resources ...");
for (final File file : this.config.resources) {
if (!file.exists()) {
throw new IOException("Resource '" + file.getAbsolutePath() + "' doesn't exist");
}
if (file.isFile()) {
PackerFileUtils.copyFile(file, new File(output.resourcesFolder, file.getName()));
}
if (!file.isDirectory()) {
continue;
}
final File target = new File(output.resourcesFolder, file.getName());
PackerFileUtils.mkdirs(target);
PackerFileUtils.copyDirectory(file, target);
}
}
}
private byte[] readResource(final String resource) throws IOException {
return IOUtils.toByteArray(Packer.class.getResourceAsStream(resource));
}
private String readResourceAsString(final String resource, final Map values) throws IOException {
final String txt = IOUtils.toString(Packer.class.getResourceAsStream(resource), "UTF-8");
return this.replace(txt, values);
}
private String replace(String txt, final Map values) {
for (final String key : values.keySet()) {
final String value = values.get(key);
txt = txt.replace(key, value);
}
return txt;
}
public static void main(final String[] args) {
try {
PackerCommandLine commandLine = CliFactory.parseArguments(PackerCommandLine.class, (args.length > 0) ? args : new String[] { "-h" });
if (commandLine.help()) {
return;
}
new Packer().pack(new PackerConfig(commandLine));
}
catch (ArgumentValidationException e) {
for (ValidationFailure failure : e.getValidationFailures()) {
System.err.println(failure.getMessage());
}
System.exit(-1);
}
catch (IOException e2) {
e2.printStackTrace();
System.exit(-1);
}
}
}