/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogicgames.packr;
import com.lexicalscope.jewel.cli.ArgumentValidationException;
import com.lexicalscope.jewel.cli.CliFactory;
import com.lexicalscope.jewel.cli.ValidationFailure;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.zeroturnaround.zip.ZipUtil;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* Takes a couple of parameters and a JRE and bundles them into a platform specific
* distributable (zip on Windows and Linux, app bundle on Mac OS X).
* @author badlogic
*
*/
public class Packr {
private PackrConfig config;
public void pack(PackrConfig config) throws IOException {
config.validate();
this.config = config;
PackrOutput output = new PackrOutput(config.outDir, config.outDir);
cleanOrCreateOutputFolder(output);
output = buildMacBundle(output);
copyExecutableAndClasspath(output);
writeConfig(output);
copyJRE(output);
copyResources(output);
PackrReduce.minimizeJre(output, config);
PackrReduce.removePlatformLibs(output, config);
System.out.println("Done!");
}
private void cleanOrCreateOutputFolder(PackrOutput output) throws IOException {
File folder = output.executableFolder;
if (folder.exists()) {
System.out.println("Cleaning output directory '" + folder.getAbsolutePath() + "' ...");
FileUtils.deleteDirectory(folder);
} else {
PackrFileUtils.mkdirs(folder);
}
}
private PackrOutput buildMacBundle(PackrOutput output) throws IOException {
if (config.platform != PackrConfig.Platform.MacOS) {
return output;
}
// replacement strings for Info.plist
Map<String, String> values = new HashMap<String, String>();
values.put("${executable}", config.executable);
if (config.bundleIdentifier != null) {
values.put("${bundleIdentifier}", config.bundleIdentifier);
} else {
values.put("${bundleIdentifier}", config.mainClass.substring(0, config.mainClass.lastIndexOf('.')));
}
// create folder structure
File root = output.executableFolder;
PackrFileUtils.mkdirs(new File(root, "Contents"));
FileUtils.writeStringToFile(new File(root, "Contents/Info.plist"), readResourceAsString("/Info.plist", values));
File target = new File(root, "Contents/MacOS");
PackrFileUtils.mkdirs(target);
File resources = new File(root, "Contents/Resources");
PackrFileUtils.mkdirs(resources);
if (config.iconResource != null) {
// copy icon to Contents/Resources/icons.icns
if (config.iconResource.exists()) {
FileUtils.copyFile(config.iconResource, new File(resources, "icons.icns"));
}
}
return new PackrOutput(target, resources);
}
private void copyExecutableAndClasspath(PackrOutput output) throws IOException {
byte[] exe = null;
String extension = "";
switch (config.platform) {
case Windows32:
exe = readResource("/packr-windows.exe");
extension = ".exe";
break;
case Windows64:
exe = readResource("/packr-windows-x64.exe");
extension = ".exe";
break;
case Linux32:
exe = readResource("/packr-linux");
break;
case Linux64:
exe = readResource("/packr-linux-x64");
break;
case MacOS:
exe = readResource("/packr-mac");
break;
}
System.out.println("Copying executable ...");
FileUtils.writeByteArrayToFile(new File(output.executableFolder, config.executable + extension), exe);
PackrFileUtils.chmodX(new File(output.executableFolder, config.executable + extension));
System.out.println("Copying classpath(s) ...");
for (String file : config.classpath) {
File cpSrc = new File(file);
File cpDst = new File(output.resourcesFolder, new File(file).getName());
if (cpSrc.isFile()) {
FileUtils.copyFile(cpSrc, cpDst);
} else if (cpSrc.isDirectory()) {
FileUtils.copyDirectory(cpSrc, cpDst);
} else {
System.err.println("Warning! Classpath not found: " + cpSrc);
}
}
}
private void writeConfig(PackrOutput output) throws IOException {
StringBuilder builder = new StringBuilder();
builder.append("{\n");
builder.append(" \"classPath\": [");
String delim = "\n";
for (String f : config.classpath) {
builder.append(delim).append(" \"").append(new File(f).getName()).append("\"");
delim = ",\n";
}
builder.append("\n ],\n");
builder.append(" \"mainClass\": \"").append(config.mainClass).append("\",\n");
builder.append(" \"vmArgs\": [\n");
for (int i = 0; i < config.vmArgs.size(); i++) {
String vmArg = config.vmArgs.get(i);
builder.append(" \"");
if (!vmArg.startsWith("-")) {
builder.append("-");
}
builder.append(vmArg).append("\"");
if (i < config.vmArgs.size() - 1) {
builder.append(",");
}
builder.append("\n");
}
builder.append(" ]\n");
builder.append("}");
FileUtils.writeStringToFile(new File(output.resourcesFolder, "config.json"), builder.toString());
}
private void copyJRE(PackrOutput output) throws IOException {
File jdkFile;
boolean fetchFromRemote = config.jdk.startsWith("http://") || config.jdk.startsWith("https://");
// add JRE from local or remote zip file
if (fetchFromRemote) {
System.out.println("Downloading JDK from '" + config.jdk + "' ...");
jdkFile = new File(output.resourcesFolder, "jdk.zip");
InputStream in = new URL(config.jdk).openStream();
OutputStream outJdk = FileUtils.openOutputStream(jdkFile);
IOUtils.copy(in, outJdk);
in.close();
outJdk.close();
} else {
jdkFile = new File(config.jdk);
}
System.out.println("Unpacking JRE ...");
File tmp = new File(output.resourcesFolder, "tmp");
PackrFileUtils.mkdirs(tmp);
if (jdkFile.isDirectory()) {
FileUtils.copyDirectoryToDirectory(jdkFile, tmp);
} else {
ZipUtil.unpack(jdkFile, tmp);
}
File jre = searchJre(tmp);
if (jre == null) {
throw new IOException("Couldn't find JRE in JDK, see '" + tmp.getAbsolutePath() + "'");
}
FileUtils.copyDirectory(jre, new File(output.resourcesFolder, "jre"));
FileUtils.deleteDirectory(tmp);
if (fetchFromRemote) {
PackrFileUtils.delete(jdkFile);
}
}
private File searchJre(File tmp) {
if (tmp.getName().equals("jre") && tmp.isDirectory()
&& (new File(tmp, "bin/java").exists() || new File(tmp, "bin/java.exe").exists())) {
return tmp;
}
File[] childs = tmp.listFiles();
if (childs != null) {
for (File child : childs) {
if (child.isDirectory()) {
File found = searchJre(child);
if (found != null) {
return found;
}
}
}
}
return null;
}
private void copyResources(PackrOutput output) throws IOException {
if (config.resources != null) {
System.out.println("Copying resources ...");
for (File file : config.resources) {
if (!file.exists()) {
throw new IOException("Resource '" + file.getAbsolutePath() + "' doesn't exist");
}
if (file.isFile()) {
FileUtils.copyFile(file, new File(output.resourcesFolder, file.getName()));
}
if (file.isDirectory()) {
File target = new File(output.resourcesFolder, file.getName());
PackrFileUtils.mkdirs(target);
FileUtils.copyDirectory(file, target);
}
}
}
}
private byte[] readResource(String resource) throws IOException {
return IOUtils.toByteArray(Packr.class.getResourceAsStream(resource));
}
private String readResourceAsString(String resource, Map<String, String> values) throws IOException {
String txt = IOUtils.toString(Packr.class.getResourceAsStream(resource), "UTF-8");
return replace(txt, values);
}
private String replace(String txt, Map<String, String> values) {
for (String key : values.keySet()) {
String value = values.get(key);
txt = txt.replace(key, value);
}
return txt;
}
public static void main(String[] args) {
try {
PackrCommandLine commandLine = CliFactory.parseArguments(PackrCommandLine.class, args);
if (commandLine.help()) {
return;
}
new Packr().pack(new PackrConfig(commandLine));
} catch (ArgumentValidationException e) {
for (ValidationFailure failure : e.getValidationFailures()) {
System.err.println(failure.getMessage());
}
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
}