Merge branch 'cli' into date2

Conflicts:
	pom.xml
This commit is contained in:
konsoletyper 2014-06-23 13:25:22 +04:00
commit c0909c7cce
32 changed files with 1404 additions and 372 deletions

View File

@ -78,6 +78,7 @@
<module>teavm-html4j</module> <module>teavm-html4j</module>
<module>teavm-samples</module> <module>teavm-samples</module>
<module>teavm-platform</module> <module>teavm-platform</module>
<module>teavm-cli</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>

4
teavm-cli/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
/.settings
/.classpath
/.project

55
teavm-cli/pom.xml Normal file
View File

@ -0,0 +1,55 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.teavm</groupId>
<artifactId>teavm</artifactId>
<version>0.2-SNAPSHOT</version>
</parent>
<artifactId>teavm-cli</artifactId>
<name>TeaVM CLI</name>
<description>TeaVM command line tools</description>
<dependencies>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}
}

View File

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

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.maven; package org.teavm.tooling;
/** /**
* *

View File

@ -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 <konsoletyper@gmail.com>
*/
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) {
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.maven; package org.teavm.tooling;
/** /**
* *

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.maven; package org.teavm.tooling;
/** /**
* *

View File

@ -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 <konsoletyper@gmail.com>
*/
public class TeaVMTestTool {
private Map<String, List<MethodReference>> groupedMethods = new HashMap<>();
private Map<MethodReference, String> fileNames = new HashMap<>();
private List<MethodReference> testMethods = new ArrayList<>();
private File outputDir = new File(".");
private boolean minifying = true;
private int numThreads = 1;
private TestAdapter adapter = new JUnitTestAdapter();
private List<ClassHolderTransformer> transformers = new ArrayList<>();
private List<String> additionalScripts = new ArrayList<>();
private List<String> additionalScriptLocalPaths = new ArrayList<>();
private Properties properties = new Properties();
private List<String> 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<ClassHolderTransformer> getTransformers() {
return transformers;
}
public List<String> getAdditionalScripts() {
return additionalScripts;
}
public Properties getProperties() {
return properties;
}
public List<String> 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<MethodReference> 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<MethodReference> 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("<init>", 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('\"');
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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<ClassHolderTransformer> transformers = new ArrayList<>();
private List<ClassAlias> classAliases = new ArrayList<>();
private List<MethodAlias> 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<ClassHolderTransformer> getTransformers() {
return transformers;
}
public List<ClassAlias> getClassAliases() {
return classAliases;
}
public List<MethodAlias> 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");
}
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}

View File

@ -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) {
}
}

View File

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

View File

@ -15,14 +15,13 @@
*/ */
package org.teavm.maven; package org.teavm.maven;
import java.io.*; import java.io.File;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.*; import java.util.*;
import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException; 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.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject; 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.ClassHolderTransformer;
import org.teavm.model.MethodDescriptor; import org.teavm.tooling.*;
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;
/** /**
* *
@ -94,6 +86,8 @@ public class BuildJavascriptMojo extends AbstractMojo {
@Parameter @Parameter
private MethodAlias[] methodAliases; private MethodAlias[] methodAliases;
private TeaVMTool tool = new TeaVMTool();
public void setProject(MavenProject project) { public void setProject(MavenProject project) {
this.project = project; this.project = project;
} }
@ -153,106 +147,36 @@ public class BuildJavascriptMojo extends AbstractMojo {
@Override @Override
public void execute() throws MojoExecutionException { public void execute() throws MojoExecutionException {
Log log = getLog(); Log log = getLog();
Runnable finalizer = null; tool.setLog(new MavenTeaVMToolLog(log));
try { try {
ClassLoader classLoader = prepareClassLoader(); ClassLoader classLoader = prepareClassLoader();
log.info("Building JavaScript file"); tool.setClassLoader(classLoader);
TeaVMBuilder vmBuilder = new TeaVMBuilder(); tool.setBytecodeLogging(bytecodeLogging);
vmBuilder.setClassLoader(classLoader).setClassSource(new ClasspathClassHolderSource(classLoader)); tool.setMainClass(mainClass);
if (numThreads != 1) { tool.setMainPageIncluded(mainPageIncluded);
int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); tool.setMinifying(minifying);
final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads); tool.setNumThreads(numThreads);
finalizer = new Runnable() { tool.setRuntime(runtime);
@Override public void run() { tool.setTargetDirectory(targetDirectory);
executor.stop(); tool.setTargetFileName(targetFileName);
} tool.getTransformers().addAll(instantiateTransformers(classLoader));
};
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");
}
if (classAliases != null) { if (classAliases != null) {
for (ClassAlias alias : classAliases) { tool.getClassAliases().addAll(Arrays.asList(classAliases));
vm.exportType(alias.getAlias(), alias.getClassName());
}
} }
if (methodAliases != null) { if (methodAliases != null) {
for (MethodAlias methodAlias : methodAliases) { tool.getMethodAliases().addAll(Arrays.asList(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(); if (properties != null) {
try (FileWriter writer = new FileWriter(new File(targetDirectory, targetFileName))) { tool.getProperties().putAll(properties);
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);
}
} }
tool.generate();
} catch (RuntimeException e) { } catch (RuntimeException e) {
throw new MojoExecutionException("Unexpected error occured", e); throw new MojoExecutionException("Unexpected error occured", e);
} catch (IOException e) { } catch (TeaVMToolException e) {
throw new MojoExecutionException("IO error occured", 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<ClassHolderTransformer> instantiateTransformers(ClassLoader classLoader) private List<ClassHolderTransformer> instantiateTransformers(ClassLoader classLoader)
throws MojoExecutionException { throws MojoExecutionException {
List<ClassHolderTransformer> transformerInstances = new ArrayList<>(); List<ClassHolderTransformer> transformerInstances = new ArrayList<>();
@ -316,18 +240,4 @@ public class BuildJavascriptMojo extends AbstractMojo {
throw new MojoExecutionException("Error gathering classpath information", e); 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");
}
}
} }

View File

@ -15,7 +15,8 @@
*/ */
package org.teavm.maven; package org.teavm.maven;
import java.io.*; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@ -25,7 +26,6 @@ import java.util.*;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException; 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.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.teavm.common.FiniteExecutor; import org.teavm.model.ClassHolderTransformer;
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.JUnitTestAdapter;
import org.teavm.testing.TestAdapter; import org.teavm.testing.TestAdapter;
import org.teavm.vm.DirectoryBuildTarget; import org.teavm.tooling.TeaVMTestTool;
import org.teavm.vm.TeaVM; import org.teavm.tooling.TeaVMToolException;
import org.teavm.vm.TeaVMBuilder;
/** /**
* *
@ -57,11 +52,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo {
private static Set<String> testScopes = new HashSet<>(Arrays.asList( private static Set<String> testScopes = new HashSet<>(Arrays.asList(
Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME,
Artifact.SCOPE_PROVIDED)); Artifact.SCOPE_PROVIDED));
private Map<String, List<MethodReference>> groupedMethods = new HashMap<>();
private Map<MethodReference, String> fileNames = new HashMap<>();
private List<MethodReference> testMethods = new ArrayList<>();
private List<String> testClasses = new ArrayList<>();
private TestAdapter adapter;
@Component @Component
private MavenProject project; private MavenProject project;
@ -93,16 +83,14 @@ public class BuildJavascriptTestMojo extends AbstractMojo {
@Parameter @Parameter
private String[] transformers; private String[] transformers;
private List<ClassHolderTransformer> transformerInstances;
@Parameter @Parameter
private String[] additionalScripts; private String[] additionalScripts;
private List<String> additionalScriptLocalPaths = new ArrayList<>();
@Parameter @Parameter
private Properties properties; private Properties properties;
private TeaVMTestTool tool = new TeaVMTestTool();
public void setProject(MavenProject project) { public void setProject(MavenProject project) {
this.project = project; this.project = project;
} }
@ -154,124 +142,33 @@ public class BuildJavascriptTestMojo extends AbstractMojo {
getLog().info("Tests build skipped as specified by system property"); getLog().info("Tests build skipped as specified by system property");
return; return;
} }
Runnable finalizer = null;
try { try {
final ClassLoader classLoader = prepareClassLoader(); final ClassLoader classLoader = prepareClassLoader();
createAdapter(classLoader);
getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'");
tool.setClassLoader(classLoader);
tool.setAdapter(createAdapter(classLoader));
findTestClasses(classLoader, testFiles, ""); findTestClasses(classLoader, testFiles, "");
if (scanDependencies) { if (scanDependencies) {
findTestsInDependencies(classLoader); findTestsInDependencies(classLoader);
} }
final Log log = getLog(); tool.getTransformers().addAll(instantiateTransformers(classLoader));
new File(outputDir, "tests").mkdirs(); tool.setLog(new MavenTeaVMToolLog(getLog()));
new File(outputDir, "res").mkdirs(); tool.setOutputDir(outputDir);
resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); tool.setNumThreads(numThreads);
resourceToFile("org/teavm/maven/res/junit-support.js", "res/junit-support.js"); tool.setMinifying(minifying);
resourceToFile("org/teavm/maven/res/junit.css", "res/junit.css"); if (properties != null) {
resourceToFile("org/teavm/maven/res/class_obj.png", "res/class_obj.png"); tool.getProperties().putAll(properties);
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);
} }
if (additionalScripts != null) {
transformerInstances = instantiateTransformers(classLoader); tool.getAdditionalScripts().addAll(Arrays.asList(additionalScripts));
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();
} }
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; Class<?> adapterClsRaw;
try { try {
adapterClsRaw = Class.forName(adapterClass, true, classLoader); 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); throw new MojoExecutionException("No default constructor found for test adapter " + adapterClass, e);
} }
try { try {
adapter = cons.newInstance(); return cons.newInstance();
} catch (IllegalAccessException | InstantiationException e) { } catch (IllegalAccessException | InstantiationException e) {
throw new MojoExecutionException("Error creating test adapter", e); throw new MojoExecutionException("Error creating test adapter", e);
} catch (InvocationTargetException 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("<init>", 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 { private void findTestsInDependencies(ClassLoader classLoader) throws MojoExecutionException {
try { try {
Log log = getLog(); Log log = getLog();
@ -477,8 +291,8 @@ public class BuildJavascriptTestMojo extends AbstractMojo {
} }
try { try {
Class<?> candidate = Class.forName(className, true, classLoader); Class<?> candidate = Class.forName(className, true, classLoader);
if (adapter.acceptClass(candidate)) { if (tool.getAdapter().acceptClass(candidate)) {
testClasses.add(candidate.getName()); tool.getTestClasses().add(candidate.getName());
getLog().info("Test class detected: " + candidate.getName()); getLog().info("Test class detected: " + candidate.getName());
} }
} catch (ClassNotFoundException e) { } 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<MethodReference> 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<ClassHolderTransformer> instantiateTransformers(ClassLoader classLoader) private List<ClassHolderTransformer> instantiateTransformers(ClassLoader classLoader)
throws MojoExecutionException { throws MojoExecutionException {
@ -543,29 +335,4 @@ public class BuildJavascriptTestMojo extends AbstractMojo {
} }
return transformerInstances; 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);
}
}
}
} }

View File

@ -0,0 +1,56 @@
package org.teavm.maven;
import org.apache.maven.plugin.logging.Log;
import org.teavm.tooling.TeaVMToolLog;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
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);
}
}