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

org.teavm.cli.TeaVMRunner Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2014 Alexey Andreev.
 *
 *  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 org.teavm.cli;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.teavm.backend.javascript.JSModuleType;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
import org.teavm.tooling.ConsoleTeaVMToolLog;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMTargetType;
import org.teavm.tooling.TeaVMTool;
import org.teavm.tooling.TeaVMToolException;
import org.teavm.tooling.util.FileSystemWatcher;
import org.teavm.vm.TeaVMOptimizationLevel;
import org.teavm.vm.TeaVMPhase;
import org.teavm.vm.TeaVMProgressFeedback;
import org.teavm.vm.TeaVMProgressListener;

public final class TeaVMRunner {
    private static Options options = new Options();
    private TeaVMTool tool = new TeaVMTool();
    private AccumulatingTeaVMToolLog log = new AccumulatingTeaVMToolLog(new ConsoleTeaVMToolLog(false));
    private CommandLine commandLine;
    private long startTime;
    private long phaseStartTime;
    private String[] classPath;
    private boolean interactive;

    static {
        setupOptions();
    }

    private static void setupOptions() {
        options.addOption(Option.builder("t")
                .argName("target")
                .hasArg()
                .desc("target type (javascript/js, webassembly/wasm, webassembly-wasi/wasm-wasi/wasi, C)")
                .build());
        options.addOption(Option.builder("d")
                .argName("directory")
                .hasArg()
                .desc("a directory where to put generated files (current directory by default)")
                .longOpt("targetdir")
                .build());
        options.addOption(Option.builder("f")
                .argName("file")
                .hasArg()
                .desc("a file where to put decompiled classes (classes.js by default)")
                .longOpt("targetfile")
                .build());
        options.addOption(Option.builder("m")
                .desc("causes TeaVM to generate minimized JavaScript file")
                .longOpt("minify")
                .build());
        options.addOption(Option.builder()
                .longOpt("strict")
                .desc("causes TeaVM to produce code that is as close to Java semantics as possible "
                        + "(in cost of performance)")
                .build());
        options.addOption(Option.builder("O")
                .desc("optimization level (1-3)")
                .hasArg()
                .argName("number")
                .build());
        options.addOption(Option.builder("g")
                .desc("Generate debug information")
                .longOpt("debug")
                .build());
        options.addOption(Option.builder("G")
                .desc("Generate source maps")
                .longOpt("sourcemaps")
                .build());
        options.addOption(Option.builder("i")
                .desc("Incremental build")
                .longOpt("incremental")
                .build());
        options.addOption(Option.builder("c")
                .argName("directory")
                .hasArg()
                .desc("Incremental build cache directory")
                .longOpt("cachedir")
                .build());
        options.addOption(Option.builder("w")
                .desc("Wait for command after compilation, in order to enable hot recompilation")
                .longOpt("wait")
                .build());
        options.addOption(Option.builder("p")
                .argName("classpath")
                .hasArgs()
                .desc("Additional classpath that will be reloaded by TeaVM each time in wait mode")
                .longOpt("classpath")
                .build());
        options.addOption(Option.builder()
                .argName("class name")
                .hasArgs()
                .desc("Tell optimizer to not remove class, so that it can be found by Class.forName")
                .longOpt("preserve-class")
                .build());
        options.addOption(Option.builder()
                .longOpt("wasm-version")
                .argName("version")
                .hasArg()
                .desc("WebAssembly binary version (currently, only 1 is supported)")
                .build());
        options.addOption(Option.builder("e")
                .longOpt("entry-point")
                .argName("name")
                .hasArg()
                .desc("Entry point name in target language (main by default)")
                .build());
        options.addOption(Option.builder()
                .longOpt("min-heap")
                .argName("size")
                .hasArg()
                .desc("Minimum heap size in megabytes (for C and WebAssembly)")
                .build());
        options.addOption(Option.builder()
                .longOpt("max-heap")
                .argName("size")
                .hasArg()
                .desc("Maximum heap size in megabytes (for C and WebAssembly)")
                .build());
        options.addOption(Option.builder()
                .longOpt("js-module-type")
                .argName("module-type")
                .hasArg()
                .desc("JavaScript module type (umd, common-js, none, es2015).")
                .build());
    }

    private TeaVMRunner(CommandLine commandLine) {
        this.commandLine = commandLine;
    }

    public static void main(String[] args) {
        if (args.length == 0) {
            printUsage();
            return;
        }
        CommandLineParser parser = new DefaultParser();
        CommandLine commandLine;
        try {
            commandLine = parser.parse(options, args);
        } catch (ParseException e) {
            printUsage();
            return;
        }

        TeaVMRunner runner = new TeaVMRunner(commandLine);
        runner.parseArguments();
        runner.setUp();
        runner.runAll();
    }

