<commit_before>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.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
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,
		linux,
		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 boolean 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()) {
			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 linux:
				exe = readResource("/packr-linux");
				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) {	
			minimizeJre(config, new File(target, "jre"));			
		}
		
		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 jreDir) throws IOException {
		System.out.println("minimizing JRE");
		System.out.println("unpacking rt.jar");
		ZipUtil.unpack(new File(jreDir, "lib/rt.jar"), new File(jreDir, "lib/rt"));
		
		if(config.platform == Platform.windows) {
			FileUtils.deleteDirectory(new File(jreDir, "bin/client"));
			for(File file: new File(jreDir, "bin").listFiles()) {
				if(file.getName().endsWith(".exe")) file.delete();
			}
		} else {
			FileUtils.deleteDirectory(new File(jreDir, "bin"));
		}
		
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/corba"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/jmx"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/jndi"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/media"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/naming"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/org"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/rowset"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/script"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/com/sun/xml"));
		
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/sun/applet"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/sun/corba"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt/sun/management"));
		new File(jreDir, "lib/rhino.jar").delete();
		
		System.out.println("packing rt.jar");
		new File(jreDir, "lib/rt.jar").delete();
		ZipUtil.pack(new File(jreDir, "lib/rt"), new File(jreDir, "lib/rt.jar"));
		FileUtils.deleteDirectory(new File(jreDir, "lib/rt"));
	}

	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) {<commit_msg>removing shared libs per platform on minimize<commit_after>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,
		linux,
		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 boolean 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()) {
			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 linux:
				exe = readResource("/packr-linux");
				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) {	
			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"));
		}
		
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/corba"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/jmx"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/jndi"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/media"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/naming"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/org"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/rowset"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/script"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/com/sun/xml"));
		
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/sun/applet"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/sun/corba"));
		FileUtils.deleteDirectory(new File(outDir, "jre/lib/rt/sun/management"));
		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.linux) { extensions.add(".dylib"); extensions.add(".dll"); }
		if(config.platform == Platform.windows) { extensions.add(".dylib"); extensions.add(".so"); }
		if(config.platform == Platform.mac) { extensions.add(".so"); extensions.add(".dll"); }
		
		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) {
{'fraction_non_alphanumeric': 0.16372423977982145, 'fraction_numerical': 0.0005370208766865812, 'mean_word_length': 4.116071428571429, 'pattern_counts': {'":': 6, '<': 23, '<?xml version=': 0, '>': 19, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5929282', 'n_tokens_mistral': 5542, 'n_tokens_neox': 4924, 'n_words': 1042}