Java tutorial
/******************************************************************************* * 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 java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.TrueFileFilter; import org.zeroturnaround.zip.ZipUtil; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; /** * 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 { public static enum Platform { windows, linux32, linux64, mac } public static class Config { public Platform platform; public String jdk; public String executable; public String jar; public String mainClass; public List<String> vmArgs = new ArrayList<String>(); public String[] minimizeJre; public List<String> resources = new ArrayList<String>(); public String outDir; } public void pack(Config config) throws IOException { // create output dir File out = new File(config.outDir); File target = out; if (out.exists()) { if (new File(".").equals(out)) { System.out.println("Output directory equals working directory, aborting"); System.exit(-1); } System.out.println("Output directory '" + out.getAbsolutePath() + "' exists, deleting"); FileUtils.deleteDirectory(out); } out.mkdirs(); Map<String, String> values = new HashMap<String, String>(); values.put("${executable}", config.executable); values.put("${bundleIdentifier}", "com.yourcompany.identifier"); // FIXME add as a param // if this is a mac build, let's create the app bundle structure if (config.platform == Platform.mac) { new File(out, "Contents").mkdirs(); FileUtils.writeStringToFile(new File(out, "Contents/Info.plist"), readResourceAsString("/Info.plist", values)); target = new File(out, "Contents/MacOS"); target.mkdirs(); new File(out, "Contents/Resources").mkdirs(); // FIXME copy icons } // write jar, exe and config to target folder byte[] exe = null; String extension = ""; switch (config.platform) { case windows: exe = readResource("/packr-windows.exe"); extension = ".exe"; break; case linux32: exe = readResource("/packr-linux"); break; case linux64: exe = readResource("/packr-linux-x64"); break; case mac: exe = readResource("/packr-mac"); break; } FileUtils.writeByteArrayToFile(new File(target, config.executable + extension), exe); new File(target, config.executable + extension).setExecutable(true); FileUtils.copyFile(new File(config.jar), new File(target, new File(config.jar).getName())); writeConfig(config, new File(target, "config.json")); // add JRE from local or remote zip file File jdkFile = null; if (config.jdk.startsWith("http://") || config.jdk.startsWith("https://")) { System.out.println("Downloading JDK from '" + config.jdk + "'"); jdkFile = new File(target, "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); } File tmp = new File(target, "tmp"); tmp.mkdirs(); System.out.println("Unpacking JRE"); ZipUtil.unpack(jdkFile, tmp); File jre = searchJre(tmp); if (jre == null) { System.out.println("Couldn't find JRE in JDK, see '" + tmp.getAbsolutePath() + "'"); System.exit(-1); } FileUtils.copyDirectory(jre, new File(target, "jre")); FileUtils.deleteDirectory(tmp); if (config.jdk.startsWith("http://") || config.jdk.startsWith("https://")) { jdkFile.delete(); } // copy resources System.out.println("copying resources"); copyResources(target, config.resources); // perform tree shaking if (config.minimizeJre != null) { minimizeJre(config, target); } System.out.println("Done!"); } private void writeConfig(Config config, File file) throws IOException { StringBuilder builder = new StringBuilder(); builder.append("{\n"); builder.append(" \"jar\": \"" + new File(config.jar).getName() + "\",\n"); builder.append(" \"mainClass\": \"" + config.mainClass + "\",\n"); builder.append(" \"vmArgs\": [\n"); for (int i = 0; i < config.vmArgs.size(); i++) { String vmArg = config.vmArgs.get(i); builder.append(" \"" + vmArg + "\""); if (i < config.vmArgs.size() - 1) { builder.append(","); } builder.append("\n"); } builder.append(" ]"); builder.append("}"); FileUtils.writeStringToFile(file, builder.toString()); } private void minimizeJre(Config config, File outDir) throws IOException { // remove stuff from the JRE System.out.println("minimizing JRE"); System.out.println("unpacking rt.jar"); ZipUtil.unpack(new File(outDir, "jre/lib/rt.jar"), new File(outDir, "jre/lib/rt")); if (config.platform == Platform.windows) { FileUtils.deleteDirectory(new File(outDir, "jre/bin/client")); for (File file : new File(outDir, "jre/bin").listFiles()) { if (file.getName().endsWith(".exe")) file.delete(); } } else { FileUtils.deleteDirectory(new File(outDir, "jre/bin")); } for (String minimizedDir : config.minimizeJre) { File file = new File(outDir, minimizedDir); if (file.isDirectory()) FileUtils.deleteDirectory(new File(outDir, minimizedDir)); else file.delete(); } new File(outDir, "jre/lib/rhino.jar").delete(); System.out.println("packing rt.jar"); new File(outDir, "jre/lib/rt.jar").delete(); ZipUtil.pack(new File(outDir, "jre/lib/rt"), new File(outDir, "jre/lib/rt.jar")); FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt")); // let's remove any shared libs not used on the platform, e.g. libgdx/lwjgl natives File jar = new File(outDir, new File(config.jar).getName()); File jarDir = new File(outDir, jar.getName() + ".tmp"); ZipUtil.unpack(jar, jarDir); Set<String> extensions = new HashSet<String>(); if (config.platform != Platform.linux32 && config.platform != Platform.linux64) { extensions.add(".so"); } if (config.platform != Platform.windows) { extensions.add(".dll"); } if (config.platform != Platform.mac) { extensions.add(".dylib"); } for (Object obj : FileUtils.listFiles(jarDir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)) { File file = new File(obj.toString()); for (String extension : extensions) { if (file.getName().endsWith(extension)) file.delete(); } } jar.delete(); ZipUtil.pack(jarDir, jar); FileUtils.deleteDirectory(jarDir); } private void copyResources(File targetDir, List<String> resources) throws IOException { for (String resource : resources) { File file = new File(resource); if (!file.exists()) { System.out.println("resource '" + file.getAbsolutePath() + "' doesn't exist"); System.exit(-1); } if (file.isFile()) { FileUtils.copyFile(file, new File(targetDir, file.getName())); } if (file.isDirectory()) { File target = new File(targetDir, file.getName()); target.mkdirs(); FileUtils.copyDirectory(file, target); } } } 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; } else { for (File child : tmp.listFiles()) { if (child.isDirectory()) { File found = searchJre(child); if (found != null) return found; } } return null; } } 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) throws IOException { if (args.length > 1) { Map<String, String> arguments = parseArgs(args); Config config = new Config(); config.platform = Platform.valueOf(arguments.get("platform")); config.jdk = arguments.get("jdk"); config.executable = arguments.get("executable"); config.jar = arguments.get("appjar"); config.mainClass = arguments.get("mainclass"); if (arguments.get("vmargs") != null) { config.vmArgs = Arrays.asList(arguments.get("vmargs").split(";")); } config.outDir = arguments.get("outdir"); if (arguments.get("minimizejre") != null) { if (new File(arguments.get("minimizejre")).exists()) { config.minimizeJre = FileUtils.readFileToString(new File(arguments.get("minimizejre"))) .split("\r?\n"); } else { InputStream in = Packr.class.getResourceAsStream("/minimize/" + arguments.get("minimizejre")); if (in != null) { config.minimizeJre = IOUtils.toString(in).split("\r?\n"); in.close(); } else { config.minimizeJre = new String[0]; } } } if (arguments.get("resources") != null) config.resources = Arrays.asList(arguments.get("resources").split(";")); new Packr().pack(config); } else { if (args.length == 0) { printHelp(); } else { JsonObject json = JsonObject.readFrom(FileUtils.readFileToString(new File(args[0]))); Config config = new Config(); config.platform = Platform.valueOf(json.get("platform").asString()); config.jdk = json.get("jdk").asString(); config.executable = json.get("executable").asString(); config.jar = json.get("appjar").asString(); config.mainClass = json.get("mainclass").asString(); if (json.get("vmargs") != null) { for (JsonValue val : json.get("vmargs").asArray()) { config.vmArgs.add(val.asString()); } } config.outDir = json.get("outdir").asString(); if (json.get("minimizejre") != null) { if (new File(json.get("minimizejre").asString()).exists()) { config.minimizeJre = FileUtils .readFileToString(new File(json.get("minimizejre").asString())).split("\r?\n"); } else { InputStream in = Packr.class.getResourceAsStream("/minimize/" + json.get("minimizejre")); if (in != null) { config.minimizeJre = IOUtils.toString(in).split("\r?\n"); in.close(); } else { config.minimizeJre = new String[0]; } } } if (json.get("resources") != null) { config.resources = toStringArray(json.get("resources").asArray()); } new Packr().pack(config); } } } private static List<String> toStringArray(JsonArray array) { List<String> result = new ArrayList<String>(); for (JsonValue value : array) { result.add(value.asString()); } return result; } private static void error() { printHelp(); System.exit(-1); } private static void printHelp() { System.out.println("Usage: packr <args>"); System.out.println("-platform <windows|linux|mac> ... operating system to pack for"); System.out.println( "-jdk <path-or-url> ... path to a JDK to be bundled (needs to fit platform)."); System.out.println(" Can be a ZIP file or URL to a ZIP file"); System.out.println( "-executable <name> ... name of the executable, e.g. 'mygame', without extension"); System.out.println( "-appjar <file> ... JAR file containing code and assets to be packed"); System.out.println( "-mainclass <main-class> ... fully qualified main class name, e.g. com/badlogic/MyApp"); System.out.println( "-vmargs <args> ... arguments passed to the JVM, e.g. -Xmx1G, separated by ;"); System.out.println( "-minimizejre <configfile> ... minimize the JRE by removing folders and files specified in the config file"); System.out.println( " three config files come with packr: 'soft' and 'hard' which may or may not break your app"); System.out.println( "-resources <files-and-folders> ... additional files and folders to be packed next to the"); System.out.println(" executable. Entries are separated by a ;"); System.out.println("-outdir <dir> ... output directory"); } private static Map<String, String> parseArgs(String[] args) { if (args.length < 12) { error(); } Map<String, String> params = new HashMap<String, String>(); for (int i = 0; i < args.length; i += 2) { String param = args[i].replace("-", ""); String value = args[i + 1]; params.put(param, value); } if (params.get("platform") == null) error(); if (params.get("jdk") == null) error(); if (params.get("executable") == null) error(); if (params.get("appjar") == null) error(); if (params.get("mainclass") == null) error(); if (params.get("outdir") == null) error(); return params; } }