    private void parseArguments() {
        parseClassPathOptions();
        parseTargetOption();
        parseOutputOptions();
        parseDebugOptions();
        parsePreserveClassOptions();
        parseOptimizationOption();
        parseIncrementalOptions();
        parseGenerationOptions();
        parseWasmOptions();
        parseCOptions();
        parseHeap();

        if (commandLine.hasOption("e")) {
            tool.setEntryPointName(commandLine.getOptionValue("e"));
        }

        interactive = commandLine.hasOption('w');

        String[] args = commandLine.getArgs();
        if (args.length > 1) {
            System.err.println("Unexpected arguments");
            printUsage();
        } else if (args.length == 1) {
            tool.setMainClass(args[0]);
        }
    }

    private void parseTargetOption() {
        if (commandLine.hasOption("t")) {
            switch (commandLine.getOptionValue('t').toLowerCase()) {
                case "javascript":
                case "js":
                    tool.setTargetType(TeaVMTargetType.JAVASCRIPT);
                    break;
                case "webassembly":
                case "wasm":
                    tool.setTargetType(TeaVMTargetType.WEBASSEMBLY);
                    break;
                case "webassembly-wasi":
                case "wasm-wasi":
                case "wasi":
                    tool.setTargetType(TeaVMTargetType.WEBASSEMBLY_WASI);
                    break;
                case "c":
                    tool.setTargetType(TeaVMTargetType.C);
                    break;
            }
        }
    }

    private void parseOutputOptions() {
        if (commandLine.hasOption("d")) {
            tool.setTargetDirectory(new File(commandLine.getOptionValue("d")));
        }
        if (commandLine.hasOption("f")) {
            tool.setTargetFileName(commandLine.getOptionValue("f"));
        }
    }

    private void parseGenerationOptions() {
        tool.setObfuscated(commandLine.hasOption("m"));
        tool.setStrict(commandLine.hasOption("strict"));
        parseJsModuleOption();

        if (commandLine.hasOption("max-toplevel-names")) {
            try {
                tool.setMaxTopLevelNames(Integer.parseInt(commandLine.getOptionValue("max-toplevel-names")));
            } catch (NumberFormatException e) {
                System.err.println("'--max-toplevel-names' must be integer number");
                printUsage();
            }
        }
    }

    private void parseJsModuleOption() {
        if (!commandLine.hasOption("js-module-type")) {
            return;
        }
        switch (commandLine.getOptionValue("js-module-type")) {
            case "umd":
                tool.setJsModuleType(JSModuleType.UMD);
                break;
            case "common-js":
                tool.setJsModuleType(JSModuleType.COMMON_JS);
                break;
            case "none":
                tool.setJsModuleType(JSModuleType.NONE);
                break;
            case "es2015":
                tool.setJsModuleType(JSModuleType.ES2015);
                break;
            default:
                System.err.print("Wrong JS module type level");
                printUsage();
        }
    }

    private void parseDebugOptions() {
        if (commandLine.hasOption('g')) {
            tool.setDebugInformationGenerated(true);
        }
        if (commandLine.hasOption('G')) {
            tool.setSourceMapsFileGenerated(true);
        }
    }

    private void parsePreserveClassOptions() {
        if (commandLine.hasOption("preserve-class")) {
            tool.getClassesToPreserve().addAll(Arrays.asList(commandLine.getOptionValues("preserve-class")));
        }
    }

    private void parseOptimizationOption() {
        if (commandLine.hasOption("O")) {
            int level;
            try {
                level = Integer.parseInt(commandLine.getOptionValue("O"));
            } catch (NumberFormatException e) {
                System.err.print("Wrong optimization level");
                printUsage();
                return;
            }
            switch (level) {
                case 1:
                    tool.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE);
                    break;
                case 2:
                    tool.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
                    break;
                case 3:
                    tool.setOptimizationLevel(TeaVMOptimizationLevel.FULL);
                    break;
                default:
                    System.err.print("Wrong optimization level");
                    printUsage();
            }
        }
    }

    private void parseIncrementalOptions() {
        if (commandLine.hasOption('i')) {
            tool.setIncremental(true);
        }
        if (commandLine.hasOption('c')) {
            tool.setCacheDirectory(new File(commandLine.getOptionValue('c')));
        } else {
            tool.setCacheDirectory(new File(tool.getTargetDirectory(), "teavm-cache"));
        }
    }

    private void parseClassPathOptions() {
        if (commandLine.hasOption('p')) {
            classPath = commandLine.getOptionValues('p');
        }
    }

    private void parseWasmOptions() {
        if (commandLine.hasOption("wasm-version")) {
            String value = commandLine.getOptionValue("wasm-version");
            try {
                int version = Integer.parseInt(value);
                switch (version) {
                    case 1:
                        tool.setWasmVersion(WasmBinaryVersion.V_0x1);
                        break;
                    default:
                        System.err.print("Wrong version value");
                        printUsage();
                }
            } catch (NumberFormatException e) {
                System.err.print("Wrong version value");
                printUsage();
            }
        }
    }

    private void parseCOptions() {
        if (commandLine.hasOption("heap-dump")) {
            tool.setHeapDump(true);
        }
    }

    private void parseHeap() {
        if (commandLine.hasOption("min-heap")) {
            int size;
            try {
                size = Integer.parseInt(commandLine.getOptionValue("min-heap"));
            } catch (NumberFormatException e) {
                System.err.print("Wrong heap size");
                printUsage();
                return;
            }
            tool.setMinHeapSize(size * 1024 * 1024);
        }
        if (commandLine.hasOption("max-heap")) {
            int size;
            try {
                size = Integer.parseInt(commandLine.getOptionValue("max-heap"));
            } catch (NumberFormatException e) {
                System.err.print("Wrong heap size");
                printUsage();
                return;
            }
            tool.setMaxHeapSize(size * 1024 * 1024);
        }
    }

    private void setUp() {
        tool.setLog(log);
        tool.getProperties().putAll(System.getProperties());
    }

    private void runAll() {
        if (interactive) {
            buildInteractive();
        } else {
            buildNonInteractive();
        }
    }

    private void buildInteractive() {
        FileSystemWatcher watcher;
        try {
            watcher = new FileSystemWatcher(classPath);
        } catch (IOException e) {
            System.err.println("Error listening file system events");
            e.printStackTrace();
            System.exit(2);
            return;
        }

        while (true) {
            ProgressListenerImpl progressListener = new ProgressListenerImpl(watcher);
            try {
                build(progressListener);
            } catch (Exception e) {
                e.printStackTrace(System.err);
            }

            try {
                System.out.println("Waiting for changes...");
                watcher.waitForChange(750);
                watcher.grabChangedFiles();
                System.out.println();
                System.out.println("Changes detected. Recompiling...");
            } catch (InterruptedException | IOException e) {
                break;
            }
        }
    }

    private void buildNonInteractive() {
        try {
            build(new ProgressListenerImpl(null));
        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.exit(-2);
        }
        if (!tool.getProblemProvider().getSevereProblems().isEmpty()) {
            System.exit(-2);
        }
    }

    private void build(ProgressListenerImpl progressListener) throws TeaVMToolException {
        tool.setProgressListener(progressListener);
        resetClassLoader();
        startTime = System.currentTimeMillis();
        phaseStartTime = System.currentTimeMillis();
        tool.generate();
        reportPhaseComplete();
        TeaVMProblemRenderer.describeProblems(tool.getDependencyInfo().getCallGraph(), tool.getProblemProvider(), log);
        log.flush();
        System.out.println("Build complete for " + ((System.currentTimeMillis() - startTime) / 1000.0) + " seconds");
    }

    private void resetClassLoader() {
        if (classPath == null || classPath.length == 0) {
            return;
        }
        URL[] urls = new URL[classPath.length];
        for (int i = 0; i < classPath.length; ++i) {
            try {
                urls[i] = new File(classPath[i]).toURI().toURL();
            } catch (MalformedURLException e) {
                System.err.println("Illegal classpath entry: " + classPath[i]);
                System.exit(-1);
                return;
            }
        }

        tool.setClassLoader(new URLClassLoader(urls, TeaVMRunner.class.getClassLoader()));
    }

    class ProgressListenerImpl implements TeaVMProgressListener {
        private TeaVMPhase currentPhase;
        private FileSystemWatcher fileSystemWatcher;

        ProgressListenerImpl(FileSystemWatcher fileSystemWatcher) {
            this.fileSystemWatcher = fileSystemWatcher;
        }

        @Override
        public TeaVMProgressFeedback progressReached(int progress) {
            return getStatus();
        }

        @Override
        public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
            log.flush();
            if (currentPhase != phase) {
                if (currentPhase != null) {
                    reportPhaseComplete();
                }
                phaseStartTime = System.currentTimeMillis();
                switch (phase) {
                    case DEPENDENCY_ANALYSIS:
                        System.out.print("Analyzing classes...");
                        break;
                    case COMPILING:
                        System.out.print("Compiling...");
                        break;
                }
                currentPhase = phase;
            }
            return getStatus();
        }

        private TeaVMProgressFeedback getStatus() {
            try {
                if (fileSystemWatcher != null && fileSystemWatcher.hasChanges()) {
                    System.out.println("Classes changed during compilation. Canceling.");
                    return TeaVMProgressFeedback.CANCEL;
                }
                return TeaVMProgressFeedback.CONTINUE;
            } catch (IOException e) {
                throw new RuntimeException("IO error occurred");
            }
        }
    }

    private void reportPhaseComplete() {
        System.out.println(" complete for " + ((System.currentTimeMillis() - phaseStartTime) / 1000.0) + " seconds");
        log.flush();
    }

    private static void printUsage() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("java " + TeaVMRunner.class.getName() + " [OPTIONS] [qualified.main.Class]", options);
        System.exit(-1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy