mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
Implement development server
This commit is contained in:
parent
952ed3f193
commit
eec458089f
|
@ -324,6 +324,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, naming, sourceWriter);
|
||||
renderer.setProperties(controller.getProperties());
|
||||
renderer.setMinifying(minifying);
|
||||
renderer.setProgressConsumer(controller::reportProgress);
|
||||
if (debugEmitter != null) {
|
||||
for (String className : classes.getClassNames()) {
|
||||
ClassHolder cls = classes.get(className);
|
||||
|
@ -352,7 +353,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
|
||||
renderer.prepare(clsNodes);
|
||||
runtimeRenderer.renderRuntime();
|
||||
renderer.render(clsNodes);
|
||||
if (!renderer.render(clsNodes)) {
|
||||
return;
|
||||
}
|
||||
renderer.renderStringPool();
|
||||
renderer.renderStringConstants();
|
||||
renderer.renderCompatibilityStubs();
|
||||
|
|
|
@ -52,20 +52,20 @@ public class DefaultNamingStrategy implements NamingStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getFullNameFor(MethodReference method) throws NamingException {
|
||||
public String getFullNameFor(MethodReference method) {
|
||||
return getFullNameFor(method, 'M');
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameForInit(MethodReference method) throws NamingException {
|
||||
public String getNameForInit(MethodReference method) {
|
||||
return getFullNameFor(method, 'I');
|
||||
}
|
||||
|
||||
private String getFullNameFor(MethodReference method, char classifier) throws NamingException {
|
||||
private String getFullNameFor(MethodReference method, char classifier) {
|
||||
MethodReference originalMethod = method;
|
||||
method = getRealMethod(method);
|
||||
if (method == null) {
|
||||
throw new NamingException("Can't provide name for method as it was not found: " + originalMethod);
|
||||
method = originalMethod;
|
||||
}
|
||||
|
||||
MethodReference resolvedMethod = method;
|
||||
|
@ -86,7 +86,7 @@ public class DefaultNamingStrategy implements NamingStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getFullNameFor(FieldReference field) throws NamingException {
|
||||
public String getFullNameFor(FieldReference field) {
|
||||
String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName());
|
||||
if (!realCls.equals(field.getClassName())) {
|
||||
String alias = getNameFor(new FieldReference(realCls, field.getFieldName()));
|
||||
|
@ -99,12 +99,12 @@ public class DefaultNamingStrategy implements NamingStrategy {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getNameForFunction(String name) throws NamingException {
|
||||
public String getNameForFunction(String name) {
|
||||
return functionAliases.computeIfAbsent(name, key -> aliasProvider.getFunctionAlias(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameForClassInit(String className) throws NamingException {
|
||||
public String getNameForClassInit(String className) {
|
||||
return classInitAliases.computeIfAbsent(className, key -> aliasProvider.getClassInitAlias(key));
|
||||
}
|
||||
|
||||
|
@ -131,17 +131,16 @@ public class DefaultNamingStrategy implements NamingStrategy {
|
|||
String initialCls = cls;
|
||||
while (!fieldExists(cls, field)) {
|
||||
ClassReader clsHolder = classSource.get(cls);
|
||||
cls = clsHolder.getParent();
|
||||
if (cls == null) {
|
||||
throw new NamingException("Can't provide name for field as the field not found: "
|
||||
+ initialCls + "." + field);
|
||||
if (clsHolder == null || clsHolder.getParent() == null) {
|
||||
return initialCls;
|
||||
}
|
||||
cls = clsHolder.getParent();
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
private boolean fieldExists(String cls, String field) {
|
||||
ClassReader classHolder = classSource.get(cls);
|
||||
return classHolder.getField(field) != null;
|
||||
return classHolder != null && classHolder.getField(field) != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 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.backend.javascript.codegen;
|
||||
|
||||
public class NamingException extends RuntimeException {
|
||||
private static final long serialVersionUID = 3472322553091962348L;
|
||||
|
||||
public NamingException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NamingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -20,19 +20,19 @@ import org.teavm.model.MethodDescriptor;
|
|||
import org.teavm.model.MethodReference;
|
||||
|
||||
public interface NamingStrategy {
|
||||
String getNameFor(String cls) throws NamingException;
|
||||
String getNameFor(String cls);
|
||||
|
||||
String getNameFor(MethodDescriptor method) throws NamingException;
|
||||
String getNameFor(MethodDescriptor method);
|
||||
|
||||
String getNameForInit(MethodReference method) throws NamingException;
|
||||
String getNameForInit(MethodReference method);
|
||||
|
||||
String getFullNameFor(MethodReference method) throws NamingException;
|
||||
String getFullNameFor(MethodReference method);
|
||||
|
||||
String getNameFor(FieldReference field) throws NamingException;
|
||||
String getNameFor(FieldReference field);
|
||||
|
||||
String getFullNameFor(FieldReference method) throws NamingException;
|
||||
String getFullNameFor(FieldReference method);
|
||||
|
||||
String getNameForFunction(String name) throws NamingException;
|
||||
String getNameForFunction(String name);
|
||||
|
||||
String getNameForClassInit(String className) throws NamingException;
|
||||
String getNameForClassInit(String className);
|
||||
}
|
||||
|
|
|
@ -98,45 +98,43 @@ public class SourceWriter implements Appendable, LocationProvider {
|
|||
innerWriter.append(csq, start, end);
|
||||
}
|
||||
|
||||
public SourceWriter appendClass(String cls) throws NamingException, IOException {
|
||||
public SourceWriter appendClass(String cls) throws IOException {
|
||||
return append(naming.getNameFor(cls));
|
||||
}
|
||||
|
||||
public SourceWriter appendClass(Class<?> cls) throws NamingException, IOException {
|
||||
public SourceWriter appendClass(Class<?> cls) throws IOException {
|
||||
return append(naming.getNameFor(cls.getName()));
|
||||
}
|
||||
|
||||
public SourceWriter appendField(FieldReference field) throws NamingException, IOException {
|
||||
public SourceWriter appendField(FieldReference field) throws IOException {
|
||||
return append(naming.getNameFor(field));
|
||||
}
|
||||
|
||||
public SourceWriter appendStaticField(FieldReference field) throws NamingException, IOException {
|
||||
public SourceWriter appendStaticField(FieldReference field) throws IOException {
|
||||
return append(naming.getFullNameFor(field));
|
||||
}
|
||||
|
||||
public SourceWriter appendMethod(MethodDescriptor method) throws NamingException, IOException {
|
||||
public SourceWriter appendMethod(MethodDescriptor method) throws IOException {
|
||||
return append(naming.getNameFor(method));
|
||||
}
|
||||
|
||||
public SourceWriter appendMethod(String name, Class<?>... params) throws NamingException, IOException {
|
||||
public SourceWriter appendMethod(String name, Class<?>... params) throws IOException {
|
||||
return append(naming.getNameFor(new MethodDescriptor(name, params)));
|
||||
}
|
||||
|
||||
public SourceWriter appendMethodBody(MethodReference method) throws NamingException, IOException {
|
||||
public SourceWriter appendMethodBody(MethodReference method) throws IOException {
|
||||
return append(naming.getFullNameFor(method));
|
||||
}
|
||||
|
||||
public SourceWriter appendMethodBody(String className, String name, ValueType... params)
|
||||
throws NamingException, IOException {
|
||||
public SourceWriter appendMethodBody(String className, String name, ValueType... params) throws IOException {
|
||||
return append(naming.getFullNameFor(new MethodReference(className, new MethodDescriptor(name, params))));
|
||||
}
|
||||
|
||||
public SourceWriter appendMethodBody(Class<?> cls, String name, Class<?>... params)
|
||||
throws NamingException, IOException {
|
||||
public SourceWriter appendMethodBody(Class<?> cls, String name, Class<?>... params) throws IOException {
|
||||
return append(naming.getFullNameFor(new MethodReference(cls, name, params)));
|
||||
}
|
||||
|
||||
public SourceWriter appendFunction(String name) throws NamingException, IOException {
|
||||
public SourceWriter appendFunction(String name) throws IOException {
|
||||
return append(naming.getNameForFunction(name));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import org.teavm.ast.AsyncMethodNode;
|
||||
import org.teavm.ast.AsyncMethodPart;
|
||||
|
@ -37,7 +38,6 @@ import org.teavm.ast.MethodNodeVisitor;
|
|||
import org.teavm.ast.NativeMethodNode;
|
||||
import org.teavm.ast.RegularMethodNode;
|
||||
import org.teavm.ast.VariableNode;
|
||||
import org.teavm.backend.javascript.codegen.NamingException;
|
||||
import org.teavm.backend.javascript.codegen.NamingOrderer;
|
||||
import org.teavm.backend.javascript.codegen.NamingStrategy;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
|
@ -57,6 +57,7 @@ import org.teavm.model.MethodReader;
|
|||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.ValueType;
|
||||
import org.teavm.vm.RenderingException;
|
||||
import org.teavm.vm.TeaVMProgressFeedback;
|
||||
|
||||
public class Renderer implements RenderingManager {
|
||||
private final NamingStrategy naming;
|
||||
|
@ -72,6 +73,7 @@ public class Renderer implements RenderingManager {
|
|||
private final Diagnostics diagnostics;
|
||||
private RenderingContext context;
|
||||
private List<PostponedFieldInitializer> postponedFieldInitializers = new ArrayList<>();
|
||||
private IntFunction<TeaVMProgressFeedback> progressConsumer = p -> TeaVMProgressFeedback.CONTINUE;
|
||||
|
||||
private ObjectIntMap<String> sizeByClass = new ObjectIntHashMap<>();
|
||||
private int stringPoolSize;
|
||||
|
@ -161,6 +163,10 @@ public class Renderer implements RenderingManager {
|
|||
this.debugEmitter = debugEmitter;
|
||||
}
|
||||
|
||||
public void setProgressConsumer(IntFunction<TeaVMProgressFeedback> progressConsumer) {
|
||||
this.progressConsumer = progressConsumer;
|
||||
}
|
||||
|
||||
public void setProperties(Properties properties) {
|
||||
this.properties.clear();
|
||||
this.properties.putAll(properties);
|
||||
|
@ -265,7 +271,7 @@ public class Renderer implements RenderingManager {
|
|||
}
|
||||
}
|
||||
|
||||
public void render(List<ClassNode> classes) throws RenderingException {
|
||||
public boolean render(List<ClassNode> classes) throws RenderingException {
|
||||
if (minifying) {
|
||||
try {
|
||||
renderRuntimeAliases();
|
||||
|
@ -273,13 +279,18 @@ public class Renderer implements RenderingManager {
|
|||
throw new RenderingException(e);
|
||||
}
|
||||
}
|
||||
int index = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
int start = writer.getOffset();
|
||||
renderDeclaration(cls);
|
||||
renderMethodBodies(cls);
|
||||
appendClassSize(cls.getName(), writer.getOffset() - start);
|
||||
if (progressConsumer.apply(1000 * ++index / classes.size()) == TeaVMProgressFeedback.CANCEL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
renderClassMetadata(classes);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void renderDeclaration(ClassNode cls) throws RenderingException {
|
||||
|
@ -337,8 +348,6 @@ public class Renderer implements RenderingManager {
|
|||
writer.append("var ").appendStaticField(fieldRef).ws().append("=").ws()
|
||||
.append(context.constantToString(value)).append(";").softNewLine();
|
||||
}
|
||||
} catch (NamingException e) {
|
||||
throw new RenderingException("Error rendering class " + cls.getName() + ". See cause for details", e);
|
||||
} catch (IOException e) {
|
||||
throw new RenderingException("IO error occurred", e);
|
||||
}
|
||||
|
@ -366,8 +375,6 @@ public class Renderer implements RenderingManager {
|
|||
for (MethodNode method : cls.getMethods()) {
|
||||
renderBody(method);
|
||||
}
|
||||
} catch (NamingException e) {
|
||||
throw new RenderingException("Error rendering class " + cls.getName() + ". See a cause for details", e);
|
||||
} catch (IOException e) {
|
||||
throw new RenderingException("IO error occurred", e);
|
||||
}
|
||||
|
@ -508,8 +515,6 @@ public class Renderer implements RenderingManager {
|
|||
renderVirtualDeclarations(virtualMethods);
|
||||
}
|
||||
writer.append("]);").newLine();
|
||||
} catch (NamingException e) {
|
||||
throw new RenderingException("Error rendering class metadata. See a cause for details", e);
|
||||
} catch (IOException e) {
|
||||
throw new RenderingException("IO error occurred", e);
|
||||
}
|
||||
|
@ -688,7 +693,7 @@ public class Renderer implements RenderingManager {
|
|||
return minifying ? RenderingUtil.indexToId(index) : "var_" + index;
|
||||
}
|
||||
|
||||
private void renderVirtualDeclarations(Collection<MethodReference> methods) throws NamingException, IOException {
|
||||
private void renderVirtualDeclarations(Collection<MethodReference> methods) throws IOException {
|
||||
if (methods.stream().noneMatch(this::isVirtual)) {
|
||||
writer.append('0');
|
||||
return;
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import org.mozilla.javascript.CompilerEnvirons;
|
||||
import org.mozilla.javascript.Context;
|
||||
import org.mozilla.javascript.ast.AstRoot;
|
||||
import org.teavm.backend.javascript.codegen.NamingException;
|
||||
import org.teavm.backend.javascript.codegen.NamingStrategy;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.model.ClassReader;
|
||||
|
@ -73,8 +72,6 @@ public class RuntimeRenderer {
|
|||
renderRuntimeCreateException();
|
||||
renderCreateStackTraceElement();
|
||||
renderSetStackTrace();
|
||||
} catch (NamingException e) {
|
||||
throw new RenderingException("Error rendering runtime methods. See a cause for details", e);
|
||||
} catch (IOException e) {
|
||||
throw new RenderingException("IO error", e);
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 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.cache;
|
||||
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class AlwaysFreshCacheStatus implements CacheStatus {
|
||||
public static final AlwaysFreshCacheStatus INSTANCE = new AlwaysFreshCacheStatus();
|
||||
|
||||
private AlwaysFreshCacheStatus() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaleClass(String className) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaleMethod(MethodReference method) {
|
||||
return false;
|
||||
}
|
||||
}
|
65
core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java
vendored
Normal file
65
core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2018 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.cache;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.teavm.model.ClassReader;
|
||||
import org.teavm.model.ClassReaderSource;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class MemoryCachedClassReaderSource implements ClassReaderSource, CacheStatus {
|
||||
private ClassReaderSource underlyingSource;
|
||||
private final Map<String, Optional<ClassReader>> cache = new HashMap<>();
|
||||
private final Set<String> freshClasses = new HashSet<>();
|
||||
|
||||
public void setUnderlyingSource(ClassReaderSource underlyingSource) {
|
||||
this.underlyingSource = underlyingSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaleClass(String className) {
|
||||
return !freshClasses.contains(className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStaleMethod(MethodReference method) {
|
||||
return isStaleClass(method.getClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassReader get(String name) {
|
||||
return cache.computeIfAbsent(name, key -> {
|
||||
if (underlyingSource == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(underlyingSource.get(key));
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
freshClasses.addAll(cache.keySet());
|
||||
}
|
||||
|
||||
public void evict(Collection<? extends String> classes) {
|
||||
cache.keySet().removeAll(classes);
|
||||
freshClasses.removeAll(classes);
|
||||
}
|
||||
}
|
|
@ -648,7 +648,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
|
|||
if (interrupted) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
while (!deferredTasks.isEmpty() || !tasks.isEmpty() || !pendingTransitions.isEmpty()) {
|
||||
while (true) {
|
||||
processNodeToNodeTransitionQueue();
|
||||
|
@ -658,12 +657,9 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
|
|||
while (!tasks.isEmpty()) {
|
||||
tasks.remove().run();
|
||||
}
|
||||
if (++index == 100) {
|
||||
if (interruptor != null && !interruptor.shouldContinue()) {
|
||||
interrupted = true;
|
||||
break;
|
||||
}
|
||||
index = 0;
|
||||
if (interruptor != null && !interruptor.shouldContinue()) {
|
||||
interrupted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,6 +243,7 @@ public class MissingItemsProcessor {
|
|||
public void visit(CastInstruction insn) {
|
||||
checkClass(insn.getLocation(), insn.getTargetType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(ClassConstantInstruction insn) {
|
||||
checkClass(insn.getLocation(), insn.getConstant());
|
||||
|
|
53
core/src/main/java/org/teavm/vm/MemoryBuildTarget.java
Normal file
53
core/src/main/java/org/teavm/vm/MemoryBuildTarget.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2018 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.vm;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class MemoryBuildTarget implements BuildTarget {
|
||||
private Map<String, ByteArrayOutputStream> data = new LinkedHashMap<>();
|
||||
private Set<? extends String> names = Collections.unmodifiableSet(data.keySet());
|
||||
|
||||
public Set<? extends String> getNames() {
|
||||
return names;
|
||||
}
|
||||
|
||||
public byte[] getContent(String name) {
|
||||
ByteArrayOutputStream stream = data.get(name);
|
||||
return stream != null ? stream.toByteArray() : null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream createResource(String fileName) throws IOException {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
data.put(fileName, stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public OutputStream appendToResource(String fileName) {
|
||||
return data.computeIfAbsent(fileName, k -> new ByteArrayOutputStream());
|
||||
}
|
||||
}
|
|
@ -140,6 +140,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
private AnnotationAwareCacheStatus cacheStatus;
|
||||
private ProgramDependencyExtractor programDependencyExtractor = new ProgramDependencyExtractor();
|
||||
private List<Predicate<MethodReference>> additionalVirtualMethods = new ArrayList<>();
|
||||
private int lastKnownClasses;
|
||||
|
||||
TeaVM(TeaVMBuilder builder) {
|
||||
target = builder.target;
|
||||
|
@ -336,6 +337,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
return writtenClasses;
|
||||
}
|
||||
|
||||
public void setLastKnownClasses(int lastKnownClasses) {
|
||||
this.lastKnownClasses = lastKnownClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Does actual build. Call this method after TeaVM is fully configured and all entry points
|
||||
* are specified. This method may fail if there are items (classes, methods and fields)
|
||||
|
@ -351,13 +356,16 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
target.setController(targetController);
|
||||
|
||||
// Check dependencies
|
||||
reportPhase(TeaVMPhase.DEPENDENCY_ANALYSIS, 1);
|
||||
reportPhase(TeaVMPhase.DEPENDENCY_ANALYSIS, lastKnownClasses > 0 ? lastKnownClasses : 1);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported());
|
||||
dependencyAnalyzer.setInterruptor(() -> progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE);
|
||||
dependencyAnalyzer.setInterruptor(() -> {
|
||||
int progress = lastKnownClasses > 0 ? dependencyAnalyzer.getReachableClasses().size() : 0;
|
||||
return progressListener.progressReached(progress) == TeaVMProgressFeedback.CONTINUE;
|
||||
});
|
||||
target.contributeDependencies(dependencyAnalyzer);
|
||||
dependencyAnalyzer.processDependencies();
|
||||
if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) {
|
||||
|
@ -368,7 +376,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass);
|
||||
|
||||
// Link
|
||||
reportPhase(TeaVMPhase.LINKING, 1);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
@ -379,28 +386,36 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
}
|
||||
|
||||
// Optimize and allocate registers
|
||||
reportPhase(TeaVMPhase.OPTIMIZATION, 1);
|
||||
int maxOptimizationProgress = classSet.getClassNames().size();
|
||||
if (optimizationLevel == TeaVMOptimizationLevel.ADVANCED) {
|
||||
maxOptimizationProgress *= 2;
|
||||
} else if (optimizationLevel == TeaVMOptimizationLevel.FULL) {
|
||||
maxOptimizationProgress *= 3;
|
||||
}
|
||||
reportPhase(TeaVMPhase.OPTIMIZATION, maxOptimizationProgress);
|
||||
|
||||
int progress = 0;
|
||||
if (optimizationLevel != TeaVMOptimizationLevel.SIMPLE) {
|
||||
devirtualize(classSet, dependencyAnalyzer);
|
||||
progress = devirtualize(progress, classSet, dependencyAnalyzer);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dependencyAnalyzer.cleanup();
|
||||
inline(classSet, dependencyAnalyzer);
|
||||
progress = inline(progress, classSet, dependencyAnalyzer);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
optimize(classSet);
|
||||
optimize(progress, classSet);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render
|
||||
try {
|
||||
reportPhase(TeaVMPhase.RENDERING, 1000);
|
||||
target.emit(classSet, buildTarget, outputName);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error generating output files", e);
|
||||
|
@ -442,28 +457,31 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) {
|
||||
private int devirtualize(int progress, ListableClassHolderSource classes, DependencyInfo dependency) {
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
return progress;
|
||||
}
|
||||
Devirtualization devirtualization = new Devirtualization(dependency, classes);
|
||||
int index = 0;
|
||||
for (String className : classes.getClassNames()) {
|
||||
ClassHolder cls = classes.get(className);
|
||||
for (final MethodHolder method : cls.getMethods()) {
|
||||
for (MethodHolder method : cls.getMethods()) {
|
||||
if (method.getProgram() != null) {
|
||||
devirtualization.apply(method);
|
||||
}
|
||||
}
|
||||
progressListener.progressReached(++index);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
virtualMethods = devirtualization.getVirtualMethods();
|
||||
return progress;
|
||||
}
|
||||
|
||||
private void inline(ListableClassHolderSource classes, DependencyInfo dependencyInfo) {
|
||||
private int inline(int progress, ListableClassHolderSource classes, DependencyInfo dependencyInfo) {
|
||||
if (optimizationLevel != TeaVMOptimizationLevel.FULL) {
|
||||
return;
|
||||
return progress;
|
||||
}
|
||||
|
||||
Map<MethodReference, Program> inlinedPrograms = new HashMap<>();
|
||||
|
@ -479,8 +497,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
inlinedPrograms.put(method.getReference(), program);
|
||||
}
|
||||
}
|
||||
progressListener.progressReached(++progress);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,18 +511,22 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
private void optimize(ListableClassHolderSource classSource) {
|
||||
private int optimize(int progress, ListableClassHolderSource classSource) {
|
||||
for (String className : classSource.getClassNames()) {
|
||||
ClassHolder cls = classSource.get(className);
|
||||
for (MethodHolder method : cls.getMethods()) {
|
||||
processMethod(method, classSource);
|
||||
}
|
||||
progressListener.progressReached(++progress);
|
||||
if (wasCancelled()) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
private void processMethod(MethodHolder method, ListableClassReaderSource classSource) {
|
||||
|
@ -700,5 +723,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
|
|||
public boolean isVirtual(MethodReference method) {
|
||||
return virtualMethods == null || virtualMethods.contains(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeaVMProgressFeedback reportProgress(int progres) {
|
||||
return progressListener.progressReached(progres);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,6 +19,5 @@ public enum TeaVMPhase {
|
|||
DEPENDENCY_ANALYSIS,
|
||||
LINKING,
|
||||
OPTIMIZATION,
|
||||
DECOMPILATION,
|
||||
RENDERING
|
||||
}
|
||||
|
|
|
@ -49,4 +49,6 @@ public interface TeaVMTargetController {
|
|||
Set<? extends String> getPreservedClasses();
|
||||
|
||||
boolean isVirtual(MethodReference method);
|
||||
|
||||
TeaVMProgressFeedback reportProgress(int progress);
|
||||
}
|
||||
|
|
3
pom.xml
3
pom.xml
|
@ -66,7 +66,7 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<sonatypeOssDistMgmtSnapshotsUrl>https://oss.sonatype.org/content/repositories/snapshots/</sonatypeOssDistMgmtSnapshotsUrl>
|
||||
<html4j.version>1.5</html4j.version>
|
||||
<jetty.version>9.2.1.v20140609</jetty.version>
|
||||
<jetty.version>9.4.14.v20181114</jetty.version>
|
||||
<slf4j.version>1.7.7</slf4j.version>
|
||||
<jackson.version>2.6.2</jackson.version>
|
||||
<idea.version>2017.3.5</idea.version>
|
||||
|
@ -93,6 +93,7 @@
|
|||
<module>tools/maven</module>
|
||||
<module>tools/chrome-rdp</module>
|
||||
<module>tools/junit</module>
|
||||
<module>tools/devserver</module>
|
||||
<module>tests</module>
|
||||
<module>extras-slf4j</module>
|
||||
<module>metaprogramming/impl</module>
|
||||
|
|
|
@ -44,6 +44,7 @@ public final class BenchmarkStarter {
|
|||
private static double timeSpentCalculating;
|
||||
private static double totalTime;
|
||||
|
||||
|
||||
private BenchmarkStarter() {
|
||||
}
|
||||
|
||||
|
|
|
@ -50,15 +50,11 @@
|
|||
<groupId>javax.websocket</groupId>
|
||||
<artifactId>javax.websocket-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<version>1.9.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.9.13</version>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package org.teavm.chromerdp;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -29,8 +31,6 @@ import java.util.concurrent.TimeoutException;
|
|||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.teavm.chromerdp.data.CallArgumentDTO;
|
||||
|
@ -149,7 +149,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
|||
future.completeExceptionally(e);
|
||||
}
|
||||
} else {
|
||||
Message message = mapper.reader(Message.class).readValue(messageText);
|
||||
Message message = mapper.readerFor(Message.class).readValue(messageText);
|
||||
if (message.getMethod() == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -404,7 +404,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
|||
|
||||
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
|
||||
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
||||
return result.getValue() != null ? result.getValue().getTextValue() : null;
|
||||
return result.getValue() != null ? result.getValue().textValue() : null;
|
||||
}
|
||||
|
||||
String getRepresentation(String objectId) {
|
||||
|
@ -417,7 +417,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
|||
|
||||
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
|
||||
RemoteObjectDTO result = response != null ? response.getResult() : null;
|
||||
return result.getValue() != null ? result.getValue().getTextValue() : null;
|
||||
return result.getValue() != null ? result.getValue().textValue() : null;
|
||||
}
|
||||
|
||||
private List<RDPLocalVariable> parseProperties(PropertyDescriptorDTO[] properties) {
|
||||
|
@ -452,7 +452,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
|||
}
|
||||
|
||||
private <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
|
||||
return mapper.reader(type).readValue(node);
|
||||
return mapper.readerFor(type).readValue(node);
|
||||
}
|
||||
|
||||
private void sendMessage(Message message) {
|
||||
|
@ -516,7 +516,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
|
|||
if (node == null) {
|
||||
out.complete(null);
|
||||
} else {
|
||||
R response = returnType != void.class ? mapper.reader(returnType).readValue(node) : null;
|
||||
R response = returnType != void.class ? mapper.readerFor(returnType).readValue(node) : null;
|
||||
out.complete(response);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
*/
|
||||
package org.teavm.chromerdp;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Extension;
|
||||
|
@ -57,9 +60,8 @@ public class ChromeRDPServer {
|
|||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
|
||||
|
||||
try {
|
||||
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
|
||||
wscontainer.addEndpoint(new RPDEndpointConfig());
|
||||
server.start();
|
||||
server.join();
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CallArgumentDTO {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CallFrameDTO {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class LocationDTO {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Message {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PropertyDescriptorDTO {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RemoteObjectDTO {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Response {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.data;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ScopeDTO {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.CallArgumentDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.RemoteObjectDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CompileScriptCommand {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CompileScriptResponse {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.LocationDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GetPropertiesCommand {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.PropertyDescriptorDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RemoveBreakpointCommand {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class RunScriptCommand {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ScriptParsedNotification {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.LocationDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.LocationDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package org.teavm.chromerdp.messages;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.util.List;
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
|
||||
import org.teavm.chromerdp.data.CallFrameDTO;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
|
|
@ -51,6 +51,11 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-devserver</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-jso-impl</artifactId>
|
||||
|
|
163
tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java
Normal file
163
tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright 2018 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.util.Arrays;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.OptionBuilder;
|
||||
import org.apache.commons.cli.Options;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.teavm.devserver.DevServer;
|
||||
import org.teavm.tooling.ConsoleTeaVMToolLog;
|
||||
|
||||
public final class TeaVMDevServerRunner {
|
||||
private static Options options = new Options();
|
||||
private ConsoleTeaVMToolLog log = new ConsoleTeaVMToolLog(false);
|
||||
private DevServer devServer;
|
||||
private CommandLine commandLine;
|
||||
|
||||
static {
|
||||
setupOptions();
|
||||
}
|
||||
|
||||
@SuppressWarnings("static-access")
|
||||
private static void setupOptions() {
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("directory")
|
||||
.hasArg()
|
||||
.withDescription("a directory, relative to server's root, which serves generated files")
|
||||
.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
|
||||
.withArgName("classpath")
|
||||
.hasArg()
|
||||
.withDescription("classpath element (either directory or jar file)")
|
||||
.withLongOpt("classpath")
|
||||
.create('p'));
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("sourcepath")
|
||||
.hasArg()
|
||||
.withDescription("source path (either directory or jar file which contains source code)")
|
||||
.withLongOpt("sourcepath")
|
||||
.create('s'));
|
||||
options.addOption(OptionBuilder
|
||||
.withArgName("number")
|
||||
.hasArg()
|
||||
.withDescription("port (default is 9090)")
|
||||
.create("port"));
|
||||
options.addOption(OptionBuilder
|
||||
.withDescription("display indicator on web page")
|
||||
.create("indicator"));
|
||||
options.addOption(OptionBuilder
|
||||
.withDescription("display more messages on server log")
|
||||
.withLongOpt("verbose")
|
||||
.create('v'));
|
||||
}
|
||||
|
||||
private TeaVMDevServerRunner(CommandLine commandLine) {
|
||||
this.commandLine = commandLine;
|
||||
devServer = new DevServer();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
CommandLineParser parser = new PosixParser();
|
||||
CommandLine commandLine;
|
||||
try {
|
||||
commandLine = parser.parse(options, args);
|
||||
} catch (ParseException e) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
TeaVMDevServerRunner runner = new TeaVMDevServerRunner(commandLine);
|
||||
runner.parseArguments();
|
||||
runner.setUp();
|
||||
runner.runAll();
|
||||
}
|
||||
|
||||
private void parseArguments() {
|
||||
parseClassPathOptions();
|
||||
parseSourcePathOptions();
|
||||
parseOutputOptions();
|
||||
|
||||
devServer.setIndicator(commandLine.hasOption("indicator"));
|
||||
if (commandLine.hasOption("port")) {
|
||||
try {
|
||||
devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port")));
|
||||
} catch (NumberFormatException e) {
|
||||
System.err.println("port must be numeric");
|
||||
printUsage();
|
||||
}
|
||||
}
|
||||
|
||||
String[] args = commandLine.getArgs();
|
||||
if (args.length != 1) {
|
||||
System.err.println("Unexpected arguments");
|
||||
printUsage();
|
||||
} else if (args.length == 1) {
|
||||
devServer.setMainClass(args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseOutputOptions() {
|
||||
if (commandLine.hasOption("d")) {
|
||||
devServer.setPathToFile(commandLine.getOptionValue("d"));
|
||||
}
|
||||
if (commandLine.hasOption("f")) {
|
||||
devServer.setFileName(commandLine.getOptionValue("f"));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseClassPathOptions() {
|
||||
if (commandLine.hasOption('p')) {
|
||||
devServer.setClassPath(commandLine.getOptionValues('p'));
|
||||
}
|
||||
}
|
||||
|
||||
private void parseSourcePathOptions() {
|
||||
if (commandLine.hasOption('s')) {
|
||||
devServer.getSourcePath().addAll(Arrays.asList(commandLine.getOptionValues('s')));
|
||||
}
|
||||
}
|
||||
|
||||
private void setUp() {
|
||||
devServer.setLog(log);
|
||||
}
|
||||
|
||||
private void runAll() {
|
||||
devServer.start();
|
||||
}
|
||||
|
||||
private static void printUsage() {
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
formatter.printHelp("java " + TeaVMDevServerRunner.class.getName() + " [OPTIONS] [qualified.main.Class]",
|
||||
options);
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
|
@ -20,24 +20,7 @@ import java.io.IOException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
|
@ -46,10 +29,12 @@ import org.apache.commons.cli.Options;
|
|||
import org.apache.commons.cli.ParseException;
|
||||
import org.apache.commons.cli.PosixParser;
|
||||
import org.teavm.backend.wasm.render.WasmBinaryVersion;
|
||||
import org.teavm.tooling.ConsoleTeaVMToolLog;
|
||||
import org.teavm.tooling.TeaVMProblemRenderer;
|
||||
import org.teavm.tooling.TeaVMTargetType;
|
||||
import org.teavm.tooling.TeaVMTool;
|
||||
import org.teavm.tooling.TeaVMToolException;
|
||||
import org.teavm.tooling.util.FileSystemWatcher;
|
||||
import org.teavm.vm.TeaVMOptimizationLevel;
|
||||
import org.teavm.vm.TeaVMPhase;
|
||||
import org.teavm.vm.TeaVMProgressFeedback;
|
||||
|
@ -58,7 +43,7 @@ import org.teavm.vm.TeaVMProgressListener;
|
|||
public final class TeaVMRunner {
|
||||
private static Options options = new Options();
|
||||
private TeaVMTool tool = new TeaVMTool();
|
||||
private AccumulatingTeaVMToolLog log = new AccumulatingTeaVMToolLog(new ConsoleTeaVMToolLog());
|
||||
private AccumulatingTeaVMToolLog log = new AccumulatingTeaVMToolLog(new ConsoleTeaVMToolLog(false));
|
||||
private CommandLine commandLine;
|
||||
private long startTime;
|
||||
private long phaseStartTime;
|
||||
|
@ -97,12 +82,6 @@ public final class TeaVMRunner {
|
|||
.hasArg()
|
||||
.withArgName("number")
|
||||
.create("O"));
|
||||
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("Generate debug information")
|
||||
.withLongOpt("debug")
|
||||
|
@ -340,30 +319,28 @@ public final class TeaVMRunner {
|
|||
}
|
||||
|
||||
private void buildInteractive() {
|
||||
InteractiveWatcher watcher = new InteractiveWatcher();
|
||||
FileSystemWatcher watcher;
|
||||
try {
|
||||
watcher = new FileSystemWatcher(classPath);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error listening file system events");
|
||||
e.printStackTrace();
|
||||
System.exit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
ProgressListenerImpl progressListener = new ProgressListenerImpl();
|
||||
Thread thread = null;
|
||||
ProgressListenerImpl progressListener = new ProgressListenerImpl(watcher);
|
||||
try {
|
||||
watcher.progressListener = progressListener;
|
||||
thread = new Thread(watcher);
|
||||
thread.start();
|
||||
if (progressListener.cancelRequested.get()) {
|
||||
continue;
|
||||
}
|
||||
build(progressListener);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
} finally {
|
||||
if (!progressListener.cancelRequested.get()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
System.out.println("Waiting for changes...");
|
||||
watcher.waitForChange();
|
||||
watcher.waitForChange(750);
|
||||
watcher.grabChangedFiles();
|
||||
System.out.println();
|
||||
System.out.println("Changes detected. Recompiling...");
|
||||
} catch (InterruptedException | IOException e) {
|
||||
|
@ -372,154 +349,9 @@ public final class TeaVMRunner {
|
|||
}
|
||||
}
|
||||
|
||||
class InteractiveWatcher implements Runnable {
|
||||
volatile ProgressListenerImpl progressListener = new ProgressListenerImpl();
|
||||
private WatchService watchService;
|
||||
private Map<WatchKey, Path> keysToPath = new HashMap<>();
|
||||
private Map<Path, WatchKey> pathsToKey = new HashMap<>();
|
||||
|
||||
InteractiveWatcher() {
|
||||
try {
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
for (String entry : classPath) {
|
||||
Path path = Paths.get(entry);
|
||||
File file = path.toFile();
|
||||
if (file.exists()) {
|
||||
if (!file.isDirectory()) {
|
||||
registerSingle(path.getParent());
|
||||
} else {
|
||||
register(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error setting up file watcher");
|
||||
e.printStackTrace(System.err);
|
||||
System.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
private void register(Path path) throws IOException {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
registerSingle(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void registerSingle(Path path) throws IOException {
|
||||
WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
keysToPath.put(key, path);
|
||||
pathsToKey.put(path, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
waitForChange();
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
progressListener.cancelRequested.set(true);
|
||||
System.out.println("Classpath changed during compilation. Cancelling...");
|
||||
}
|
||||
} catch (InterruptedException | IOException e) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
if (thread.isAlive()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void waitForChange() throws InterruptedException, IOException {
|
||||
take();
|
||||
while (poll(750)) {
|
||||
// continue polling
|
||||
}
|
||||
while (pollNow()) {
|
||||
// continue polling
|
||||
}
|
||||
}
|
||||
|
||||
private void take() throws InterruptedException, IOException {
|
||||
while (true) {
|
||||
WatchKey key = watchService.take();
|
||||
if (key != null) {
|
||||
if (!filter(key).isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean poll(int milliseconds) throws IOException, InterruptedException {
|
||||
long end = System.currentTimeMillis() + milliseconds;
|
||||
while (true) {
|
||||
int timeToWait = (int) (end - System.currentTimeMillis());
|
||||
WatchKey key = watchService.poll(timeToWait, TimeUnit.MILLISECONDS);
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
if (!filter(key).isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean pollNow() throws IOException {
|
||||
WatchKey key = watchService.poll();
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
filter(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Path> filter(WatchKey key) throws IOException {
|
||||
List<Path> result = new ArrayList<>();
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
Path path = filter(key, event);
|
||||
if (path != null) {
|
||||
result.add(path);
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
private Path filter(WatchKey baseKey, WatchEvent<?> event) throws IOException {
|
||||
if (!(event.context() instanceof Path)) {
|
||||
return null;
|
||||
}
|
||||
Path basePath = keysToPath.get(baseKey);
|
||||
Path path = basePath.resolve((Path) event.context());
|
||||
WatchKey key = pathsToKey.get(path);
|
||||
|
||||
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
if (key != null) {
|
||||
pathsToKey.remove(path);
|
||||
keysToPath.remove(key);
|
||||
key.cancel();
|
||||
}
|
||||
} else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
|
||||
if (Files.isDirectory(path)) {
|
||||
register(path);
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private void buildNonInteractive() {
|
||||
try {
|
||||
build(new ProgressListenerImpl());
|
||||
build(new ProgressListenerImpl(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
System.exit(-2);
|
||||
|
@ -561,12 +393,17 @@ public final class TeaVMRunner {
|
|||
|
||||
class ProgressListenerImpl implements TeaVMProgressListener {
|
||||
private TeaVMPhase currentPhase;
|
||||
AtomicBoolean cancelRequested = new AtomicBoolean();
|
||||
private FileSystemWatcher fileSystemWatcher;
|
||||
|
||||
ProgressListenerImpl(FileSystemWatcher fileSystemWatcher) {
|
||||
this.fileSystemWatcher = fileSystemWatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeaVMProgressFeedback progressReached(int progress) {
|
||||
return cancelRequested.get() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE;
|
||||
return getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
|
||||
log.flush();
|
||||
|
@ -585,16 +422,25 @@ public final class TeaVMRunner {
|
|||
case OPTIMIZATION:
|
||||
System.out.print("Optimizing code...");
|
||||
break;
|
||||
case DECOMPILATION:
|
||||
System.out.print("Decompiling...");
|
||||
break;
|
||||
case RENDERING:
|
||||
System.out.print("Generating output...");
|
||||
break;
|
||||
}
|
||||
currentPhase = phase;
|
||||
}
|
||||
return cancelRequested.get() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE;
|
||||
return getStatus();
|
||||
}
|
||||
|
||||
private TeaVMProgressFeedback getStatus() {
|
||||
try {
|
||||
if (fileSystemWatcher != null && fileSystemWatcher.hasChanges()) {
|
||||
System.out.println("Classes changed during compilation. Canceling.");
|
||||
return TeaVMProgressFeedback.CANCEL;
|
||||
}
|
||||
return TeaVMProgressFeedback.CONTINUE;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO error occurred");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2014 Alexey Andreev.
|
||||
* Copyright 2018 Alexey Andreev.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,11 +13,15 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.teavm.cli;
|
||||
package org.teavm.tooling;
|
||||
|
||||
import org.teavm.tooling.TeaVMToolLog;
|
||||
public class ConsoleTeaVMToolLog implements TeaVMToolLog {
|
||||
private boolean debug;
|
||||
|
||||
public ConsoleTeaVMToolLog(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
class ConsoleTeaVMToolLog implements TeaVMToolLog {
|
||||
@Override
|
||||
public void info(String text) {
|
||||
System.out.println("INFO: " + text);
|
||||
|
@ -25,7 +29,9 @@ class ConsoleTeaVMToolLog implements TeaVMToolLog {
|
|||
|
||||
@Override
|
||||
public void debug(String text) {
|
||||
System.out.println("DEBUG: " + text);
|
||||
if (debug) {
|
||||
System.out.println("DEBUG: " + text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -46,8 +52,10 @@ class ConsoleTeaVMToolLog implements TeaVMToolLog {
|
|||
|
||||
@Override
|
||||
public void debug(String text, Throwable e) {
|
||||
System.out.println("DEBUG: " + text);
|
||||
e.printStackTrace(System.out);
|
||||
if (debug) {
|
||||
System.out.println("DEBUG: " + text);
|
||||
e.printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2018 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchKey;
|
||||
import java.nio.file.WatchService;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FileSystemWatcher {
|
||||
private WatchService watchService;
|
||||
private Map<WatchKey, Path> keysToPath = new HashMap<>();
|
||||
private Map<Path, WatchKey> pathsToKey = new HashMap<>();
|
||||
private Set<File> changedFiles = new LinkedHashSet<>();
|
||||
|
||||
public FileSystemWatcher(String[] classPath) throws IOException {
|
||||
watchService = FileSystems.getDefault().newWatchService();
|
||||
for (String entry : classPath) {
|
||||
Path path = Paths.get(entry);
|
||||
File file = path.toFile();
|
||||
if (file.exists()) {
|
||||
if (!file.isDirectory()) {
|
||||
registerSingle(path.getParent());
|
||||
} else {
|
||||
register(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() throws IOException {
|
||||
watchService.close();
|
||||
}
|
||||
|
||||
private List<Path> register(Path path) throws IOException {
|
||||
List<Path> files = new ArrayList<>();
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
registerSingle(dir);
|
||||
files.addAll(Arrays.stream(dir.toFile().listFiles(File::isFile)).map(File::toPath)
|
||||
.collect(Collectors.toList()));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
private void registerSingle(Path path) throws IOException {
|
||||
WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
|
||||
keysToPath.put(key, path);
|
||||
pathsToKey.put(path, key);
|
||||
}
|
||||
|
||||
public boolean hasChanges() throws IOException {
|
||||
return !changedFiles.isEmpty() || pollNow();
|
||||
}
|
||||
|
||||
public void waitForChange(int timeout) throws InterruptedException, IOException {
|
||||
if (!hasChanges()) {
|
||||
take();
|
||||
}
|
||||
while (poll(timeout)) {
|
||||
// continue polling
|
||||
}
|
||||
while (pollNow()) {
|
||||
// continue polling
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> grabChangedFiles() {
|
||||
List<File> result = new ArrayList<>(changedFiles);
|
||||
changedFiles.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
private void take() throws InterruptedException, IOException {
|
||||
while (true) {
|
||||
WatchKey key = watchService.take();
|
||||
if (key != null && filter(key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean poll(int milliseconds) throws IOException, InterruptedException {
|
||||
long end = System.currentTimeMillis() + milliseconds;
|
||||
while (true) {
|
||||
int timeToWait = (int) (end - System.currentTimeMillis());
|
||||
if (timeToWait <= 0) {
|
||||
return false;
|
||||
}
|
||||
WatchKey key = watchService.poll(timeToWait, TimeUnit.MILLISECONDS);
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
if (filter(key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean pollNow() throws IOException {
|
||||
WatchKey key = watchService.poll();
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
return filter(key);
|
||||
}
|
||||
|
||||
private boolean filter(WatchKey key) throws IOException {
|
||||
boolean hasNew = false;
|
||||
for (WatchEvent<?> event : key.pollEvents()) {
|
||||
List<Path> paths = filter(key, event);
|
||||
if (!paths.isEmpty()) {
|
||||
changedFiles.addAll(paths.stream().map(Path::toFile).collect(Collectors.toList()));
|
||||
hasNew = true;
|
||||
}
|
||||
}
|
||||
key.reset();
|
||||
return hasNew;
|
||||
}
|
||||
|
||||
private List<Path> filter(WatchKey baseKey, WatchEvent<?> event) throws IOException {
|
||||
if (!(event.context() instanceof Path)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Path basePath = keysToPath.get(baseKey);
|
||||
Path path = basePath.resolve((Path) event.context());
|
||||
WatchKey key = pathsToKey.get(path);
|
||||
|
||||
List<Path> result = new ArrayList<>();
|
||||
result.add(path);
|
||||
|
||||
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
|
||||
if (key != null) {
|
||||
pathsToKey.remove(path);
|
||||
keysToPath.remove(key);
|
||||
key.cancel();
|
||||
}
|
||||
} else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
|
||||
if (Files.isDirectory(path)) {
|
||||
result.addAll(register(path));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
129
tools/devserver/pom.xml
Normal file
129
tools/devserver/pom.xml
Normal file
|
@ -0,0 +1,129 @@
|
|||
<!--
|
||||
~ Copyright 2018 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.6.0-SNAPSHOT</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
<artifactId>teavm-devserver</artifactId>
|
||||
|
||||
<name>TeaVM Dev Sever</name>
|
||||
<description>TeaVM development server that builds and serves JavaScript files</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-commons</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-util</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.carrotsearch</groupId>
|
||||
<artifactId>hppc</artifactId>
|
||||
<version>0.7.3</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mozilla</groupId>
|
||||
<artifactId>rhino</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-tooling</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-classlib</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-metaprogramming-impl</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.teavm</groupId>
|
||||
<artifactId>teavm-jso-impl</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>javax-websocket-server-impl</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<configuration>
|
||||
<configLocation>../../checkstyle.xml</configLocation>
|
||||
<propertyExpansion>config_loc=${basedir}/../..</propertyExpansion>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<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>
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* Copyright 2018 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.devserver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.teavm.backend.javascript.JavaScriptTarget;
|
||||
import org.teavm.cache.InMemoryMethodNodeCache;
|
||||
import org.teavm.cache.InMemoryProgramCache;
|
||||
import org.teavm.cache.MemoryCachedClassReaderSource;
|
||||
import org.teavm.debugging.information.DebugInformation;
|
||||
import org.teavm.debugging.information.DebugInformationBuilder;
|
||||
import org.teavm.dependency.FastDependencyAnalyzer;
|
||||
import org.teavm.model.ClassReader;
|
||||
import org.teavm.model.PreOptimizingClassHolderSource;
|
||||
import org.teavm.parsing.ClasspathClassHolderSource;
|
||||
import org.teavm.tooling.EmptyTeaVMToolLog;
|
||||
import org.teavm.tooling.TeaVMProblemRenderer;
|
||||
import org.teavm.tooling.TeaVMToolLog;
|
||||
import org.teavm.tooling.util.FileSystemWatcher;
|
||||
import org.teavm.vm.MemoryBuildTarget;
|
||||
import org.teavm.vm.TeaVM;
|
||||
import org.teavm.vm.TeaVMBuilder;
|
||||
import org.teavm.vm.TeaVMOptimizationLevel;
|
||||
import org.teavm.vm.TeaVMPhase;
|
||||
import org.teavm.vm.TeaVMProgressFeedback;
|
||||
import org.teavm.vm.TeaVMProgressListener;
|
||||
|
||||
public class CodeServlet extends HttpServlet {
|
||||
private static final Supplier<InputStream> EMPTY_CONTENT = () -> null;
|
||||
|
||||
private String mainClass;
|
||||
private String[] classPath;
|
||||
private String fileName = "classes.js";
|
||||
private String pathToFile = "/";
|
||||
private List<String> sourcePath = new ArrayList<>();
|
||||
private TeaVMToolLog log = new EmptyTeaVMToolLog();
|
||||
private boolean indicator;
|
||||
private int port;
|
||||
|
||||
private Map<String, Supplier<InputStream>> sourceFileCache = new HashMap<>();
|
||||
|
||||
private volatile boolean stopped;
|
||||
private FileSystemWatcher watcher;
|
||||
private MemoryCachedClassReaderSource classSource;
|
||||
private InMemoryProgramCache programCache;
|
||||
private InMemoryMethodNodeCache astCache;
|
||||
private int lastReachedClasses;
|
||||
private boolean firstTime = true;
|
||||
|
||||
private final Object contentLock = new Object();
|
||||
private final Map<String, byte[]> content = new HashMap<>();
|
||||
private MemoryBuildTarget buildTarget = new MemoryBuildTarget();
|
||||
|
||||
private CodeWsEndpoint wsEndpoint;
|
||||
|
||||
public CodeServlet(String mainClass, String[] classPath) {
|
||||
this.mainClass = mainClass;
|
||||
this.classPath = classPath.clone();
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public void setPathToFile(String pathToFile) {
|
||||
if (!pathToFile.endsWith("/")) {
|
||||
pathToFile += "/";
|
||||
}
|
||||
if (!pathToFile.startsWith("/")) {
|
||||
pathToFile = "/" + pathToFile;
|
||||
}
|
||||
this.pathToFile = pathToFile;
|
||||
}
|
||||
|
||||
public List<String> getSourcePath() {
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
public void setLog(TeaVMToolLog log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public void setIndicator(boolean indicator) {
|
||||
this.indicator = indicator;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void setWsEndpoint(CodeWsEndpoint wsEndpoint) {
|
||||
this.wsEndpoint = wsEndpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
String path = req.getPathInfo();
|
||||
if (path != null) {
|
||||
log.debug("Serving " + path);
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
if (path.startsWith(pathToFile) && path.length() > pathToFile.length()) {
|
||||
String fileName = path.substring(pathToFile.length());
|
||||
if (fileName.startsWith("src/")) {
|
||||
if (serveSourceFile(fileName.substring("src/".length()), resp)) {
|
||||
log.debug("File " + path + " served as source file");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
byte[] fileContent;
|
||||
boolean firstTime;
|
||||
synchronized (contentLock) {
|
||||
fileContent = content.get(fileName);
|
||||
firstTime = this.firstTime;
|
||||
}
|
||||
if (fileContent != null) {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
resp.getOutputStream().write(fileContent);
|
||||
resp.getOutputStream().flush();
|
||||
log.debug("File " + path + " served as generated file");
|
||||
return;
|
||||
} else if (fileName.equals(this.fileName) && indicator && firstTime) {
|
||||
serveBootFile(resp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("File " + path + " not found");
|
||||
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException {
|
||||
super.init();
|
||||
Thread thread = new Thread(this::runTeaVM);
|
||||
thread.setDaemon(true);
|
||||
thread.setName("TeaVM compiler");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private boolean serveSourceFile(String fileName, HttpServletResponse resp) throws IOException {
|
||||
try (InputStream stream = sourceFileCache.computeIfAbsent(fileName, this::findSourceFile).get()) {
|
||||
if (stream == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
IOUtils.copy(stream, resp.getOutputStream());
|
||||
resp.getOutputStream().flush();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<InputStream> findSourceFile(String fileName) {
|
||||
for (String element : sourcePath) {
|
||||
File sourceFile = new File(element);
|
||||
if (sourceFile.isFile()) {
|
||||
Supplier<InputStream> result = findSourceFileInZip(sourceFile, fileName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
} else if (sourceFile.isDirectory()) {
|
||||
File result = new File(sourceFile, fileName);
|
||||
if (result.exists()) {
|
||||
return () -> {
|
||||
try {
|
||||
return new FileInputStream(result);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_CONTENT;
|
||||
}
|
||||
|
||||
private Supplier<InputStream> findSourceFileInZip(File zipFile, String fileName) {
|
||||
try (ZipFile zip = new ZipFile(zipFile)) {
|
||||
ZipEntry entry = zip.getEntry(fileName);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return () -> {
|
||||
try {
|
||||
ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile));
|
||||
while (true) {
|
||||
ZipEntry e = input.getNextEntry();
|
||||
if (e == null) {
|
||||
return null;
|
||||
}
|
||||
if (e.getName().equals(fileName)) {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void serveBootFile(HttpServletResponse resp) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().write("function main() { }\n");
|
||||
resp.getWriter().write(getIndicatorScript(true));
|
||||
resp.getWriter().flush();
|
||||
log.debug("Served boot file");
|
||||
}
|
||||
|
||||
private void runTeaVM() {
|
||||
try {
|
||||
initBuilder();
|
||||
|
||||
while (!stopped) {
|
||||
buildOnce();
|
||||
|
||||
if (stopped) {
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
watcher.waitForChange(750);
|
||||
} catch (InterruptedException e) {
|
||||
log.info("Build thread interrupted");
|
||||
break;
|
||||
}
|
||||
log.info("Changes detected. Recompiling.");
|
||||
List<String> staleClasses = getChangedClasses(watcher.grabChangedFiles());
|
||||
log.debug("Following classes changed: " + staleClasses);
|
||||
classSource.evict(staleClasses);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
log.error("Compile server crashed", e);
|
||||
} finally {
|
||||
shutdownBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
private void initBuilder() throws IOException {
|
||||
watcher = new FileSystemWatcher(classPath);
|
||||
|
||||
classSource = new MemoryCachedClassReaderSource();
|
||||
astCache = new InMemoryMethodNodeCache();
|
||||
programCache = new InMemoryProgramCache();
|
||||
}
|
||||
|
||||
private void shutdownBuilder() {
|
||||
try {
|
||||
watcher.dispose();
|
||||
} catch (IOException e) {
|
||||
log.debug("Exception caught", e);
|
||||
}
|
||||
classSource = null;
|
||||
watcher = null;
|
||||
astCache = null;
|
||||
programCache = null;
|
||||
synchronized (content) {
|
||||
content.clear();
|
||||
}
|
||||
buildTarget.clear();
|
||||
|
||||
log.info("Build thread complete");
|
||||
}
|
||||
|
||||
private void buildOnce() {
|
||||
DebugInformationBuilder debugInformationBuilder = new DebugInformationBuilder();
|
||||
ClassLoader classLoader = initClassLoader();
|
||||
classSource.setUnderlyingSource(new PreOptimizingClassHolderSource(
|
||||
new ClasspathClassHolderSource(classLoader)));
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
JavaScriptTarget jsTarget = new JavaScriptTarget();
|
||||
|
||||
TeaVM vm = new TeaVMBuilder(jsTarget)
|
||||
.setClassLoader(classLoader)
|
||||
.setClassSource(classSource)
|
||||
.setDependencyAnalyzerFactory(FastDependencyAnalyzer::new)
|
||||
.build();
|
||||
|
||||
jsTarget.setStackTraceIncluded(true);
|
||||
jsTarget.setMinifying(false);
|
||||
jsTarget.setAstCache(astCache);
|
||||
jsTarget.setDebugEmitter(debugInformationBuilder);
|
||||
vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE);
|
||||
vm.setCacheStatus(classSource);
|
||||
vm.addVirtualMethods(m -> true);
|
||||
vm.setProgressListener(progressListener);
|
||||
vm.setProgramCache(programCache);
|
||||
vm.installPlugins();
|
||||
|
||||
vm.setLastKnownClasses(lastReachedClasses);
|
||||
vm.entryPoint(mainClass);
|
||||
|
||||
log.info("Starting build");
|
||||
progressListener.last = 0;
|
||||
progressListener.lastTime = System.currentTimeMillis();
|
||||
vm.build(buildTarget, fileName);
|
||||
addIndicator();
|
||||
generateDebug(debugInformationBuilder);
|
||||
|
||||
postBuild(vm, startTime);
|
||||
}
|
||||
|
||||
private void addIndicator() {
|
||||
if (!indicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
String script = getIndicatorScript(false);
|
||||
try (Writer writer = new OutputStreamWriter(buildTarget.appendToResource(fileName), StandardCharsets.UTF_8)) {
|
||||
writer.append("\n");
|
||||
writer.append(script);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO error occurred writing debug information", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getIndicatorScript(boolean boot) {
|
||||
try (Reader reader = new InputStreamReader(CodeServlet.class.getResourceAsStream("indicator.js"),
|
||||
StandardCharsets.UTF_8)) {
|
||||
String script = IOUtils.toString(reader);
|
||||
script = script.substring(script.indexOf("*/") + 2);
|
||||
script = script.replace("WS_PATH", "localhost:" + port + pathToFile + fileName + ".ws");
|
||||
script = script.replace("BOOT_FLAG", Boolean.toString(boot));
|
||||
script = script.replace("FILE_NAME", "http://localhost:" + port + pathToFile + fileName);
|
||||
return script;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO error occurred writing debug information", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDebug(DebugInformationBuilder debugInformationBuilder) {
|
||||
try {
|
||||
DebugInformation debugInformation = debugInformationBuilder.getDebugInformation();
|
||||
String sourceMapName = fileName + ".map";
|
||||
|
||||
try (Writer writer = new OutputStreamWriter(buildTarget.appendToResource(fileName),
|
||||
StandardCharsets.UTF_8)) {
|
||||
writer.append("\n//# sourceMappingURL=" + sourceMapName);
|
||||
}
|
||||
|
||||
try (Writer writer = new OutputStreamWriter(buildTarget.createResource(sourceMapName),
|
||||
StandardCharsets.UTF_8)) {
|
||||
debugInformation.writeAsSourceMaps(writer, "src", fileName);
|
||||
}
|
||||
debugInformation.write(buildTarget.createResource(fileName + ".teavmdbg"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IO error occurred writing debug information", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void postBuild(TeaVM vm, long startTime) {
|
||||
if (!vm.wasCancelled()) {
|
||||
if (vm.getProblemProvider().getSevereProblems().isEmpty()) {
|
||||
log.info("Build complete successfully");
|
||||
saveNewResult();
|
||||
lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size();
|
||||
classSource.commit();
|
||||
if (wsEndpoint != null) {
|
||||
wsEndpoint.complete(true);
|
||||
}
|
||||
} else {
|
||||
log.info("Build complete with errors");
|
||||
if (wsEndpoint != null) {
|
||||
wsEndpoint.complete(false);
|
||||
}
|
||||
}
|
||||
printStats(vm, startTime);
|
||||
TeaVMProblemRenderer.describeProblems(vm, log);
|
||||
} else {
|
||||
log.info("Build cancelled");
|
||||
}
|
||||
|
||||
buildTarget.clear();
|
||||
}
|
||||
|
||||
private void printStats(TeaVM vm, long startTime) {
|
||||
if (vm.getWrittenClasses() != null) {
|
||||
int classCount = vm.getWrittenClasses().getClassNames().size();
|
||||
int methodCount = 0;
|
||||
for (String className : vm.getWrittenClasses().getClassNames()) {
|
||||
ClassReader cls = vm.getWrittenClasses().get(className);
|
||||
methodCount += cls.getMethods().size();
|
||||
}
|
||||
|
||||
log.info("Classes compiled: " + classCount);
|
||||
log.info("Methods compiled: " + methodCount);
|
||||
}
|
||||
|
||||
log.info("Compilation took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
}
|
||||
|
||||
private void saveNewResult() {
|
||||
synchronized (contentLock) {
|
||||
firstTime = false;
|
||||
content.clear();
|
||||
for (String name : buildTarget.getNames()) {
|
||||
content.put(name, buildTarget.getContent(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getChangedClasses(Collection<File> changedFiles) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
for (File file : changedFiles) {
|
||||
String path = file.getPath();
|
||||
if (!path.endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String prefix = Arrays.stream(classPath)
|
||||
.filter(path::startsWith)
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
int start = prefix.length();
|
||||
if (start < path.length() && path.charAt(start) == '/') {
|
||||
++start;
|
||||
}
|
||||
|
||||
path = path.substring(start, path.length() - ".class".length()).replace('/', '.');
|
||||
result.add(path);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ClassLoader initClassLoader() {
|
||||
URL[] urls = new URL[classPath.length];
|
||||
try {
|
||||
for (int i = 0; i < classPath.length; i++) {
|
||||
urls[i] = new File(classPath[i]).toURI().toURL();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return new URLClassLoader(urls, CodeServlet.class.getClassLoader());
|
||||
}
|
||||
|
||||
private final ProgressListenerImpl progressListener = new ProgressListenerImpl();
|
||||
|
||||
class ProgressListenerImpl implements TeaVMProgressListener {
|
||||
private int start;
|
||||
private int end;
|
||||
private int phaseLimit;
|
||||
private int last;
|
||||
private long lastTime;
|
||||
|
||||
@Override
|
||||
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
|
||||
switch (phase) {
|
||||
case DEPENDENCY_ANALYSIS:
|
||||
start = 0;
|
||||
end = 500;
|
||||
break;
|
||||
case LINKING:
|
||||
start = 400;
|
||||
end = 500;
|
||||
break;
|
||||
case OPTIMIZATION:
|
||||
start = 500;
|
||||
end = 750;
|
||||
break;
|
||||
case RENDERING:
|
||||
start = 750;
|
||||
end = 1000;
|
||||
break;
|
||||
}
|
||||
phaseLimit = count;
|
||||
return progressReached(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TeaVMProgressFeedback progressReached(int progress) {
|
||||
if (wsEndpoint != null && indicator) {
|
||||
int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit;
|
||||
if (current != last) {
|
||||
if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) {
|
||||
lastTime = System.currentTimeMillis();
|
||||
last = current;
|
||||
wsEndpoint.progress(current / 10.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return getResult();
|
||||
}
|
||||
|
||||
private TeaVMProgressFeedback getResult() {
|
||||
if (stopped) {
|
||||
log.info("Trying to cancel compilation due to server stopping");
|
||||
return TeaVMProgressFeedback.CANCEL;
|
||||
}
|
||||
|
||||
try {
|
||||
if (watcher.hasChanges()) {
|
||||
log.info("Changes detected, cancelling build");
|
||||
return TeaVMProgressFeedback.CANCEL;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.info("IO error occurred", e);
|
||||
return TeaVMProgressFeedback.CANCEL;
|
||||
}
|
||||
|
||||
return TeaVMProgressFeedback.CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2018 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.devserver;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import javax.websocket.OnOpen;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
@ServerEndpoint("/")
|
||||
public class CodeWsEndpoint {
|
||||
private Session session;
|
||||
|
||||
@OnOpen
|
||||
public void open(Session session) {
|
||||
this.session = session;
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<CodeWsEndpoint> consumer = (Consumer<CodeWsEndpoint>) session.getUserProperties().get("ws.consumer");
|
||||
if (consumer != null) {
|
||||
consumer.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void progress(double value) {
|
||||
session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }");
|
||||
}
|
||||
|
||||
public void complete(boolean success) {
|
||||
session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }");
|
||||
}
|
||||
}
|
170
tools/devserver/src/main/java/org/teavm/devserver/DevServer.java
Normal file
170
tools/devserver/src/main/java/org/teavm/devserver/DevServer.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 2018 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.devserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import javax.websocket.Decoder;
|
||||
import javax.websocket.Encoder;
|
||||
import javax.websocket.Extension;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
|
||||
import org.teavm.tooling.ConsoleTeaVMToolLog;
|
||||
import org.teavm.tooling.TeaVMToolLog;
|
||||
|
||||
public class DevServer {
|
||||
private String mainClass;
|
||||
private String[] classPath;
|
||||
private String pathToFile = "";
|
||||
private String fileName = "classes.js";
|
||||
private List<String> sourcePath = new ArrayList<>();
|
||||
private boolean indicator;
|
||||
private TeaVMToolLog log = new ConsoleTeaVMToolLog(false);
|
||||
|
||||
private Server server;
|
||||
private int port = 9090;
|
||||
|
||||
public void setMainClass(String mainClass) {
|
||||
this.mainClass = mainClass;
|
||||
}
|
||||
|
||||
public void setClassPath(String[] classPath) {
|
||||
this.classPath = classPath;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void setPathToFile(String pathToFile) {
|
||||
if (!pathToFile.startsWith("/")) {
|
||||
pathToFile = "/" + pathToFile;
|
||||
}
|
||||
if (!pathToFile.endsWith("/")) {
|
||||
pathToFile += "/";
|
||||
}
|
||||
this.pathToFile = pathToFile;
|
||||
}
|
||||
|
||||
public void setLog(TeaVMToolLog log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public void setIndicator(boolean indicator) {
|
||||
this.indicator = indicator;
|
||||
}
|
||||
|
||||
public List<String> getSourcePath() {
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
server = new Server();
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(port);
|
||||
server.addConnector(connector);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
CodeServlet servlet = new CodeServlet(mainClass, classPath);
|
||||
servlet.setFileName(fileName);
|
||||
servlet.setPathToFile(pathToFile);
|
||||
servlet.setLog(log);
|
||||
servlet.getSourcePath().addAll(sourcePath);
|
||||
servlet.setIndicator(indicator);
|
||||
servlet.setPort(port);
|
||||
context.addServlet(new ServletHolder(servlet), "/*");
|
||||
|
||||
try {
|
||||
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
|
||||
wscontainer.addEndpoint(new DevServerEndpointConfig(servlet::setWsEndpoint));
|
||||
server.start();
|
||||
server.join();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
server.stop();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class DevServerEndpointConfig implements ServerEndpointConfig {
|
||||
private Map<String, Object> userProperties = new HashMap<>();
|
||||
|
||||
public DevServerEndpointConfig(Consumer<CodeWsEndpoint> consumer) {
|
||||
userProperties.put("ws.consumer", consumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Decoder>> getDecoders() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Encoder>> getEncoders() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getUserProperties() {
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Configurator getConfigurator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getEndpointClass() {
|
||||
return CodeWsEndpoint.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Extension> getExtensions() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return pathToFile + fileName + ".ws";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubprotocols() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright 2018 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.
|
||||
*/
|
||||
(function () {
|
||||
var boot = BOOT_FLAG;
|
||||
|
||||
function createWebSocket() {
|
||||
var loc = window.location;
|
||||
var newUri = loc.protocol === "https:" ? "wss:" : "ws:";
|
||||
newUri += "//WS_PATH";
|
||||
return new WebSocket(newUri);
|
||||
}
|
||||
|
||||
function createIndicator() {
|
||||
function createMainElement() {
|
||||
var element = document.createElement("div");
|
||||
element.style.position = "fixed";
|
||||
element.style.left = "0";
|
||||
element.style.bottom = "0";
|
||||
element.style.backgroundColor = "black";
|
||||
element.style.color = "white";
|
||||
element.style.opacity = 0.4;
|
||||
element.style.padding = "5px";
|
||||
element.style.fontSize = "18px";
|
||||
element.style.fontWeight = "bold";
|
||||
element.style.pointerEvents = "none";
|
||||
element.style.zIndex = 1000;
|
||||
element.style.display = "none";
|
||||
return element;
|
||||
}
|
||||
|
||||
function createLabelElement() {
|
||||
return document.createElement("span");
|
||||
}
|
||||
|
||||
function createProgressElements() {
|
||||
var element = document.createElement("span");
|
||||
element.style.display = "none";
|
||||
element.style.marginLeft = "10px";
|
||||
element.style.width = "150px";
|
||||
element.style.height = "15px";
|
||||
element.style.borderWidth = "1px";
|
||||
element.style.borderColor = "rgb(170,170,170)";
|
||||
element.style.borderStyle = "solid";
|
||||
element.style.borderRadius = "2px";
|
||||
element.style.backgroundColor = "white";
|
||||
element.style.position = "relative";
|
||||
|
||||
var progress = document.createElement("span");
|
||||
progress.style.display = "block";
|
||||
progress.style.position = "absolute";
|
||||
progress.style.left = "0";
|
||||
progress.style.top = "0";
|
||||
progress.style.bottom = "0";
|
||||
progress.style.backgroundColor = "rgb(210,210,210)";
|
||||
progress.style.borderWidth = "1px";
|
||||
progress.style.borderColor = "rgb(170,170,170)";
|
||||
progress.style.borderRightStyle = "solid";
|
||||
|
||||
element.appendChild(progress);
|
||||
|
||||
return {
|
||||
container: element,
|
||||
indicator: progress
|
||||
};
|
||||
}
|
||||
|
||||
var container = createMainElement();
|
||||
var label = createLabelElement();
|
||||
var progress = createProgressElements();
|
||||
container.appendChild(label);
|
||||
container.appendChild(progress.container);
|
||||
|
||||
return {
|
||||
container: container,
|
||||
label: label,
|
||||
progress: progress,
|
||||
timer: void 0,
|
||||
|
||||
show: function(text, timeout) {
|
||||
this.container.style.display = "";
|
||||
this.label.innerText = text;
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = void 0;
|
||||
}
|
||||
if (timeout) {
|
||||
setTimeout(function() {
|
||||
this.timer = void 0;
|
||||
this.container.style.display = "none";
|
||||
}.bind(this), timeout * 1000);
|
||||
}
|
||||
},
|
||||
|
||||
showProgress(value) {
|
||||
this.progress.container.style.display = "inline-block";
|
||||
this.progress.indicator.style.width = value.toFixed(2) + "%";
|
||||
},
|
||||
|
||||
hideProgress() {
|
||||
this.progress.container.style.display = "none";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var indicator = createIndicator();
|
||||
window.addEventListener("load", function() {
|
||||
document.body.appendChild(indicator.container);
|
||||
});
|
||||
|
||||
var ws = createWebSocket();
|
||||
ws.onmessage = function(event) {
|
||||
var message = JSON.parse(event.data);
|
||||
switch (message.command) {
|
||||
case "compiling":
|
||||
indicator.show("Compiling...");
|
||||
indicator.showProgress(message.progress || 0);
|
||||
break;
|
||||
case "complete":
|
||||
if (message.success) {
|
||||
indicator.show("Compilation complete", 10);
|
||||
if (boot) {
|
||||
var scriptElem = document.createElement("script");
|
||||
scriptElem.src = "FILE_NAME";
|
||||
scriptElem.onload = function() {
|
||||
main();
|
||||
};
|
||||
document.head.appendChild(scriptElem);
|
||||
}
|
||||
} else {
|
||||
indicator.show("Compilation errors occurred")
|
||||
}
|
||||
indicator.hideProgress();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (boot) {
|
||||
indicator.show("File is not ready, about to compile");
|
||||
}
|
||||
})();
|
|
@ -182,8 +182,6 @@ class TeaVMBuild {
|
|||
return "Discovering classes to compile";
|
||||
case LINKING:
|
||||
return "Resolving method invocations";
|
||||
case DECOMPILATION:
|
||||
return "Compiling classes";
|
||||
case OPTIMIZATION:
|
||||
return "Optimizing code";
|
||||
case RENDERING:
|
||||
|
|
Loading…
Reference in New Issue
Block a user