All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy