diff --git a/pom.xml b/pom.xml index d8bdf4a45..68e4aa6a2 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ teavm-html4j teavm-samples teavm-platform + teavm-cli diff --git a/teavm-cli/.gitignore b/teavm-cli/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-cli/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-cli/pom.xml b/teavm-cli/pom.xml new file mode 100644 index 000000000..cca0a2556 --- /dev/null +++ b/teavm-cli/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-cli + + TeaVM CLI + TeaVM command line tools + + + + org.teavm + teavm-core + ${project.version} + + + commons-cli + commons-cli + 1.2 + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java b/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java new file mode 100644 index 000000000..2105333ea --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java @@ -0,0 +1,68 @@ +/* + * 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 org.teavm.tooling.TeaVMToolLog; + +/** + * + * @author Alexey Andreev + */ +class ConsoleTeaVMToolLog implements TeaVMToolLog { + @Override + public void info(String text) { + System.out.println("INFO: " + text); + } + + @Override + public void debug(String text) { + System.out.println("DEBUG: " + text); + } + + @Override + public void warning(String text) { + System.out.println("WARNING: " + text); + } + + @Override + public void error(String text) { + System.out.println("ERROR: " + text); + } + + @Override + public void info(String text, Throwable e) { + System.out.println("INFO: " + text); + e.printStackTrace(System.out); + } + + @Override + public void debug(String text, Throwable e) { + System.out.println("DEBUG: " + text); + e.printStackTrace(System.out); + } + + @Override + public void warning(String text, Throwable e) { + System.out.println("WARNING: " + text); + e.printStackTrace(System.out); + } + + @Override + public void error(String text, Throwable e) { + System.out.println("ERROR: " + text); + e.printStackTrace(System.out); + } +} diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java new file mode 100644 index 000000000..be4ddbf0a --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -0,0 +1,146 @@ +/* + * 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 org.apache.commons.cli.*; +import org.teavm.tooling.RuntimeCopyOperation; +import org.teavm.tooling.TeaVMTool; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMRunner { + @SuppressWarnings("static-access") + public static void main(String[] args) { + Options options = new Options(); + options.addOption(OptionBuilder + .withArgName("directory") + .hasArg() + .withDescription("a directory where to put generated files (current directory by default)") + .withLongOpt("targetdir") + .create('d')); + options.addOption(OptionBuilder + .withArgName("file") + .hasArg() + .withDescription("a file where to put decompiled classes (classes.js by default)") + .withLongOpt("targetfile") + .create('f')); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to generate minimized JavaScript file") + .withLongOpt("minify") + .create("m")); + options.addOption(OptionBuilder + .withArgName("separate|merge|none") + .hasArg() + .withDescription("how to attach runtime. Possible values are: separate|merge|none") + .withLongOpt("runtime") + .create("r")); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to include default main page") + .withLongOpt("mainpage") + .create()); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to log bytecode") + .create("logbytecode")); + options.addOption(OptionBuilder + .withArgName("number") + .hasArg() + .withDescription("how many threads should TeaVM run") + .withLongOpt("threads") + .create("t")); + + if (args.length == 0) { + printUsage(options); + return; + } + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + printUsage(options); + return; + } + + TeaVMTool tool = new TeaVMTool(); + tool.setBytecodeLogging(commandLine.hasOption("logbytecode")); + if (commandLine.hasOption("d")) { + tool.setTargetDirectory(new File(commandLine.getOptionValue("d"))); + } + if (commandLine.hasOption("f")) { + tool.setTargetFileName(commandLine.getOptionValue("f")); + } + if (commandLine.hasOption("m")) { + tool.setMinifying(true); + } else { + tool.setMinifying(false); + } + if (commandLine.hasOption("r")) { + switch (commandLine.getOptionValue("r")) { + case "separate": + tool.setRuntime(RuntimeCopyOperation.SEPARATE); + break; + case "merge": + tool.setRuntime(RuntimeCopyOperation.MERGED); + break; + case "none": + tool.setRuntime(RuntimeCopyOperation.NONE); + break; + default: + System.err.println("Wrong parameter for -r option specified"); + printUsage(options); + return; + } + } + if (commandLine.hasOption("mainpage")) { + tool.setMainPageIncluded(true); + } + if (commandLine.hasOption("t")) { + try { + tool.setNumThreads(Integer.parseInt(commandLine.getOptionValue("t"))); + } catch (NumberFormatException e) { + System.err.println("Wrong parameter for -t option specified"); + printUsage(options); + return; + } + } + args = commandLine.getArgs(); + if (args.length > 1) { + System.err.println("Unexpected arguments"); + printUsage(options); + return; + } else if (args.length == 1) { + tool.setMainClass(args[0]); + } + tool.setLog(new ConsoleTeaVMToolLog()); + tool.getProperties().putAll(System.getProperties()); + + try { + tool.generate(); + } catch (Exception e) { + e.printStackTrace(System.err); + System.exit(-2); + } + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java " + TeaVMRunner.class.getName() + " [OPTIONS] [qualified.main.Class]", options); + System.exit(-1); + } +} diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java new file mode 100644 index 000000000..988d18858 --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java @@ -0,0 +1,183 @@ +package org.teavm.cli; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import org.apache.commons.cli.*; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.testing.TestAdapter; +import org.teavm.tooling.TeaVMTestTool; +import org.teavm.tooling.TeaVMToolException; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTestRunner { + @SuppressWarnings("static-access") + public static void main(String[] args) { + Options options = new Options(); + options.addOption(OptionBuilder + .withArgName("directory") + .hasArg() + .withDescription("a directory where to put generated files (current directory by default)") + .withLongOpt("targetdir") + .create('d')); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to generate minimized JavaScript file") + .withLongOpt("minify") + .create("m")); + options.addOption(OptionBuilder + .withArgName("number") + .hasArg() + .withDescription("how many threads should TeaVM run") + .withLongOpt("threads") + .create("t")); + options.addOption(OptionBuilder + .withArgName("class name") + .hasArg() + .withDescription("qualified class name of test adapter") + .withLongOpt("adapter") + .create("a")); + options.addOption(OptionBuilder + .hasArg() + .hasOptionalArgs() + .withArgName("class name") + .withDescription("qualified class names of transformers") + .withLongOpt("transformers") + .create("T")); + + if (args.length == 0) { + printUsage(options); + return; + } + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + printUsage(options); + return; + } + + TeaVMTestTool tool = new TeaVMTestTool(); + tool.setOutputDir(new File(commandLine.getOptionValue("d", "."))); + tool.setMinifying(commandLine.hasOption("m")); + try { + tool.setNumThreads(Integer.parseInt(commandLine.getOptionValue("t", "1"))); + } catch (NumberFormatException e) { + System.err.println("Invalid number specified for -t option"); + printUsage(options); + return; + } + if (commandLine.hasOption("a")) { + tool.setAdapter(instantiateAdapter(commandLine.getOptionValue("a"))); + } + if (commandLine.hasOption("T")) { + for (String transformerType : commandLine.getOptionValues("T")) { + tool.getTransformers().add(instantiateTransformer(transformerType)); + } + } + args = commandLine.getArgs(); + if (args.length == 0) { + System.err.println("You did not specify any test classes"); + printUsage(options); + return; + } + tool.getTestClasses().addAll(Arrays.asList(args)); + + tool.setLog(new ConsoleTeaVMToolLog()); + tool.getProperties().putAll(System.getProperties()); + long start = System.currentTimeMillis(); + try { + tool.generate(); + } catch (TeaVMToolException e) { + e.printStackTrace(System.err); + System.exit(-2); + } + System.out.println("Operation took " + (System.currentTimeMillis() - start) + " milliseconds"); + } + + private static TestAdapter instantiateAdapter(String adapterName) { + Class adapterClass; + try { + adapterClass = Class.forName(adapterName, true, TeaVMTestRunner.class.getClassLoader()); + } catch (ClassNotFoundException e) { + System.err.println("Adapter not found: " + adapterName); + System.exit(-1); + return null; + } + if (!TestAdapter.class.isAssignableFrom(adapterClass)) { + System.err.println("Adapter class does not implement TestAdapter: " + adapterName); + System.exit(-1); + return null; + } + Constructor cons; + try { + cons = adapterClass.getConstructor(); + } catch (NoSuchMethodException e) { + System.err.println("Adapter class does not contain no-arg constructor: " + adapterName); + System.exit(-1); + return null; + } + + try { + return (TestAdapter)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + System.err.println("Error instantiating adapter: " + adapterName); + e.printStackTrace(System.err); + System.exit(-1); + return null; + } catch (InvocationTargetException e) { + System.err.println("Error instantiating adapter: " + adapterName); + e.getTargetException().printStackTrace(System.err); + System.exit(-1); + return null; + } + } + + private static ClassHolderTransformer instantiateTransformer(String transformerName) { + Class adapterClass; + try { + adapterClass = Class.forName(transformerName, true, TeaVMTestRunner.class.getClassLoader()); + } catch (ClassNotFoundException e) { + System.err.println("Transformer not found: " + transformerName); + System.exit(-1); + return null; + } + if (!ClassHolderTransformer.class.isAssignableFrom(adapterClass)) { + System.err.println("Transformer class does not implement ClassHolderTransformer: " + transformerName); + System.exit(-1); + return null; + } + Constructor cons; + try { + cons = adapterClass.getConstructor(); + } catch (NoSuchMethodException e) { + System.err.println("Transformer class does not contain no-arg constructor: " + transformerName); + System.exit(-1); + return null; + } + + try { + return (ClassHolderTransformer)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + System.err.println("Error instantiating transformer: " + transformerName); + e.printStackTrace(System.err); + System.exit(-1); + return null; + } catch (InvocationTargetException e) { + System.err.println("Error instantiating transformer: " + transformerName); + e.getTargetException().printStackTrace(System.err); + System.exit(-1); + return null; + } + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java " + TeaVMTestRunner.class.getName() + " [OPTIONS] test_name {test_name}", options); + System.exit(-1); + } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java b/teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java similarity index 97% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java rename to teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java index 29aefd6b0..f5fe8a5e9 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java +++ b/teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java new file mode 100644 index 000000000..917e73fb4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java @@ -0,0 +1,54 @@ +/* + * 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.tooling; + +/** + * + * @author Alexey Andreev + */ +public class EmptyTeaVMToolLog implements TeaVMToolLog { + @Override + public void info(String text) { + } + + @Override + public void debug(String text) { + } + + @Override + public void warning(String text) { + } + + @Override + public void error(String text) { + } + + @Override + public void info(String text, Throwable e) { + } + + @Override + public void debug(String text, Throwable e) { + } + + @Override + public void warning(String text, Throwable e) { + } + + @Override + public void error(String text, Throwable e) { + } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java b/teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java similarity index 98% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java rename to teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java index 1e477d35c..58a6bdadb 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java +++ b/teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java b/teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java similarity index 96% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java rename to teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java index 7e8f0f598..aa3b493d7 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java +++ b/teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java new file mode 100644 index 000000000..9d70eca42 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java @@ -0,0 +1,359 @@ +/* + * 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.tooling; + +import java.io.*; +import java.util.*; +import org.apache.commons.io.IOUtils; +import org.teavm.common.FiniteExecutor; +import org.teavm.common.SimpleFiniteExecutor; +import org.teavm.common.ThreadPoolFiniteExecutor; +import org.teavm.model.*; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.testing.JUnitTestAdapter; +import org.teavm.testing.TestAdapter; +import org.teavm.vm.DirectoryBuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTestTool { + private Map> groupedMethods = new HashMap<>(); + private Map fileNames = new HashMap<>(); + private List testMethods = new ArrayList<>(); + private File outputDir = new File("."); + private boolean minifying = true; + private int numThreads = 1; + private TestAdapter adapter = new JUnitTestAdapter(); + private List transformers = new ArrayList<>(); + private List additionalScripts = new ArrayList<>(); + private List additionalScriptLocalPaths = new ArrayList<>(); + private Properties properties = new Properties(); + private List testClasses = new ArrayList<>(); + private ClassLoader classLoader = TeaVMTestTool.class.getClassLoader(); + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + + public File getOutputDir() { + return outputDir; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + public boolean isMinifying() { + return minifying; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public int getNumThreads() { + return numThreads; + } + + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + + public TestAdapter getAdapter() { + return adapter; + } + + public void setAdapter(TestAdapter adapter) { + this.adapter = adapter; + } + + public List getTransformers() { + return transformers; + } + + public List getAdditionalScripts() { + return additionalScripts; + } + + public Properties getProperties() { + return properties; + } + + public List getTestClasses() { + return testClasses; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public TeaVMToolLog getLog() { + return log; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public void generate() throws TeaVMToolException { + Runnable finalizer = null; + try { + new File(outputDir, "tests").mkdirs(); + new File(outputDir, "res").mkdirs(); + resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); + String prefix = "org/teavm/tooling/test"; + resourceToFile(prefix + "/res/junit-support.js", "res/junit-support.js"); + resourceToFile(prefix + "/res/junit.css", "res/junit.css"); + resourceToFile(prefix + "/res/class_obj.png", "res/class_obj.png"); + resourceToFile(prefix + "/res/control-000-small.png", "res/control-000-small.png"); + resourceToFile(prefix + "/res/methpub_obj.png", "res/methpub_obj.png"); + resourceToFile(prefix + "/res/package_obj.png", "res/package_obj.png"); + resourceToFile(prefix + "/res/tick-small-red.png", "res/tick-small-red.png"); + resourceToFile(prefix + "/res/tick-small.png", "res/tick-small.png"); + resourceToFile(prefix + "/res/toggle-small-expand.png", "res/toggle-small-expand.png"); + resourceToFile(prefix + "/res/toggle-small.png", "res/toggle-small.png"); + resourceToFile(prefix + "/junit.html", "junit.html"); + final ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); + for (String testClass : testClasses) { + ClassHolder classHolder = classSource.get(testClass); + if (classHolder == null) { + throw new TeaVMToolException("Could not find class " + testClass); + } + findTests(classHolder); + } + + includeAdditionalScripts(classLoader); + File allTestsFile = new File(outputDir, "tests/all.js"); + try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { + allTestsWriter.write("prepare = function() {\n"); + allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); + boolean first = true; + for (String testClass : testClasses) { + Collection methods = groupedMethods.get(testClass); + if (methods == null) { + continue; + } + if (!first) { + allTestsWriter.append(","); + } + first = false; + allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); + boolean firstMethod = true; + for (MethodReference methodRef : methods) { + String scriptName = "tests/" + fileNames.size() + ".js"; + fileNames.put(methodRef, scriptName); + if (!firstMethod) { + allTestsWriter.append(","); + } + firstMethod = false; + allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + + scriptName + "\", expected : ["); + MethodHolder methodHolder = classSource.get(testClass).getMethod( + methodRef.getDescriptor()); + boolean firstException = true; + for (String exception : adapter.getExpectedExceptions(methodHolder)) { + if (!firstException) { + allTestsWriter.append(", "); + } + firstException = false; + allTestsWriter.append("\"" + exception + "\""); + } + allTestsWriter.append("], additionalScripts : ["); + for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { + if (i > 0) { + allTestsWriter.append(", "); + } + escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); + } + allTestsWriter.append("] }"); + } + allTestsWriter.append("] }"); + } + allTestsWriter.write("], function() {}); }"); + } + int methodsGenerated = 0; + log.info("Generating test files"); + FiniteExecutor executor = new SimpleFiniteExecutor(); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + threadedExecutor.stop(); + } + }; + executor = threadedExecutor; + } + for (final MethodReference method : testMethods) { + executor.execute(new Runnable() { + @Override public void run() { + log.debug("Building test for " + method); + try { + decompileClassesForTest(classLoader, new CopyClassHolderSource(classSource), method, + fileNames.get(method), new SimpleFiniteExecutor()); + } catch (IOException e) { + log.error("Error generating JavaScript", e); + } + } + }); + ++methodsGenerated; + } + executor.complete(); + log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); + } catch (IOException e) { + throw new TeaVMToolException("IO error occured generating JavaScript files", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } + } + } + + private void resourceToFile(String resource, String fileName) throws IOException { + try (InputStream input = TeaVMTestTool.class.getClassLoader().getResourceAsStream(resource)) { + try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { + IOUtils.copy(input, output); + } + } + } + + private void findTests(ClassHolder cls) { + for (MethodHolder method : cls.getMethods()) { + if (adapter.acceptMethod(method)) { + MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); + testMethods.add(ref); + List group = groupedMethods.get(cls.getName()); + if (group == null) { + group = new ArrayList<>(); + groupedMethods.put(cls.getName(), group); + } + group.add(ref); + } + } + } + + private void includeAdditionalScripts(ClassLoader classLoader) throws TeaVMToolException { + if (additionalScripts == null) { + return; + } + for (String script : additionalScripts) { + String simpleName = script.substring(script.lastIndexOf('/') + 1); + additionalScriptLocalPaths.add("tests/" + simpleName); + if (classLoader.getResource(script) == null) { + throw new TeaVMToolException("Additional script " + script + " was not found"); + } + File file = new File(outputDir, "tests/" + simpleName); + try (InputStream in = classLoader.getResourceAsStream(script)) { + if (!file.exists()) { + file.createNewFile(); + } + try(OutputStream out = new FileOutputStream(file)) { + IOUtils.copy(in, out); + } + } catch (IOException e) { + throw new TeaVMToolException("Error copying additional script " + script, e); + } + } + } + + private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, + MethodReference methodRef, String targetName, FiniteExecutor executor) throws IOException { + TeaVM vm = new TeaVMBuilder() + .setClassLoader(classLoader) + .setClassSource(classSource) + .setExecutor(executor) + .build(); + vm.setProperties(properties); + vm.setMinifying(minifying); + vm.installPlugins(); + new TestExceptionPlugin().install(vm); + for (ClassHolderTransformer transformer : transformers) { + vm.add(transformer); + } + File file = new File(outputDir, targetName); + try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { + MethodReference cons = new MethodReference(methodRef.getClassName(), + new MethodDescriptor("", ValueType.VOID)); + MethodReference exceptionMsg = new MethodReference("java.lang.Throwable", "getMessage", + ValueType.object("java.lang.String")); + vm.entryPoint("initInstance", cons); + vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); + vm.entryPoint("extractException", exceptionMsg); + vm.exportType("TestClass", cons.getClassName()); + vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); + if (!vm.hasMissingItems()) { + innerWriter.append("\n"); + innerWriter.append("\nJUnitClient.run();"); + innerWriter.close(); + } else { + innerWriter.append("JUnitClient.reportError(\n"); + StringBuilder sb = new StringBuilder(); + vm.showMissingItems(sb); + escapeStringLiteral(sb.toString(), innerWriter); + innerWriter.append(");"); + log.warning("Error building test " + methodRef); + log.warning(sb.toString()); + } + } + } + + private void escapeStringLiteral(String text, Writer writer) throws IOException { + int index = 0; + while (true) { + int next = text.indexOf('\n', index); + if (next < 0) { + break; + } + escapeString(text.substring(index, next + 1), writer); + writer.append(" +\n"); + index = next + 1; + } + escapeString(text.substring(index), writer); + } + + private void escapeString(String string, Writer writer) throws IOException { + writer.append('\"'); + for (int i = 0; i < string.length(); ++i) { + char c = string.charAt(i); + switch (c) { + case '"': + writer.append("\\\""); + break; + case '\\': + writer.append("\\\\"); + break; + case '\n': + writer.append("\\n"); + break; + case '\r': + writer.append("\\r"); + break; + case '\t': + writer.append("\\t"); + break; + default: + writer.append(c); + break; + } + } + writer.append('\"'); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java new file mode 100644 index 000000000..75a909243 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -0,0 +1,256 @@ +/* + * 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.tooling; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import org.apache.commons.io.IOUtils; +import org.teavm.common.ThreadPoolFiniteExecutor; +import org.teavm.javascript.RenderingContext; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.vm.*; +import org.teavm.vm.spi.AbstractRendererListener; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTool { + private File targetDirectory = new File("."); + private String targetFileName = "classes.js"; + private boolean minifying = true; + private String mainClass; + private RuntimeCopyOperation runtime = RuntimeCopyOperation.SEPARATE; + private Properties properties = new Properties(); + private boolean mainPageIncluded; + private boolean bytecodeLogging; + private int numThreads = 1; + private List transformers = new ArrayList<>(); + private List classAliases = new ArrayList<>(); + private List methodAliases = new ArrayList<>(); + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + private ClassLoader classLoader = TeaVMTool.class.getClassLoader(); + + public File getTargetDirectory() { + return targetDirectory; + } + + public void setTargetDirectory(File targetDirectory) { + this.targetDirectory = targetDirectory; + } + + public String getTargetFileName() { + return targetFileName; + } + + public void setTargetFileName(String targetFileName) { + this.targetFileName = targetFileName; + } + + public boolean isMinifying() { + return minifying; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public String getMainClass() { + return mainClass; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public RuntimeCopyOperation getRuntime() { + return runtime; + } + + public void setRuntime(RuntimeCopyOperation runtime) { + this.runtime = runtime; + } + + public boolean isMainPageIncluded() { + return mainPageIncluded; + } + + public void setMainPageIncluded(boolean mainPageIncluded) { + this.mainPageIncluded = mainPageIncluded; + } + + public boolean isBytecodeLogging() { + return bytecodeLogging; + } + + public void setBytecodeLogging(boolean bytecodeLogging) { + this.bytecodeLogging = bytecodeLogging; + } + + public int getNumThreads() { + return numThreads; + } + + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + + public Properties getProperties() { + return properties; + } + + public List getTransformers() { + return transformers; + } + + public List getClassAliases() { + return classAliases; + } + + public List getMethodAliases() { + return methodAliases; + } + + public TeaVMToolLog getLog() { + return log; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void generate() throws TeaVMToolException { + Runnable finalizer = null; + try { + log.info("Building JavaScript file"); + TeaVMBuilder vmBuilder = new TeaVMBuilder(); + vmBuilder.setClassLoader(classLoader).setClassSource(new ClasspathClassHolderSource(classLoader)); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + executor.stop(); + } + }; + vmBuilder.setExecutor(executor); + } + TeaVM vm = vmBuilder.build(); + vm.setMinifying(minifying); + vm.setBytecodeLogging(bytecodeLogging); + vm.setProperties(properties); + vm.installPlugins(); + for (ClassHolderTransformer transformer : transformers) { + vm.add(transformer); + } + if (mainClass != null) { + MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( + ValueType.object("java.lang.String")), ValueType.VOID); + vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) + .withValue(1, "java.lang.String"); + } + for (ClassAlias alias : classAliases) { + vm.exportType(alias.getAlias(), alias.getClassName()); + } + for (MethodAlias methodAlias : methodAliases) { + MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(), + MethodDescriptor.parseSignature(methodAlias.getDescriptor())); + TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref); + if (methodAlias.getTypes() != null) { + for (int i = 0; i < methodAlias.getTypes().length; ++i) { + String types = methodAlias.getTypes()[i]; + if (types != null) { + for (String type : types.split(" +")) { + type = type.trim(); + if (!type.isEmpty()) { + entryPoint.withValue(i, type); + } + } + } + } + } + } + targetDirectory.mkdirs(); + try (FileWriter writer = new FileWriter(new File(targetDirectory, targetFileName))) { + if (runtime == RuntimeCopyOperation.MERGED) { + vm.add(runtimeInjector); + } + vm.build(writer, new DirectoryBuildTarget(targetDirectory)); + vm.checkForMissingItems(); + log.info("JavaScript file successfully built"); + } + if (runtime == RuntimeCopyOperation.SEPARATE) { + resourceToFile("org/teavm/javascript/runtime.js", "runtime.js"); + } + if (mainPageIncluded) { + String text; + try (Reader reader = new InputStreamReader(classLoader.getResourceAsStream( + "org/teavm/tooling/main.html"), "UTF-8")) { + text = IOUtils.toString(reader).replace("${classes.js}", targetFileName); + } + File mainPageFile = new File(targetDirectory, "main.html"); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(mainPageFile), "UTF-8")) { + writer.append(text); + } + } + } catch (IOException e) { + throw new TeaVMToolException("IO error occured", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } + } + } + + private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { + @Override + public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + @SuppressWarnings("resource") + StringWriter writer = new StringWriter(); + resourceToWriter("org/teavm/javascript/runtime.js", writer); + writer.close(); + context.getWriter().append(writer.toString()).newLine(); + } + }; + + private void resourceToFile(String resource, String fileName) throws IOException { + try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { + try (OutputStream output = new FileOutputStream(new File(targetDirectory, fileName))) { + IOUtils.copy(input, output); + } + } + } + + private void resourceToWriter(String resource, Writer writer) throws IOException { + try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { + IOUtils.copy(input, writer, "UTF-8"); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java new file mode 100644 index 000000000..510006be6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java @@ -0,0 +1,36 @@ +/* + * 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.tooling; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMToolException extends Exception { + private static final long serialVersionUID = 579149191624783241L; + + public TeaVMToolException(String message, Throwable cause) { + super(message, cause); + } + + public TeaVMToolException(String message) { + super(message); + } + + public TeaVMToolException(Throwable cause) { + super(cause); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java new file mode 100644 index 000000000..bc1ee5b81 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java @@ -0,0 +1,38 @@ +/* + * 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.tooling; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMToolLog { + void info(String text); + + void debug(String text); + + void warning(String text); + + void error(String text); + + void info(String text, Throwable e); + + void debug(String text, Throwable e); + + void warning(String text, Throwable e); + + void error(String text, Throwable e); +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java new file mode 100644 index 000000000..bf447b030 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java @@ -0,0 +1,69 @@ +/* + * 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.tooling; + +import org.teavm.dependency.*; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionDependency implements DependencyListener { + private MethodReference getMessageRef = new MethodReference("java.lang.Throwable", "getMessage", + ValueType.object("java.lang.String")); + private DependencyNode allClasses; + + @Override + public void started(DependencyAgent agent) { + allClasses = agent.createNode(); + } + + @Override + public void classAchieved(DependencyAgent agent, String className) { + if (isException(agent.getClassSource(), className)) { + allClasses.propagate(className); + } + } + + private boolean isException(ClassReaderSource classSource, String className) { + while (className != null) { + if (className.equals("java.lang.Throwable")) { + return true; + } + ClassReader cls = classSource.get(className); + if (cls == null) { + return false; + } + className = cls.getParent(); + } + return false; + } + + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method) { + if (method.getReference().equals(getMessageRef)) { + allClasses.connect(method.getVariable(0)); + } + } + + @Override + public void fieldAchieved(DependencyAgent agent, FieldDependency field) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java new file mode 100644 index 000000000..2ec7136c6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java @@ -0,0 +1,30 @@ +/* + * 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.tooling; + +import org.teavm.vm.spi.TeaVMHost; +import org.teavm.vm.spi.TeaVMPlugin; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionPlugin implements TeaVMPlugin { + @Override + public void install(TeaVMHost host) { + host.add(new TestExceptionDependency()); + } +} diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/main.html b/teavm-core/src/main/resources/org/teavm/tooling/main.html similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/main.html rename to teavm-core/src/main/resources/org/teavm/tooling/main.html diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html b/teavm-core/src/main/resources/org/teavm/tooling/test/junit.html similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html rename to teavm-core/src/main/resources/org/teavm/tooling/test/junit.html diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index b414e0cfb..a5f57c210 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -15,14 +15,13 @@ */ package org.teavm.maven; -import java.io.*; +import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; -import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -32,15 +31,8 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.teavm.common.ThreadPoolFiniteExecutor; -import org.teavm.javascript.RenderingContext; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; -import org.teavm.parsing.ClasspathClassHolderSource; -import org.teavm.vm.*; -import org.teavm.vm.spi.AbstractRendererListener; +import org.teavm.tooling.*; /** * @@ -94,6 +86,8 @@ public class BuildJavascriptMojo extends AbstractMojo { @Parameter private MethodAlias[] methodAliases; + private TeaVMTool tool = new TeaVMTool(); + public void setProject(MavenProject project) { this.project = project; } @@ -153,106 +147,36 @@ public class BuildJavascriptMojo extends AbstractMojo { @Override public void execute() throws MojoExecutionException { Log log = getLog(); - Runnable finalizer = null; + tool.setLog(new MavenTeaVMToolLog(log)); try { ClassLoader classLoader = prepareClassLoader(); - log.info("Building JavaScript file"); - TeaVMBuilder vmBuilder = new TeaVMBuilder(); - vmBuilder.setClassLoader(classLoader).setClassSource(new ClasspathClassHolderSource(classLoader)); - if (numThreads != 1) { - int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); - final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads); - finalizer = new Runnable() { - @Override public void run() { - executor.stop(); - } - }; - vmBuilder.setExecutor(executor); - } - TeaVM vm = vmBuilder.build(); - vm.setMinifying(minifying); - vm.setBytecodeLogging(bytecodeLogging); - vm.setProperties(properties); - vm.installPlugins(); - for (ClassHolderTransformer transformer : instantiateTransformers(classLoader)) { - vm.add(transformer); - } - if (mainClass != null) { - MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( - ValueType.object("java.lang.String")), ValueType.VOID); - vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) - .withValue(1, "java.lang.String"); - } + tool.setClassLoader(classLoader); + tool.setBytecodeLogging(bytecodeLogging); + tool.setMainClass(mainClass); + tool.setMainPageIncluded(mainPageIncluded); + tool.setMinifying(minifying); + tool.setNumThreads(numThreads); + tool.setRuntime(runtime); + tool.setTargetDirectory(targetDirectory); + tool.setTargetFileName(targetFileName); + tool.getTransformers().addAll(instantiateTransformers(classLoader)); if (classAliases != null) { - for (ClassAlias alias : classAliases) { - vm.exportType(alias.getAlias(), alias.getClassName()); - } + tool.getClassAliases().addAll(Arrays.asList(classAliases)); } if (methodAliases != null) { - for (MethodAlias methodAlias : methodAliases) { - MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(), - MethodDescriptor.parseSignature(methodAlias.getDescriptor())); - TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref); - if (methodAlias.getTypes() != null) { - for (int i = 0; i < methodAlias.getTypes().length; ++i) { - String types = methodAlias.getTypes()[i]; - if (types != null) { - for (String type : types.split(" +")) { - type = type.trim(); - if (!type.isEmpty()) { - entryPoint.withValue(i, type); - } - } - } - } - } - } + tool.getMethodAliases().addAll(Arrays.asList(methodAliases)); } - targetDirectory.mkdirs(); - try (FileWriter writer = new FileWriter(new File(targetDirectory, targetFileName))) { - if (runtime == RuntimeCopyOperation.MERGED) { - vm.add(runtimeInjector); - } - vm.build(writer, new DirectoryBuildTarget(targetDirectory)); - vm.checkForMissingItems(); - log.info("JavaScript file successfully built"); - } - if (runtime == RuntimeCopyOperation.SEPARATE) { - resourceToFile("org/teavm/javascript/runtime.js", "runtime.js"); - } - if (mainPageIncluded) { - String text; - try (Reader reader = new InputStreamReader(classLoader.getResourceAsStream( - "org/teavm/maven/main.html"), "UTF-8")) { - text = IOUtils.toString(reader).replace("${classes.js}", targetFileName); - } - File mainPageFile = new File(targetDirectory, "main.html"); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(mainPageFile), "UTF-8")) { - writer.append(text); - } + if (properties != null) { + tool.getProperties().putAll(properties); } + tool.generate(); } catch (RuntimeException e) { throw new MojoExecutionException("Unexpected error occured", e); - } catch (IOException e) { + } catch (TeaVMToolException e) { throw new MojoExecutionException("IO error occured", e); - } finally { - if (finalizer != null) { - finalizer.run(); - } } } - private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { - @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { - @SuppressWarnings("resource") - StringWriter writer = new StringWriter(); - resourceToWriter("org/teavm/javascript/runtime.js", writer); - writer.close(); - context.getWriter().append(writer.toString()).newLine(); - } - }; - private List instantiateTransformers(ClassLoader classLoader) throws MojoExecutionException { List transformerInstances = new ArrayList<>(); @@ -316,18 +240,4 @@ public class BuildJavascriptMojo extends AbstractMojo { throw new MojoExecutionException("Error gathering classpath information", e); } } - - private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptMojo.class.getClassLoader().getResourceAsStream(resource)) { - try (OutputStream output = new FileOutputStream(new File(targetDirectory, fileName))) { - IOUtils.copy(input, output); - } - } - } - - private void resourceToWriter(String resource, Writer writer) throws IOException { - try (InputStream input = BuildJavascriptMojo.class.getClassLoader().getResourceAsStream(resource)) { - IOUtils.copy(input, writer, "UTF-8"); - } - } } diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java index 1bb62251d..6702cc11c 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java @@ -15,7 +15,8 @@ */ package org.teavm.maven; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; @@ -25,7 +26,6 @@ import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -36,16 +36,11 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.teavm.common.FiniteExecutor; -import org.teavm.common.SimpleFiniteExecutor; -import org.teavm.common.ThreadPoolFiniteExecutor; -import org.teavm.model.*; -import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.model.ClassHolderTransformer; import org.teavm.testing.JUnitTestAdapter; import org.teavm.testing.TestAdapter; -import org.teavm.vm.DirectoryBuildTarget; -import org.teavm.vm.TeaVM; -import org.teavm.vm.TeaVMBuilder; +import org.teavm.tooling.TeaVMTestTool; +import org.teavm.tooling.TeaVMToolException; /** * @@ -57,11 +52,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { private static Set testScopes = new HashSet<>(Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_PROVIDED)); - private Map> groupedMethods = new HashMap<>(); - private Map fileNames = new HashMap<>(); - private List testMethods = new ArrayList<>(); - private List testClasses = new ArrayList<>(); - private TestAdapter adapter; @Component private MavenProject project; @@ -93,16 +83,14 @@ public class BuildJavascriptTestMojo extends AbstractMojo { @Parameter private String[] transformers; - private List transformerInstances; - @Parameter private String[] additionalScripts; - private List additionalScriptLocalPaths = new ArrayList<>(); - @Parameter private Properties properties; + private TeaVMTestTool tool = new TeaVMTestTool(); + public void setProject(MavenProject project) { this.project = project; } @@ -154,124 +142,33 @@ public class BuildJavascriptTestMojo extends AbstractMojo { getLog().info("Tests build skipped as specified by system property"); return; } - Runnable finalizer = null; try { final ClassLoader classLoader = prepareClassLoader(); - createAdapter(classLoader); getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); + tool.setClassLoader(classLoader); + tool.setAdapter(createAdapter(classLoader)); findTestClasses(classLoader, testFiles, ""); if (scanDependencies) { findTestsInDependencies(classLoader); } - final Log log = getLog(); - new File(outputDir, "tests").mkdirs(); - new File(outputDir, "res").mkdirs(); - resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); - resourceToFile("org/teavm/maven/res/junit-support.js", "res/junit-support.js"); - resourceToFile("org/teavm/maven/res/junit.css", "res/junit.css"); - resourceToFile("org/teavm/maven/res/class_obj.png", "res/class_obj.png"); - resourceToFile("org/teavm/maven/res/control-000-small.png", "res/control-000-small.png"); - resourceToFile("org/teavm/maven/res/methpub_obj.png", "res/methpub_obj.png"); - resourceToFile("org/teavm/maven/res/package_obj.png", "res/package_obj.png"); - resourceToFile("org/teavm/maven/res/tick-small-red.png", "res/tick-small-red.png"); - resourceToFile("org/teavm/maven/res/tick-small.png", "res/tick-small.png"); - resourceToFile("org/teavm/maven/res/toggle-small-expand.png", "res/toggle-small-expand.png"); - resourceToFile("org/teavm/maven/res/toggle-small.png", "res/toggle-small.png"); - resourceToFile("org/teavm/maven/junit.html", "junit.html"); - final ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); - for (String testClass : testClasses) { - ClassHolder classHolder = classSource.get(testClass); - if (classHolder == null) { - throw new MojoFailureException("Could not find class " + testClass); - } - findTests(classHolder); + tool.getTransformers().addAll(instantiateTransformers(classLoader)); + tool.setLog(new MavenTeaVMToolLog(getLog())); + tool.setOutputDir(outputDir); + tool.setNumThreads(numThreads); + tool.setMinifying(minifying); + if (properties != null) { + tool.getProperties().putAll(properties); } - - transformerInstances = instantiateTransformers(classLoader); - includeAdditionalScripts(classLoader); - File allTestsFile = new File(outputDir, "tests/all.js"); - try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { - allTestsWriter.write("prepare = function() {\n"); - allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); - boolean first = true; - for (String testClass : testClasses) { - if (!first) { - allTestsWriter.append(","); - } - first = false; - allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); - boolean firstMethod = true; - for (MethodReference methodRef : groupedMethods.get(testClass)) { - String scriptName = "tests/" + fileNames.size() + ".js"; - fileNames.put(methodRef, scriptName); - if (!firstMethod) { - allTestsWriter.append(","); - } - firstMethod = false; - allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + - scriptName + "\", expected : ["); - MethodHolder methodHolder = classSource.get(testClass).getMethod( - methodRef.getDescriptor()); - boolean firstException = true; - for (String exception : adapter.getExpectedExceptions(methodHolder)) { - if (!firstException) { - allTestsWriter.append(", "); - } - firstException = false; - allTestsWriter.append("\"" + exception + "\""); - } - allTestsWriter.append("], additionalScripts : ["); - for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { - if (i > 0) { - allTestsWriter.append(", "); - } - escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); - } - allTestsWriter.append("] }"); - } - allTestsWriter.append("] }"); - } - allTestsWriter.write("], function() {}); }"); - } - int methodsGenerated = 0; - log.info("Generating test files"); - FiniteExecutor executor = new SimpleFiniteExecutor(); - if (numThreads != 1) { - int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); - final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); - finalizer = new Runnable() { - @Override public void run() { - threadedExecutor.stop(); - } - }; - executor = threadedExecutor; - } - for (final MethodReference method : testMethods) { - executor.execute(new Runnable() { - @Override public void run() { - log.debug("Building test for " + method); - try { - decompileClassesForTest(classLoader, new CopyClassHolderSource(classSource), method, - fileNames.get(method), new SimpleFiniteExecutor()); - } catch (IOException e) { - log.error("Error generating JavaScript", e); - } - } - }); - ++methodsGenerated; - } - executor.complete(); - log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); - } catch (IOException e) { - throw new MojoFailureException("IO error occured generating JavaScript files", e); - } finally { - if (finalizer != null) { - finalizer.run(); + if (additionalScripts != null) { + tool.getAdditionalScripts().addAll(Arrays.asList(additionalScripts)); } + tool.generate(); + } catch (TeaVMToolException e) { + throw new MojoFailureException("Error occured generating JavaScript files", e); } } - private void createAdapter(ClassLoader classLoader) throws MojoExecutionException { + private TestAdapter createAdapter(ClassLoader classLoader) throws MojoExecutionException { Class adapterClsRaw; try { adapterClsRaw = Class.forName(adapterClass, true, classLoader); @@ -290,7 +187,7 @@ public class BuildJavascriptTestMojo extends AbstractMojo { throw new MojoExecutionException("No default constructor found for test adapter " + adapterClass, e); } try { - adapter = cons.newInstance(); + return cons.newInstance(); } catch (IllegalAccessException | InstantiationException e) { throw new MojoExecutionException("Error creating test adapter", e); } catch (InvocationTargetException e) { @@ -330,89 +227,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } } - private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, - MethodReference methodRef, String targetName, FiniteExecutor executor) throws IOException { - TeaVM vm = new TeaVMBuilder() - .setClassLoader(classLoader) - .setClassSource(classSource) - .setExecutor(executor) - .build(); - vm.setProperties(properties); - vm.setMinifying(minifying); - vm.installPlugins(); - new TestExceptionPlugin().install(vm); - for (ClassHolderTransformer transformer : transformerInstances) { - vm.add(transformer); - } - File file = new File(outputDir, targetName); - try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { - MethodReference cons = new MethodReference(methodRef.getClassName(), - new MethodDescriptor("", ValueType.VOID)); - MethodReference exceptionMsg = new MethodReference("java.lang.Throwable", "getMessage", - ValueType.object("java.lang.String")); - vm.entryPoint("initInstance", cons); - vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); - vm.entryPoint("extractException", exceptionMsg); - vm.exportType("TestClass", cons.getClassName()); - vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); - if (!vm.hasMissingItems()) { - innerWriter.append("\n"); - innerWriter.append("\nJUnitClient.run();"); - innerWriter.close(); - } else { - innerWriter.append("JUnitClient.reportError(\n"); - StringBuilder sb = new StringBuilder(); - vm.showMissingItems(sb); - escapeStringLiteral(sb.toString(), innerWriter); - innerWriter.append(");"); - getLog().warn("Error building test " + methodRef); - getLog().warn(sb); - } - } - } - - private void escapeStringLiteral(String text, Writer writer) throws IOException { - int index = 0; - while (true) { - int next = text.indexOf('\n', index); - if (next < 0) { - break; - } - escapeString(text.substring(index, next + 1), writer); - writer.append(" +\n"); - index = next + 1; - } - escapeString(text.substring(index), writer); - } - - private void escapeString(String string, Writer writer) throws IOException { - writer.append('\"'); - for (int i = 0; i < string.length(); ++i) { - char c = string.charAt(i); - switch (c) { - case '"': - writer.append("\\\""); - break; - case '\\': - writer.append("\\\\"); - break; - case '\n': - writer.append("\\n"); - break; - case '\r': - writer.append("\\r"); - break; - case '\t': - writer.append("\\t"); - break; - default: - writer.append(c); - break; - } - } - writer.append('\"'); - } - private void findTestsInDependencies(ClassLoader classLoader) throws MojoExecutionException { try { Log log = getLog(); @@ -477,8 +291,8 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } try { Class candidate = Class.forName(className, true, classLoader); - if (adapter.acceptClass(candidate)) { - testClasses.add(candidate.getName()); + if (tool.getAdapter().acceptClass(candidate)) { + tool.getTestClasses().add(candidate.getName()); getLog().info("Test class detected: " + candidate.getName()); } } catch (ClassNotFoundException e) { @@ -486,28 +300,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } } - private void findTests(ClassHolder cls) { - for (MethodHolder method : cls.getMethods()) { - if (adapter.acceptMethod(method)) { - MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); - testMethods.add(ref); - List group = groupedMethods.get(cls.getName()); - if (group == null) { - group = new ArrayList<>(); - groupedMethods.put(cls.getName(), group); - } - group.add(ref); - } - } - } - - private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptTestMojo.class.getClassLoader().getResourceAsStream(resource)) { - try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { - IOUtils.copy(input, output); - } - } - } private List instantiateTransformers(ClassLoader classLoader) throws MojoExecutionException { @@ -543,29 +335,4 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } return transformerInstances; } - - private void includeAdditionalScripts(ClassLoader classLoader) throws MojoExecutionException { - if (additionalScripts == null) { - return; - } - for (String script : additionalScripts) { - String simpleName = script.substring(script.lastIndexOf('/') + 1); - additionalScriptLocalPaths.add("tests/" + simpleName); - if (classLoader.getResource(script) == null) { - throw new MojoExecutionException("Additional script " + script + " was not found"); - } - File file = new File(outputDir, "tests/" + simpleName); - try (InputStream in = classLoader.getResourceAsStream(script)) { - if (!file.exists()) { - file.createNewFile(); - } - try(OutputStream out = new FileOutputStream(file)) { - IOUtils.copy(in, out); - } - } catch (IOException e) { - throw new MojoExecutionException("Error copying additional script " + script, e); - } - } - } } - diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java new file mode 100644 index 000000000..657e69e28 --- /dev/null +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java @@ -0,0 +1,56 @@ +package org.teavm.maven; + +import org.apache.maven.plugin.logging.Log; +import org.teavm.tooling.TeaVMToolLog; + +/** + * + * @author Alexey Andreev + */ +public class MavenTeaVMToolLog implements TeaVMToolLog { + private Log log; + + public MavenTeaVMToolLog(Log log) { + this.log = log; + } + + @Override + public void info(String text) { + log.info(text); + } + + @Override + public void debug(String text) { + log.debug(text); + } + + @Override + public void warning(String text) { + log.warn(text); + } + + @Override + public void error(String text) { + log.error(text); + } + + @Override + public void info(String text, Throwable e) { + log.info(text, e); + } + + @Override + public void debug(String text, Throwable e) { + log.debug(text, e); + } + + @Override + public void warning(String text, Throwable e) { + log.warn(text, e); + } + + @Override + public void error(String text, Throwable e) { + log.error(text, e); + } +}