diff --git a/.idea/runConfigurations/IDEA.xml b/.idea/runConfigurations/IDEA.xml index 234d10d0b..89a67c4c6 100644 --- a/.idea/runConfigurations/IDEA.xml +++ b/.idea/runConfigurations/IDEA.xml @@ -1,9 +1,11 @@ - + \ No newline at end of file diff --git a/classlib/pom.xml b/classlib/pom.xml index dd6286a49..651f8dc76 100644 --- a/classlib/pom.xml +++ b/classlib/pom.xml @@ -80,6 +80,11 @@ provided true + + commons-io + commons-io + true + com.google.code.gson gson @@ -135,6 +140,15 @@ org.apache.maven.plugins maven-javadoc-plugin + + org.apache.maven.plugins + maven-jar-plugin + + + html/** + + + org.apache.maven.plugins maven-shade-plugin @@ -151,7 +165,7 @@ - cd + org.objectweb.asm org.teavm.asm diff --git a/core/pom.xml b/core/pom.xml index 838bc758c..53fcf6dfc 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -107,10 +107,19 @@ junit:junit - org:teavm:* + org.teavm:teavm-interop + org.teavm:teavm-metaprogramming-api com.fasterxml.jackson.core:jackson-annotations + + + *:* + + **/module-info.class + + + org.objectweb.asm diff --git a/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java index 1fe47c283..fff47f76f 100644 --- a/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java +++ b/core/src/main/java/org/teavm/cache/InMemoryMethodNodeCache.java @@ -79,6 +79,13 @@ public class InMemoryMethodNodeCache implements MethodNodeCache { newAsyncItems.clear(); } + public void invalidate() { + cache.clear(); + newItems.clear(); + asyncCache.clear(); + newAsyncItems.clear(); + } + static final class RegularItem { final RegularMethodNode node; final String[] dependencies; diff --git a/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java index ee3f4c26a..bef448dc5 100644 --- a/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java +++ b/core/src/main/java/org/teavm/cache/InMemoryProgramCache.java @@ -59,6 +59,11 @@ public class InMemoryProgramCache implements ProgramCache { newItems.clear(); } + public void invalidate() { + cache.clear(); + newItems.clear(); + } + static final class Item { final Program program; final String[] dependencies; diff --git a/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java b/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java index 13b18515e..0a1415e5f 100644 --- a/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java +++ b/core/src/main/java/org/teavm/cache/MemoryCachedClassReaderSource.java @@ -62,4 +62,9 @@ public class MemoryCachedClassReaderSource implements ClassReaderSource, CacheSt cache.keySet().removeAll(classes); freshClasses.removeAll(classes); } + + public void invalidate() { + cache.clear(); + freshClasses.clear(); + } } diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 76442bc03..3eb5910ab 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -365,7 +365,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository { dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported()); dependencyAnalyzer.setInterruptor(() -> { int progress = lastKnownClasses > 0 ? dependencyAnalyzer.getReachableClasses().size() : 0; - return progressListener.progressReached(progress) == TeaVMProgressFeedback.CONTINUE; + cancelled |= progressListener.progressReached(progress) != TeaVMProgressFeedback.CONTINUE; + return !cancelled; }); target.contributeDependencies(dependencyAnalyzer); dependencyAnalyzer.processDependencies(); diff --git a/interop/core/pom.xml b/interop/core/pom.xml index 0bd082391..cc90b3605 100644 --- a/interop/core/pom.xml +++ b/interop/core/pom.xml @@ -34,17 +34,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/jso/apis/pom.xml b/jso/apis/pom.xml index c6322aef9..d4fed183e 100644 --- a/jso/apis/pom.xml +++ b/jso/apis/pom.xml @@ -43,17 +43,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/jso/core/pom.xml b/jso/core/pom.xml index 92644d7d4..3c1f49711 100644 --- a/jso/core/pom.xml +++ b/jso/core/pom.xml @@ -38,17 +38,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/metaprogramming/api/pom.xml b/metaprogramming/api/pom.xml index e0a6a43df..99c3fc6fa 100644 --- a/metaprogramming/api/pom.xml +++ b/metaprogramming/api/pom.xml @@ -57,17 +57,6 @@ org.apache.maven.plugins maven-javadoc-plugin - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - \ No newline at end of file diff --git a/metaprogramming/impl/pom.xml b/metaprogramming/impl/pom.xml index 540698127..b9781dce7 100644 --- a/metaprogramming/impl/pom.xml +++ b/metaprogramming/impl/pom.xml @@ -46,6 +46,7 @@ commons-io commons-io + true org.ow2.asm @@ -82,17 +83,6 @@ org.apache.maven.plugins maven-javadoc-plugin - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - org.apache.maven.plugins maven-shade-plugin diff --git a/pom.xml b/pom.xml index 86f27e4e7..625f8f3b7 100644 --- a/pom.xml +++ b/pom.xml @@ -197,11 +197,6 @@ rhino ${rhino.version} - - org.apache.maven.plugins - maven-shade-plugin - 3.1.1 - @@ -286,6 +281,11 @@ maven-jar-plugin 3.0.2 + + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 + diff --git a/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java b/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java index 78b207c0e..3fab3cd1c 100644 --- a/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java +++ b/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java @@ -86,7 +86,7 @@ public final class BenchmarkStarter { private static void render() { CanvasRenderingContext2D context = (CanvasRenderingContext2D) canvas.getContext("2d"); context.setFillStyle("white"); - context.setStrokeStyle("grey"); + context.setStrokeStyle("red"); context.fillRect(0, 0, 600, 600); context.save(); context.translate(0, 600); diff --git a/tests/pom.xml b/tests/pom.xml index 103872295..612e05f21 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -48,13 +48,6 @@ teavm-jso-apis ${project.version} - - org.teavm - teavm-jso-apis - ${project.version} - tests - compile - org.teavm teavm-metaprogramming-impl diff --git a/tools/cli/pom.xml b/tools/cli/pom.xml index 678bc8fdd..d0b311d2e 100644 --- a/tools/cli/pom.xml +++ b/tools/cli/pom.xml @@ -110,18 +110,6 @@ maven-javadoc-plugin - - org.apache.maven.plugins - maven-jar-plugin - - - - org.teavm.cli.TeaVMRunner - - - - - org.apache.maven.plugins maven-shade-plugin @@ -134,7 +122,16 @@ + + org.teavm.cli.TeaVMRunner + + + + org.apache.commons + org.teavm.apachecommons + + diff --git a/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java b/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java index 123d6f837..fdec2f02e 100644 --- a/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java +++ b/tools/cli/src/main/java/org/teavm/cli/TeaVMDevServerRunner.java @@ -114,7 +114,7 @@ public final class TeaVMDevServerRunner { devServer.setIndicator(commandLine.hasOption("indicator")); devServer.setReloadedAutomatically(commandLine.hasOption("auto-reload")); - devServer.setVerbose(commandLine.hasOption('v')); + devServer.setLog(new ConsoleTeaVMToolLog(commandLine.hasOption('v'))); if (commandLine.hasOption("port")) { try { devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port"))); diff --git a/tools/core/src/main/java/org/teavm/tooling/InstructionLocationReader.java b/tools/core/src/main/java/org/teavm/tooling/InstructionLocationReader.java index 88324964a..4a225f07d 100644 --- a/tools/core/src/main/java/org/teavm/tooling/InstructionLocationReader.java +++ b/tools/core/src/main/java/org/teavm/tooling/InstructionLocationReader.java @@ -15,11 +15,18 @@ */ package org.teavm.tooling; +import java.util.LinkedHashSet; import java.util.Set; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; import org.teavm.model.TextLocation; import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.vm.TeaVM; -class InstructionLocationReader extends AbstractInstructionReader { +public class InstructionLocationReader extends AbstractInstructionReader { private Set resources; public InstructionLocationReader(Set resources) { @@ -32,4 +39,32 @@ class InstructionLocationReader extends AbstractInstructionReader { resources.add(location.getFileName()); } } + + public static Set extractUsedResources(TeaVM vm) { + Set resources = new LinkedHashSet<>(); + ClassReaderSource classSource = vm.getDependencyClassSource(); + InstructionLocationReader reader = new InstructionLocationReader(resources); + for (MethodReference methodRef : vm.getMethods()) { + ClassReader cls = classSource.get(methodRef.getClassName()); + if (cls == null) { + continue; + } + + MethodReader method = cls.getMethod(methodRef.getDescriptor()); + if (method == null) { + continue; + } + + ProgramReader program = method.getProgram(); + if (program == null) { + continue; + } + + for (int i = 0; i < program.basicBlockCount(); ++i) { + program.basicBlockAt(i).readAllInstructions(reader); + } + } + + return resources; + } } diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 29b33c7bb..c7f683c14 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -52,11 +52,7 @@ import org.teavm.diagnostics.ProblemProvider; import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; -import org.teavm.model.ClassReaderSource; -import org.teavm.model.MethodReader; -import org.teavm.model.MethodReference; import org.teavm.model.PreOptimizingClassHolderSource; -import org.teavm.model.ProgramReader; import org.teavm.parsing.ClasspathClassHolderSource; import org.teavm.tooling.sources.SourceFileProvider; import org.teavm.tooling.sources.SourceFilesCopier; @@ -268,31 +264,7 @@ public class TeaVMTool { return Collections.emptyList(); } - Set resources = new HashSet<>(); - ClassReaderSource classSource = vm.getDependencyClassSource(); - InstructionLocationReader reader = new InstructionLocationReader(resources); - for (MethodReference methodRef : vm.getMethods()) { - ClassReader cls = classSource.get(methodRef.getClassName()); - if (cls == null) { - continue; - } - - MethodReader method = cls.getMethod(methodRef.getDescriptor()); - if (method == null) { - continue; - } - - ProgramReader program = method.getProgram(); - if (program == null) { - continue; - } - - for (int i = 0; i < program.basicBlockCount(); ++i) { - program.basicBlockAt(i).readAllInstructions(reader); - } - } - - return resources; + return InstructionLocationReader.extractUsedResources(vm); } public void addSourceFileProvider(SourceFileProvider sourceFileProvider) { diff --git a/tools/devserver/pom.xml b/tools/devserver/pom.xml index 0bcc64922..d6ae692d8 100644 --- a/tools/devserver/pom.xml +++ b/tools/devserver/pom.xml @@ -66,24 +66,6 @@ teavm-tooling ${project.version} - - org.teavm - teavm-classlib - ${project.version} - runtime - - - org.teavm - teavm-metaprogramming-impl - ${project.version} - runtime - - - org.teavm - teavm-jso-impl - ${project.version} - runtime - org.eclipse.jetty diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java index 1d0e1d026..608bb8895 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeServlet.java @@ -96,8 +96,12 @@ public class CodeServlet extends HttpServlet { private final Set wsEndpoints = new LinkedHashSet<>(); private final Object statusLock = new Object(); + private volatile boolean cancelRequested; private boolean compiling; private double progress; + private boolean waiting; + private Thread buildThread; + private List listeners = new ArrayList<>(); public CodeServlet(String mainClass, String[] classPath) { this.mainClass = mainClass; @@ -160,6 +164,37 @@ public class CodeServlet extends HttpServlet { } } + public void addListener(DevServerListener listener) { + listeners.add(listener); + } + + public void invalidateCache() { + synchronized (statusLock) { + if (compiling) { + return; + } + astCache.invalidate(); + programCache.invalidate(); + classSource.invalidate(); + } + } + + public void buildProject() { + synchronized (statusLock) { + if (waiting) { + buildThread.interrupt(); + } + } + } + + public void cancelBuild() { + synchronized (statusLock) { + if (compiling) { + cancelRequested = true; + } + } + } + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = req.getPathInfo(); @@ -206,15 +241,20 @@ public class CodeServlet extends HttpServlet { public void destroy() { super.destroy(); stopped = true; + synchronized (statusLock) { + if (waiting) { + buildThread.interrupt(); + } + } } @Override public void init() throws ServletException { super.init(); Thread thread = new Thread(this::runTeaVM); - thread.setDaemon(true); thread.setName("TeaVM compiler"); thread.start(); + buildThread = thread; } private boolean serveSourceFile(String fileName, HttpServletResponse resp) throws IOException { @@ -306,12 +346,20 @@ public class CodeServlet extends HttpServlet { } try { + synchronized (statusLock) { + waiting = true; + } watcher.waitForChange(750); + synchronized (statusLock) { + waiting = false; + } + log.info("Changes detected. Recompiling."); } catch (InterruptedException e) { - log.info("Build thread interrupted"); - break; + if (stopped) { + break; + } + log.info("Build triggered by user"); } - log.info("Changes detected. Recompiling."); List staleClasses = getChangedClasses(watcher.grabChangedFiles()); if (staleClasses.size() > 15) { @@ -325,6 +373,7 @@ public class CodeServlet extends HttpServlet { classSource.evict(staleClasses); } + log.info("Build process stopped"); } catch (Throwable e) { log.error("Compile server crashed", e); } finally { @@ -359,6 +408,9 @@ public class CodeServlet extends HttpServlet { } private void buildOnce() { + fireBuildStarted(); + reportProgress(0); + DebugInformationBuilder debugInformationBuilder = new DebugInformationBuilder(); ClassLoader classLoader = initClassLoader(); classSource.setUnderlyingSource(new PreOptimizingClassHolderSource( @@ -390,7 +442,6 @@ public class CodeServlet extends HttpServlet { log.info("Starting build"); progressListener.last = 0; progressListener.lastTime = System.currentTimeMillis(); - reportProgress(0); vm.build(buildTarget, fileName); addIndicator(); generateDebug(debugInformationBuilder); @@ -450,6 +501,7 @@ public class CodeServlet extends HttpServlet { private void postBuild(TeaVM vm, long startTime) { if (!vm.wasCancelled()) { log.info("Recompiled stale methods: " + programCache.getPendingItemsCount()); + fireBuildComplete(vm); if (vm.getProblemProvider().getSevereProblems().isEmpty()) { log.info("Build complete successfully"); saveNewResult(); @@ -466,11 +518,13 @@ public class CodeServlet extends HttpServlet { TeaVMProblemRenderer.describeProblems(vm, log); } else { log.info("Build cancelled"); + fireBuildCancelled(); } astCache.discard(); programCache.discard(); buildTarget.clear(); + cancelRequested = false; } private void printStats(TeaVM vm, long startTime) { @@ -553,6 +607,10 @@ public class CodeServlet extends HttpServlet { for (CodeWsEndpoint endpoint : endpoints) { endpoint.progress(progress); } + + for (DevServerListener listener : listeners) { + listener.compilationProgress(progress); + } } private void reportCompilationComplete(boolean success) { @@ -573,6 +631,25 @@ public class CodeServlet extends HttpServlet { } } + private void fireBuildStarted() { + for (DevServerListener listener : listeners) { + listener.compilationStarted(); + } + } + + private void fireBuildCancelled() { + for (DevServerListener listener : listeners) { + listener.compilationCancelled(); + } + } + + private void fireBuildComplete(TeaVM vm) { + CodeServletBuildResult result = new CodeServletBuildResult(vm, new ArrayList<>(buildTarget.getNames())); + for (DevServerListener listener : listeners) { + listener.compilationComplete(result); + } + } + private final ProgressListenerImpl progressListener = new ProgressListenerImpl(); class ProgressListenerImpl implements TeaVMProgressListener { @@ -622,6 +699,11 @@ public class CodeServlet extends HttpServlet { } private TeaVMProgressFeedback getResult() { + if (cancelRequested) { + log.info("Trying to cancel compilation due to user request"); + return TeaVMProgressFeedback.CANCEL; + } + if (stopped) { log.info("Trying to cancel compilation due to server stopping"); return TeaVMProgressFeedback.CANCEL; diff --git a/tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java b/tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java new file mode 100644 index 000000000..2df2ff254 --- /dev/null +++ b/tools/devserver/src/main/java/org/teavm/devserver/CodeServletBuildResult.java @@ -0,0 +1,63 @@ +/* + * 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.Collection; +import java.util.List; +import org.teavm.callgraph.CallGraph; +import org.teavm.diagnostics.ProblemProvider; +import org.teavm.tooling.InstructionLocationReader; +import org.teavm.tooling.builder.BuildResult; +import org.teavm.vm.TeaVM; + +class CodeServletBuildResult implements BuildResult { + private TeaVM vm; + private List generatedFiles; + private Collection usedResources; + + public CodeServletBuildResult(TeaVM vm, List generatedFiles) { + this.vm = vm; + this.generatedFiles = generatedFiles; + } + + @Override + public CallGraph getCallGraph() { + return vm.getDependencyInfo().getCallGraph(); + } + + @Override + public ProblemProvider getProblems() { + return vm.getProblemProvider(); + } + + @Override + public Collection getUsedResources() { + if (usedResources == null) { + usedResources = InstructionLocationReader.extractUsedResources(vm); + } + return usedResources; + } + + @Override + public Collection getClasses() { + return vm.getClasses(); + } + + @Override + public Collection getGeneratedFiles() { + return generatedFiles; + } +} diff --git a/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java b/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java index 675e7c1dd..c7b499f6d 100644 --- a/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java +++ b/tools/devserver/src/main/java/org/teavm/devserver/DevServer.java @@ -30,7 +30,6 @@ 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 { @@ -41,8 +40,9 @@ public class DevServer { private List sourcePath = new ArrayList<>(); private boolean indicator; private boolean reloadedAutomatically; - private boolean verbose; private TeaVMToolLog log; + private CodeServlet servlet; + private List listeners = new ArrayList<>(); private Server server; private int port = 9090; @@ -85,16 +85,27 @@ public class DevServer { this.reloadedAutomatically = reloadedAutomatically; } - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - public List getSourcePath() { return sourcePath; } + public void invalidateCache() { + servlet.invalidateCache(); + } + + public void buildProject() { + servlet.buildProject(); + } + + public void cancelBuild() { + servlet.cancelBuild(); + } + + public void addListener(DevServerListener listener) { + listeners.add(listener); + } + public void start() { - log = new ConsoleTeaVMToolLog(verbose); server = new Server(); ServerConnector connector = new ServerConnector(server); connector.setPort(port); @@ -103,7 +114,7 @@ public class DevServer { ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); context.setContextPath("/"); server.setHandler(context); - CodeServlet servlet = new CodeServlet(mainClass, classPath); + servlet = new CodeServlet(mainClass, classPath); servlet.setFileName(fileName); servlet.setPathToFile(pathToFile); servlet.setLog(log); @@ -111,6 +122,9 @@ public class DevServer { servlet.setIndicator(indicator); servlet.setAutomaticallyReloaded(reloadedAutomatically); servlet.setPort(port); + for (DevServerListener listener : listeners) { + servlet.addListener(listener); + } context.addServlet(new ServletHolder(servlet), "/*"); try { @@ -129,6 +143,8 @@ public class DevServer { } catch (Exception e) { throw new RuntimeException(e); } + server = null; + servlet = null; } private class DevServerEndpointConfig implements ServerEndpointConfig { diff --git a/tools/devserver/src/main/java/org/teavm/devserver/DevServerListener.java b/tools/devserver/src/main/java/org/teavm/devserver/DevServerListener.java new file mode 100644 index 000000000..6250a6a3a --- /dev/null +++ b/tools/devserver/src/main/java/org/teavm/devserver/DevServerListener.java @@ -0,0 +1,28 @@ +/* + * 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 org.teavm.tooling.builder.BuildResult; + +public interface DevServerListener { + void compilationStarted(); + + void compilationProgress(double progress); + + void compilationComplete(BuildResult result); + + void compilationCancelled(); +} diff --git a/tools/idea/idea-artifacts/dep-pom.xml b/tools/idea/idea-artifacts/dep-pom.xml index 7f01c8492..f564b4289 100644 --- a/tools/idea/idea-artifacts/dep-pom.xml +++ b/tools/idea/idea-artifacts/dep-pom.xml @@ -44,23 +44,21 @@ org.teavm teavm-tooling ${teavm.version} - - - org.slf4j - slf4j-api - - + + + org.teavm + teavm-devserver + ${teavm.version} + + + org.teavm + teavm-classlib + ${teavm.version} org.teavm teavm-chrome-rdp ${teavm.version} - - - org.slf4j - slf4j-api - - @@ -125,6 +123,36 @@ false dependencies/teavm.jar + + + com.google.gson + org.teavm.shade.gson + + + com.jcraft.jzlib + org.teavm.shade.jzlib + + + org.eclipse.jetty + org.teavm.shade.jetty + + + org.joda.time + org.teavm.shade.jodatime + + + org.objectweb.asm + org.teavm.shade.jetty.asm + + + org.slf4j + org.teavm.shade.slf4j + + + org.apache.commons + org.teavm.apachecommons + + diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/DaemonUtil.java b/tools/idea/plugin/src/main/java/org/teavm/idea/DaemonUtil.java index 78cc05ab7..d0d36e0a2 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/DaemonUtil.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/DaemonUtil.java @@ -24,13 +24,16 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.teavm.idea.devserver.DevServerRunner; import org.teavm.tooling.daemon.BuildDaemon; public final class DaemonUtil { private static final Set PLUGIN_FILES = new HashSet<>(Arrays.asList("teavm-jps-common.jar", "teavm-plugin.jar", "teavm.jar")); private static final String DAEMON_CLASS = BuildDaemon.class.getName().replace('.', '/') + ".class"; + private static final String DEV_SERVER_CLASS = DevServerRunner.class.getName().replace('.', '/') + ".class"; private static final int DAEMON_CLASS_DEPTH; + private static final int DEV_SERVER_CLASS_DEPTH; static { int depth = 0; @@ -40,6 +43,14 @@ public final class DaemonUtil { } } DAEMON_CLASS_DEPTH = depth; + + depth = 0; + for (int i = 0; i < DEV_SERVER_CLASS.length(); ++i) { + if (DEV_SERVER_CLASS.charAt(i) == '/') { + depth++; + } + } + DEV_SERVER_CLASS_DEPTH = depth; } private DaemonUtil() { @@ -64,6 +75,11 @@ public final class DaemonUtil { file = file.getParentFile(); } targetFiles.add(file.getAbsolutePath()); + } else if (file.getPath().endsWith(DEV_SERVER_CLASS)) { + for (int i = 0; i <= DEV_SERVER_CLASS_DEPTH; ++i) { + file = file.getParentFile(); + } + targetFiles.add(file.getAbsolutePath()); } else if (file.isDirectory()) { for (File childFile : file.listFiles()) { findInHierarchy(childFile, targetFiles, visited); diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/DevServerRunnerListener.java b/tools/idea/plugin/src/main/java/org/teavm/idea/DevServerRunnerListener.java new file mode 100644 index 000000000..94b31fc1e --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/DevServerRunnerListener.java @@ -0,0 +1,24 @@ +/* + * 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.idea; + +public interface DevServerRunnerListener { + void error(String text); + + void info(String text); + + void stopped(int code); +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/debug/TeaVMDebugConfiguration.java b/tools/idea/plugin/src/main/java/org/teavm/idea/debug/TeaVMDebugConfiguration.java index 676e5e545..fe5544202 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/debug/TeaVMDebugConfiguration.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/debug/TeaVMDebugConfiguration.java @@ -15,7 +15,6 @@ */ package org.teavm.idea.debug; -import com.intellij.execution.ExecutionException; import com.intellij.execution.Executor; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.LocatableConfigurationBase; @@ -45,8 +44,7 @@ public class TeaVMDebugConfiguration extends LocatableConfigurationBase @Nullable @Override - public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws - ExecutionException { + public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { return new TeaVMRunState(environment, port); } diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerBuildResult.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerBuildResult.java new file mode 100644 index 000000000..5989d58e3 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerBuildResult.java @@ -0,0 +1,27 @@ +/* + * 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.idea.devserver; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.teavm.callgraph.CallGraph; +import org.teavm.diagnostics.Problem; + +public class DevServerBuildResult implements Serializable { + public CallGraph callGraph; + public final List problems = new ArrayList<>(); +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerConfiguration.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerConfiguration.java new file mode 100644 index 000000000..656ce43ff --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerConfiguration.java @@ -0,0 +1,29 @@ +/* + * 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.idea.devserver; + +public class DevServerConfiguration { + public String javaHome; + public int maxHeap; + public String mainClass; + public String[] classPath; + public String[] sourcePath; + public boolean indicator; + public boolean autoReload; + public int port; + public String pathToFile; + public String fileName; +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerInfo.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerInfo.java new file mode 100644 index 000000000..d0cc2d6c7 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerInfo.java @@ -0,0 +1,28 @@ +/* + * 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.idea.devserver; + +public class DevServerInfo { + public final int port; + public final DevServerManager server; + public final Process process; + + DevServerInfo(int port, DevServerManager server, Process process) { + this.port = port; + this.server = server; + this.process = process; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManager.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManager.java new file mode 100644 index 000000000..6341981dd --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManager.java @@ -0,0 +1,33 @@ +/* + * 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.idea.devserver; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface DevServerManager extends Remote { + String ID = "TeaVM-BuildServer"; + + void stop() throws RemoteException; + + void invalidateCache() throws RemoteException; + + void buildProject() throws RemoteException; + + void cancelBuild() throws RemoteException; + + void addListener(DevServerManagerListener listener) throws RemoteException; +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManagerListener.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManagerListener.java new file mode 100644 index 000000000..019345b6c --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerManagerListener.java @@ -0,0 +1,29 @@ +/* + * 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.idea.devserver; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface DevServerManagerListener extends Remote { + void compilationStarted() throws RemoteException; + + void compilationProgress(double progress) throws RemoteException; + + void compilationComplete(DevServerBuildResult result) throws RemoteException; + + void compilationCancelled() throws RemoteException; +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerRunner.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerRunner.java new file mode 100644 index 000000000..ac0ffff1e --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/DevServerRunner.java @@ -0,0 +1,308 @@ +/* + * 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.idea.devserver; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.rmi.AlreadyBoundException; +import java.rmi.NoSuchObjectException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; +import org.teavm.devserver.DevServer; +import org.teavm.devserver.DevServerListener; +import org.teavm.idea.DevServerRunnerListener; +import org.teavm.tooling.ConsoleTeaVMToolLog; +import org.teavm.tooling.builder.BuildResult; + +public class DevServerRunner extends UnicastRemoteObject implements DevServerManager { + private static final int MIN_PORT = 10000; + private static final int MAX_PORT = 1 << 16; + private static final String PORT_MESSAGE_PREFIX = "Build server port: "; + private static final String DEBUG_PORT_PROPERTY = "teavm.server.debug.port"; + private int port; + private Registry registry; + private DevServer server; + private List listeners = new ArrayList<>(); + + private DevServerRunner(DevServer server) throws RemoteException { + super(); + Random random = new Random(); + for (int i = 0; i < 20; ++i) { + port = random.nextInt(MAX_PORT - MIN_PORT) + MIN_PORT; + try { + registry = LocateRegistry.createRegistry(port); + } catch (RemoteException e) { + continue; + } + try { + registry.bind(ID, this); + } catch (RemoteException | AlreadyBoundException e) { + throw new IllegalStateException("Could not bind remote service", e); + } + + this.server = server; + server.addListener(devServerListener); + + return; + } + throw new IllegalStateException("Could not create RMI registry"); + } + + @Override + public void stop() { + server.stop(); + } + + @Override + public void invalidateCache() { + server.invalidateCache(); + } + + @Override + public void buildProject() { + server.buildProject(); + } + + @Override + public void cancelBuild() { + server.cancelBuild(); + } + + @Override + public void addListener(DevServerManagerListener listener) { + listeners.add(listener); + } + + public static void main(String[] args) throws Exception { + DevServer server = new DevServer(); + server.setLog(new ConsoleTeaVMToolLog(true)); + server.setMainClass(args[0]); + List classPath = new ArrayList<>(); + for (int i = 1; i < args.length; ++i) { + switch (args[i]) { + case "-c": + classPath.add(args[++i]); + break; + case "-s": + server.getSourcePath().add(args[++i]); + break; + case "-i": + server.setIndicator(true); + break; + case "-a": + server.setReloadedAutomatically(true); + break; + case "-p": + server.setPort(Integer.parseInt(args[++i])); + break; + case "-d": + server.setPathToFile(args[++i]); + break; + case "-f": + server.setFileName(args[++i]); + break; + } + } + server.setClassPath(classPath.toArray(new String[0])); + + DevServerRunner daemon = new DevServerRunner(server); + System.out.println(PORT_MESSAGE_PREFIX + daemon.port); + server.start(); + + try { + daemon.registry.unbind(ID); + UnicastRemoteObject.unexportObject(daemon, true); + } catch (NoSuchObjectException e) { + throw new IllegalStateException("Could not shutdown RMI registry", e); + } + } + + public static DevServerInfo start(String[] classPathEntries, DevServerConfiguration options, + DevServerRunnerListener listener) throws IOException { + String javaCommand = options.javaHome + "/bin/java"; + String classPath = String.join(File.pathSeparator, classPathEntries); + List arguments = new ArrayList<>(); + + arguments.addAll(Arrays.asList(javaCommand, "-cp", classPath, "-Xmx" + options.maxHeap + "m")); + + String debugPort = System.getProperty(DEBUG_PORT_PROPERTY); + if (debugPort != null) { + arguments.add("-agentlib:jdwp=transport=dt_socket,quiet=y,server=y,address=" + debugPort + ",suspend=y"); + } + + arguments.add(DevServerRunner.class.getName()); + arguments.add(options.mainClass); + + if (options.indicator) { + arguments.add("-i"); + } + if (options.autoReload) { + arguments.add("-a"); + } + arguments.add("-d"); + arguments.add(options.pathToFile); + arguments.add("-f"); + arguments.add(options.fileName); + arguments.add("-p"); + arguments.add(Integer.toString(options.port)); + + for (String entry : options.classPath) { + arguments.add("-c"); + arguments.add(entry); + } + for (String entry : options.sourcePath) { + arguments.add("-s"); + arguments.add(entry); + } + + ProcessBuilder builder = new ProcessBuilder(arguments.toArray(new String[0])); + Process process = builder.start(); + BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream(), + StandardCharsets.UTF_8)); + BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), + StandardCharsets.UTF_8)); + String line = stdoutReader.readLine(); + if (line == null || !line.startsWith(PORT_MESSAGE_PREFIX)) { + StringBuilder sb = new StringBuilder(); + while (true) { + line = stderrReader.readLine(); + if (line == null) { + break; + } + sb.append(line).append('\n'); + } + stderrReader.close(); + stdoutReader.close(); + process.destroy(); + throw new IllegalStateException("Could not start daemon. Stderr: " + sb); + } + int port = Integer.parseInt(line.substring(PORT_MESSAGE_PREFIX.length())); + + daemonThread("TeaVM devserver stdout", new ProcessOutputWatcher(stdoutReader, listener::info)).start(); + daemonThread("TeaVM devserver stderr", new ProcessOutputWatcher(stderrReader, listener::error)).start(); + daemonThread("TeaVM devserver monitor", () -> { + int exitCode; + try { + exitCode = process.waitFor(); + } catch (InterruptedException e) { + return; + } + listener.stopped(exitCode); + }).start(); + + DevServerManager service; + try { + Registry registry = LocateRegistry.getRegistry(port); + service = (DevServerManager) registry.lookup(ID); + } catch (RemoteException | NotBoundException e) { + throw new RuntimeException("Error connecting TeaVM process", e); + } + + return new DevServerInfo(port, service, process); + } + + private static Thread daemonThread(String name, Runnable runnable) { + Thread thread = new Thread(runnable); + thread.setName(name); + thread.setDaemon(true); + return thread; + } + + static class ProcessOutputWatcher implements Runnable { + private BufferedReader reader; + private Consumer consumer; + + ProcessOutputWatcher(BufferedReader reader, Consumer consumer) { + this.reader = reader; + this.consumer = consumer; + } + + @Override + public void run() { + try { + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + consumer.accept(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + final DevServerListener devServerListener = new DevServerListener() { + @Override + public void compilationStarted() { + for (DevServerManagerListener listener : listeners) { + try { + listener.compilationStarted(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + @Override + public void compilationProgress(double v) { + for (DevServerManagerListener listener : listeners) { + try { + listener.compilationProgress(v); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + @Override + public void compilationComplete(BuildResult buildResult) { + DevServerBuildResult result = new DevServerBuildResult(); + result.callGraph = buildResult.getCallGraph(); + result.problems.addAll(buildResult.getProblems().getProblems()); + for (DevServerManagerListener listener : listeners) { + try { + listener.compilationComplete(result); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + @Override + public void compilationCancelled() { + for (DevServerManagerListener listener : listeners) { + try { + listener.compilationCancelled(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + }; +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfiguration.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfiguration.java new file mode 100644 index 000000000..7e505d2eb --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfiguration.java @@ -0,0 +1,187 @@ +/* + * 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.idea.devserver; + +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.ModuleBasedConfiguration; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunConfigurationModule; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.facet.FacetManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.util.xmlb.XmlSerializer; +import com.intellij.util.xmlb.annotations.Property; +import com.intellij.util.xmlb.annotations.Tag; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.teavm.idea.TeaVMFacetType; +import org.teavm.idea.devserver.ui.TeaVMDevServerSettingsEditor; + +public class TeaVMDevServerConfiguration + extends ModuleBasedConfiguration { + private String mainClass = ""; + private String jdkPath; + private int port = 9090; + private String pathToFile = ""; + private String fileName = "classes.js"; + private boolean indicator = true; + private boolean automaticallyReloaded; + private int maxHeap = 1024; + + public TeaVMDevServerConfiguration( + @NotNull RunConfigurationModule configurationModule, + @NotNull ConfigurationFactory factory) { + super("TeaVM dev server", configurationModule, factory); + } + + @Override + public Collection getValidModules() { + Module[] modules = ModuleManager.getInstance(getProject()).getModules(); + List validModules = new ArrayList<>(); + for (Module module : modules) { + FacetManager facetManager = FacetManager.getInstance(module); + if (facetManager.getFacetByType(TeaVMFacetType.TYPE_ID) != null) { + validModules.add(module); + } + } + return validModules; + } + + @Override + public void readExternal(@NotNull Element element) throws InvalidDataException { + super.readExternal(element); + + Element child = element.getChild("teavm"); + if (child != null) { + XmlSerializer.deserializeInto(this, child); + } + } + + @Override + public void writeExternal(@NotNull Element element) throws WriteExternalException { + super.writeExternal(element); + + Element child = element.getChild("teavm"); + if (child == null) { + child = new Element("teavm"); + element.addContent(child); + } + XmlSerializer.serializeInto(this, child, (accessor, bean) -> + !accessor.getName().equals("isAllowRunningInParallel")); + } + + @NotNull + @Override + public SettingsEditor getConfigurationEditor() { + return new TeaVMDevServerSettingsEditor(getProject()); + } + + @Nullable + @Override + public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { + return new TeaVMDevServerRunState(environment, this); + } + + @Property + @Tag + public String getMainClass() { + return mainClass; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + @Property + @Tag + public String getJdkPath() { + return jdkPath; + } + + public void setJdkPath(String jdkPath) { + this.jdkPath = jdkPath; + } + + @Property + @Tag + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Property + @Tag + public String getPathToFile() { + return pathToFile; + } + + public void setPathToFile(String pathToFile) { + this.pathToFile = pathToFile; + } + + @Property + @Tag + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + @Property + @Tag + public boolean isIndicator() { + return indicator; + } + + public void setIndicator(boolean indicator) { + this.indicator = indicator; + } + + @Property + @Tag + public boolean isAutomaticallyReloaded() { + return automaticallyReloaded; + } + + public void setAutomaticallyReloaded(boolean automaticallyReloaded) { + this.automaticallyReloaded = automaticallyReloaded; + } + + @Property + @Tag + public int getMaxHeap() { + return maxHeap; + } + + public void setMaxHeap(int maxHeap) { + this.maxHeap = maxHeap; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfigurationType.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfigurationType.java new file mode 100644 index 000000000..07ef1167c --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerConfigurationType.java @@ -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.idea.devserver; + +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.ConfigurationTypeBase; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunConfigurationModule; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.IconLoader; +import org.jetbrains.annotations.NotNull; + +public class TeaVMDevServerConfigurationType extends ConfigurationTypeBase { + public TeaVMDevServerConfigurationType() { + super("TeaVMDevServerConfiguration", "TeaVM development server", "TeaVM development server" + + "agent", IconLoader.getIcon("/teavm-16.png")); + } + + private final ConfigurationFactory factory = new ConfigurationFactory(this) { + @NotNull + @Override + public RunConfiguration createTemplateConfiguration(@NotNull Project project) { + return new TeaVMDevServerConfiguration(new RunConfigurationModule(project), this); + } + }; + + @Override + public ConfigurationFactory[] getConfigurationFactories() { + return new ConfigurationFactory[] { factory }; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunState.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunState.java new file mode 100644 index 000000000..3c7ff10b9 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunState.java @@ -0,0 +1,162 @@ +/* + * 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.idea.devserver; + +import com.intellij.execution.DefaultExecutionResult; +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.SearchScopeProvider; +import com.intellij.execution.filters.TextConsoleBuilder; +import com.intellij.execution.filters.TextConsoleBuilderFactory; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.execution.util.JavaParametersUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.OrderEnumerator; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.GlobalSearchScope; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.teavm.idea.DaemonUtil; +import org.teavm.idea.DevServerRunnerListener; + +public class TeaVMDevServerRunState implements RunProfileState { + private final TeaVMDevServerConfiguration configuration; + private final TextConsoleBuilder consoleBuilder; + + public TeaVMDevServerRunState(@NotNull ExecutionEnvironment environment, + @NotNull TeaVMDevServerConfiguration configuration) { + this.configuration = configuration; + + Project project = environment.getProject(); + GlobalSearchScope searchScope = SearchScopeProvider.createSearchScope(project, environment.getRunProfile()); + consoleBuilder = TextConsoleBuilderFactory.getInstance().createBuilder(project, searchScope); + } + + @Nullable + @Override + public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { + DevServerConfiguration config = new DevServerConfiguration(); + Module module = configuration.getConfigurationModule().getModule(); + + Sdk moduleSdk = JavaParametersUtil.createModuleJdk(module, true, configuration.getJdkPath()); + config.javaHome = moduleSdk.getHomePath(); + OrderEnumerator enumerator = OrderEnumerator.orderEntries(module).withoutSdk().recursively(); + config.classPath = Arrays.stream(enumerator.getClassesRoots()) + .map(this::path) + .filter(Objects::nonNull) + .toArray(String[]::new); + config.sourcePath = Arrays.stream(enumerator.getSourceRoots()) + .map(this::path) + .filter(Objects::nonNull) + .toArray(String[]::new); + config.pathToFile = configuration.getPathToFile(); + config.fileName = configuration.getFileName(); + config.port = configuration.getPort(); + config.indicator = configuration.isIndicator(); + config.autoReload = configuration.isAutomaticallyReloaded(); + config.mainClass = configuration.getMainClass(); + config.maxHeap = configuration.getMaxHeap(); + + try { + ConsoleView console = consoleBuilder.getConsole(); + ProcessHandlerImpl processHandler = new ProcessHandlerImpl(config, console); + console.attachToProcess(processHandler); + processHandler.start(); + return new DefaultExecutionResult(console, processHandler); + } catch (IOException e) { + throw new ExecutionException(e); + } + } + + private String path(VirtualFile file) { + while (file.getFileSystem() instanceof JarFileSystem) { + file = ((JarFileSystem) file.getFileSystem()).getLocalByEntry(file); + if (file == null) { + return null; + } + } + return file.getCanonicalPath(); + } + + class ProcessHandlerImpl extends ProcessHandler implements DevServerRunnerListener { + private DevServerConfiguration config; + private ConsoleView console; + private DevServerInfo info; + + ProcessHandlerImpl(DevServerConfiguration config, ConsoleView console) { + this.config = config; + this.console = console; + } + + void start() throws IOException { + info = DevServerRunner.start(DaemonUtil.detectClassPath().toArray(new String[0]), config, this); + } + + @Override + protected void destroyProcessImpl() { + info.process.destroy(); + } + + @Override + protected void detachProcessImpl() { + try { + info.server.stop(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public boolean detachIsDefault() { + return true; + } + + @Nullable + @Override + public OutputStream getProcessInput() { + return null; + } + + + @Override + public void error(String text) { + console.print(text + System.lineSeparator(), ConsoleViewContentType.ERROR_OUTPUT); + } + + @Override + public void info(String text) { + console.print(text + System.lineSeparator(), ConsoleViewContentType.NORMAL_OUTPUT); + } + + @Override + public void stopped(int code) { + notifyProcessTerminated(code); + } + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunner.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunner.java new file mode 100644 index 000000000..588f8e554 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/TeaVMDevServerRunner.java @@ -0,0 +1,52 @@ +/* + * 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.idea.devserver; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.ExecutionResult; +import com.intellij.execution.configurations.RunProfile; +import com.intellij.execution.configurations.RunProfileState; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.GenericProgramRunner; +import com.intellij.execution.ui.RunContentDescriptor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class TeaVMDevServerRunner extends GenericProgramRunner { + @NotNull + @Override + public String getRunnerId() { + return "TeaVMDevServerRunner"; + } + + @Override + public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) { + return profile instanceof TeaVMDevServerConfiguration; + } + + @Nullable + @Override + protected RunContentDescriptor doExecute(@NotNull RunProfileState state, + @NotNull ExecutionEnvironment environment) throws ExecutionException { + ExecutionResult executionResult = state.execute(environment.getExecutor(), environment.getRunner()); + if (executionResult == null) { + return null; + } + + return null; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsEditor.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsEditor.java new file mode 100644 index 000000000..efca85d75 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsEditor.java @@ -0,0 +1,66 @@ +/* + * 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.idea.devserver.ui; + +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import javax.swing.JComponent; +import org.jetbrains.annotations.NotNull; +import org.teavm.idea.devserver.TeaVMDevServerConfiguration; + +public class TeaVMDevServerSettingsEditor extends SettingsEditor { + private final Project project; + private TeaVMDevServerSettingsPanel panel; + + public TeaVMDevServerSettingsEditor(Project project) { + this.project = project; + } + + @Override + protected void resetEditorFrom(@NotNull TeaVMDevServerConfiguration s) { + if (panel == null) { + return; + } + + panel.load(s); + } + + @Override + protected void applyEditorTo(@NotNull TeaVMDevServerConfiguration s) { + if (panel == null) { + return; + } + + panel.save(s); + } + + @Override + protected void disposeEditor() { + if (panel != null) { + panel = null; + } + super.disposeEditor(); + } + + @NotNull + @Override + protected JComponent createEditor() { + if (panel == null) { + panel = new TeaVMDevServerSettingsPanel(project); + } + return panel; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsPanel.java b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsPanel.java new file mode 100644 index 000000000..1369cee88 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/devserver/ui/TeaVMDevServerSettingsPanel.java @@ -0,0 +1,145 @@ +/* + * 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.idea.devserver.ui; + +import com.intellij.application.options.ModuleDescriptionsComboBox; +import com.intellij.execution.configurations.ConfigurationUtil; +import com.intellij.execution.ui.ConfigurationModuleSelector; +import com.intellij.execution.ui.DefaultJreSelector; +import com.intellij.execution.ui.JrePathEditor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaCodeFragment; +import com.intellij.psi.PsiClass; +import com.intellij.psi.util.PsiMethodUtil; +import com.intellij.ui.EditorTextFieldWithBrowseButton; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.text.DecimalFormat; +import javax.swing.JCheckBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.border.EmptyBorder; +import org.teavm.idea.devserver.TeaVMDevServerConfiguration; + +public class TeaVMDevServerSettingsPanel extends JPanel { + private final JrePathEditor jrePathEditor; + + private final ModuleDescriptionsComboBox moduleField; + private final ConfigurationModuleSelector moduleSelector; + + private EditorTextFieldWithBrowseButton mainClassField; + + private JFormattedTextField portField; + private JTextField pathToFileField; + private JTextField fileNameField; + private JCheckBox indicatorField; + private JCheckBox autoReloadField; + private JFormattedTextField maxHeapField; + + public TeaVMDevServerSettingsPanel(Project project) { + moduleField = new ModuleDescriptionsComboBox(); + moduleSelector = new ConfigurationModuleSelector(project, moduleField); + + JavaCodeFragment.VisibilityChecker visibilityChecker = (declaration, place) -> { + if (declaration instanceof PsiClass) { + PsiClass cls = (PsiClass) declaration; + if (ConfigurationUtil.MAIN_CLASS.value(cls) && PsiMethodUtil.findMainMethod(cls) != null + || place.getParent() != null && moduleSelector.findClass(cls.getQualifiedName()) != null) { + return JavaCodeFragment.VisibilityChecker.Visibility.VISIBLE; + } + } + return JavaCodeFragment.VisibilityChecker.Visibility.NOT_VISIBLE; + }; + mainClassField = new EditorTextFieldWithBrowseButton(project, true, visibilityChecker); + mainClassField.setButtonEnabled(true); + + jrePathEditor = new JrePathEditor(DefaultJreSelector.fromSourceRootsDependencies(moduleField, mainClassField)); + + portField = new JFormattedTextField(new DecimalFormat("#0")); + fileNameField = new JTextField(); + pathToFileField = new JTextField(); + indicatorField = new JCheckBox("Display indicator on a web page:"); + autoReloadField = new JCheckBox("Reload page automatically:"); + maxHeapField = new JFormattedTextField(new DecimalFormat("#0")); + + initLayout(); + } + + private void initLayout() { + setLayout(new GridBagLayout()); + setBorder(new EmptyBorder(10, 10, 10, 10)); + + GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.insets.right = 5; + labelConstraints.anchor = GridBagConstraints.LINE_START; + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.weightx = 1; + constraints.insets.top = 4; + constraints.insets.bottom = 4; + + add(new JLabel("Main class:"), labelConstraints); + add(mainClassField, constraints); + + add(new JLabel("Use classpath of module:"), labelConstraints); + add(moduleField, constraints); + + add(jrePathEditor, constraints); + + add(new JLabel("Port:"), labelConstraints); + add(portField, constraints); + + add(new JLabel("File name:"), labelConstraints); + add(fileNameField, constraints); + + add(new JLabel("Path to file:"), labelConstraints); + add(pathToFileField, constraints); + + add(indicatorField, constraints); + add(autoReloadField, constraints); + + add(new JLabel("Server heap limit:"), labelConstraints); + add(maxHeapField, constraints); + } + + public void load(TeaVMDevServerConfiguration configuration) { + mainClassField.setText(configuration.getMainClass()); + moduleSelector.reset(configuration); + jrePathEditor.setPathOrName(configuration.getJdkPath(), false); + fileNameField.setText(configuration.getFileName()); + pathToFileField.setText(configuration.getPathToFile()); + indicatorField.setSelected(configuration.isIndicator()); + autoReloadField.setSelected(configuration.isAutomaticallyReloaded()); + maxHeapField.setText(Integer.toString(configuration.getMaxHeap())); + portField.setText(Integer.toString(configuration.getPort())); + } + + public void save(TeaVMDevServerConfiguration configuration) { + configuration.setMainClass(mainClassField.getText()); + moduleSelector.applyTo(configuration); + configuration.setJdkPath(jrePathEditor.getJrePathOrName()); + configuration.setFileName(fileNameField.getText()); + configuration.setPathToFile(pathToFileField.getText()); + configuration.setIndicator(indicatorField.isSelected()); + configuration.setAutomaticallyReloaded(autoReloadField.isSelected()); + configuration.setMaxHeap(Integer.parseInt(maxHeapField.getText())); + configuration.setPort(Integer.parseInt(portField.getText())); + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/maven/TeaVMMavenImporter.java b/tools/idea/plugin/src/main/java/org/teavm/idea/maven/TeaVMMavenImporter.java index ca3d8ddf7..486b564d1 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/maven/TeaVMMavenImporter.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/maven/TeaVMMavenImporter.java @@ -110,6 +110,9 @@ public class TeaVMMavenImporter extends MavenImporter { } TeaVMJpsConfiguration configuration = facet.getConfiguration().getState(); + if (justCreated) { + configuration.setSkipped(true); + } for (Element child : source.getChildren()) { switch (child.getName()) { diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMConfigurable.java b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMConfigurable.java index d9b78e9d7..80f1a4277 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMConfigurable.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMConfigurable.java @@ -17,7 +17,6 @@ package org.teavm.idea.ui; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.Configurable; -import com.intellij.openapi.options.ConfigurationException; import javax.swing.JComponent; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; @@ -60,7 +59,7 @@ public class TeaVMConfigurable implements Configurable { } @Override - public void apply() throws ConfigurationException { + public void apply() { panel.save(configuration); } diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMFacetEditorTab.java b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMFacetEditorTab.java index 3a8a76b6f..1ce97f712 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMFacetEditorTab.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMFacetEditorTab.java @@ -17,7 +17,6 @@ package org.teavm.idea.ui; import com.intellij.facet.ui.FacetEditorTab; import com.intellij.openapi.module.Module; -import com.intellij.openapi.options.ConfigurationException; import javax.swing.JComponent; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -48,7 +47,7 @@ public class TeaVMFacetEditorTab extends FacetEditorTab { } @Override - public void apply() throws ConfigurationException { + public void apply() { configurable.apply(); } diff --git a/tools/idea/plugin/src/main/resources/META-INF/plugin.xml b/tools/idea/plugin/src/main/resources/META-INF/plugin.xml index 946e6c838..8cb8f7c95 100644 --- a/tools/idea/plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/idea/plugin/src/main/resources/META-INF/plugin.xml @@ -36,6 +36,9 @@ + + +