From 1ae4a95128b445325c87481acd8f5de823ad5e96 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 15 Mar 2017 22:42:35 +0300 Subject: [PATCH] Add TeaVM build daemon to IDEA --- .../model/TeaVMJpsWorkspaceConfiguration.java | 44 ++++ .../jps/remote/TeaVMRemoteBuildCallback.java | 27 ++ .../jps/remote/TeaVMRemoteBuildRequest.java | 33 +++ .../jps/remote/TeaVMRemoteBuildResponse.java | 33 +++ .../jps/remote/TeaVMRemoteBuildService.java | 27 ++ .../teavm/idea/jps/RemoteBuildStrategy.java | 188 ++++++++++++++ .../java/org/teavm/idea/jps/TeaVMBuilder.java | 20 +- .../org/teavm/idea/TeaVMDaemonComponent.java | 91 +++++++ .../org/teavm/idea/TeaVMJPSConfigurator.java | 14 +- ...> TeaVMWorkspaceConfigurationStorage.java} | 16 +- .../teavm/idea/daemon/TeaVMBuildDaemon.java | 242 ++++++++++++++++++ .../teavm/idea/daemon/TeaVMDaemonInfo.java | 34 +++ .../teavm/idea/ui/TeaVMSettingsEditorTab.java | 95 +++++++ .../src/main/resources/META-INF/plugin.xml | 15 +- tools/idea/plugin/teavm-idea.iml | 4 +- 15 files changed, 865 insertions(+), 18 deletions(-) create mode 100644 tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMJpsWorkspaceConfiguration.java create mode 100644 tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildCallback.java create mode 100644 tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildRequest.java create mode 100644 tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildResponse.java create mode 100644 tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildService.java create mode 100644 tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/RemoteBuildStrategy.java create mode 100644 tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java rename tools/idea/plugin/src/main/java/org/teavm/idea/{TeaVMConfigurationStorage.java => TeaVMWorkspaceConfigurationStorage.java} (62%) create mode 100644 tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMBuildDaemon.java create mode 100644 tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMDaemonInfo.java create mode 100644 tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMSettingsEditorTab.java diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMJpsWorkspaceConfiguration.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMJpsWorkspaceConfiguration.java new file mode 100644 index 000000000..d6e57cc1f --- /dev/null +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMJpsWorkspaceConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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.jps.model; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.ex.JpsElementBase; + +public class TeaVMJpsWorkspaceConfiguration extends JpsElementBase { + private boolean daemonEnabled; + + public boolean isDaemonEnabled() { + return daemonEnabled; + } + + public void setDaemonEnabled(boolean daemonEnabled) { + this.daemonEnabled = daemonEnabled; + } + + @NotNull + @Override + public TeaVMJpsWorkspaceConfiguration createCopy() { + TeaVMJpsWorkspaceConfiguration copy = new TeaVMJpsWorkspaceConfiguration(); + copy.applyChanges(this); + return copy; + } + + @Override + public void applyChanges(@NotNull TeaVMJpsWorkspaceConfiguration configuration) { + daemonEnabled = configuration.daemonEnabled; + } +} diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildCallback.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildCallback.java new file mode 100644 index 000000000..9a19b224c --- /dev/null +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildCallback.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 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.jps.remote; + +import java.rmi.Remote; +import java.rmi.RemoteException; +import org.teavm.vm.TeaVMPhase; +import org.teavm.vm.TeaVMProgressFeedback; + +public interface TeaVMRemoteBuildCallback extends Remote { + TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) throws RemoteException; + + TeaVMProgressFeedback progressReached(int progress) throws RemoteException; +} diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildRequest.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildRequest.java new file mode 100644 index 000000000..6eefb4e4c --- /dev/null +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 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.jps.remote; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import org.teavm.tooling.TeaVMTargetType; + +public class TeaVMRemoteBuildRequest implements Serializable { + public final List sourceDirectories = new ArrayList<>(); + public final List sourceJarFiles = new ArrayList<>(); + public final List classPath = new ArrayList<>(); + public TeaVMTargetType targetType; + public String mainClass; + public String targetDirectory; + public boolean sourceMapsFileGenerated; + public boolean debugInformationGenerated; + public boolean sourceFilesCopied; +} diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildResponse.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildResponse.java new file mode 100644 index 000000000..5cad11c0c --- /dev/null +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildResponse.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 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.jps.remote; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.teavm.callgraph.CallGraph; +import org.teavm.diagnostics.Problem; + +public class TeaVMRemoteBuildResponse implements Serializable { + public CallGraph callGraph; + public boolean errorOccurred; + public final List problems = new ArrayList<>(); + public final List severeProblems = new ArrayList<>(); + public final Set usedResources = new HashSet<>(); + public final Set classes = new HashSet<>(); +} diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildService.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildService.java new file mode 100644 index 000000000..951991ca3 --- /dev/null +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/remote/TeaVMRemoteBuildService.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 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.jps.remote; + +import java.rmi.Remote; +import java.rmi.RemoteException; + +public interface TeaVMRemoteBuildService extends Remote { + String REMOTE_PORT = "teavm.daemon.remote-port"; + String ID = "TeaVM-Daemon"; + + TeaVMRemoteBuildResponse build(TeaVMRemoteBuildRequest request, TeaVMRemoteBuildCallback callback) + throws RemoteException; +} diff --git a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/RemoteBuildStrategy.java b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/RemoteBuildStrategy.java new file mode 100644 index 000000000..012df3976 --- /dev/null +++ b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/RemoteBuildStrategy.java @@ -0,0 +1,188 @@ +/* + * Copyright 2017 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.jps; + +import java.rmi.RemoteException; +import java.rmi.server.UnicastRemoteObject; +import java.util.Collection; +import java.util.List; +import org.jetbrains.jps.incremental.CompileContext; +import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.teavm.callgraph.CallGraph; +import org.teavm.diagnostics.Problem; +import org.teavm.diagnostics.ProblemProvider; +import org.teavm.idea.jps.model.TeaVMBuildResult; +import org.teavm.idea.jps.model.TeaVMBuildStrategy; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildCallback; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildRequest; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildResponse; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildService; +import org.teavm.tooling.TeaVMTargetType; +import org.teavm.vm.TeaVMPhase; +import org.teavm.vm.TeaVMProgressFeedback; + +public class RemoteBuildStrategy implements TeaVMBuildStrategy { + private final CompileContext context; + private TeaVMRemoteBuildRequest request; + private TeaVMRemoteBuildService buildService; + + public RemoteBuildStrategy(CompileContext context, TeaVMRemoteBuildService buildService) { + this.context = context; + this.buildService = buildService; + } + + @Override + public void init() { + request = new TeaVMRemoteBuildRequest(); + } + + @Override + public void addSourcesDirectory(String directory) { + request.sourceDirectories.add(directory); + } + + @Override + public void addSourcesJar(String jarFile) { + request.sourceJarFiles.add(jarFile); + } + + @Override + public void setClassPathEntries(List entries) { + request.classPath.addAll(entries); + } + + @Override + public void setTargetType(TeaVMTargetType targetType) { + request.targetType = targetType; + } + + @Override + public void setMainClass(String mainClass) { + request.mainClass = mainClass; + } + + @Override + public void setTargetDirectory(String targetDirectory) { + request.targetDirectory = targetDirectory; + } + + @Override + public void setSourceMapsFileGenerated(boolean sourceMapsFileGenerated) { + request.sourceMapsFileGenerated = sourceMapsFileGenerated; + } + + @Override + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + request.debugInformationGenerated = debugInformationGenerated; + } + + @Override + public void setSourceFilesCopied(boolean sourceFilesCopied) { + request.sourceFilesCopied = sourceFilesCopied; + } + + @Override + public TeaVMBuildResult build() { + TeaVMRemoteBuildResponse response; + try { + response = buildService.build(request, new CallbackImpl(context)); + } catch (Throwable e) { + throw new RuntimeException(e); + } + return new TeaVMBuildResult() { + private ProblemProvider problems = new ProblemProvider() { + @Override + public List getProblems() { + return response.problems; + } + + @Override + public List getSevereProblems() { + return response.severeProblems; + } + }; + + @Override + public CallGraph getCallGraph() { + return response.callGraph; + } + + @Override + public boolean isErrorOccurred() { + return response.errorOccurred; + } + + @Override + public ProblemProvider getProblems() { + return problems; + } + + @Override + public Collection getUsedResources() { + return response.usedResources; + } + + @Override + public Collection getClasses() { + return response.classes; + } + }; + } + + static class CallbackImpl extends UnicastRemoteObject implements TeaVMRemoteBuildCallback { + private CompileContext context; + int expectedCount; + TeaVMPhase currentPhase; + + public CallbackImpl(CompileContext context) throws RemoteException { + super(); + this.context = context; + } + + @Override + public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) throws RemoteException { + expectedCount = count; + context.processMessage(new ProgressMessage(phaseName(phase), 0)); + currentPhase = phase; + return context.getCancelStatus().isCanceled() ? TeaVMProgressFeedback.CANCEL + : TeaVMProgressFeedback.CONTINUE; + } + + @Override + public TeaVMProgressFeedback progressReached(int progress) throws RemoteException { + context.processMessage(new ProgressMessage(phaseName(currentPhase), (float) progress / expectedCount)); + return context.getCancelStatus().isCanceled() ? TeaVMProgressFeedback.CANCEL + : TeaVMProgressFeedback.CONTINUE; + } + } + + private static String phaseName(TeaVMPhase phase) { + switch (phase) { + case DEPENDENCY_CHECKING: + return "Discovering classes to compile"; + case LINKING: + return "Resolving method invocations"; + case DECOMPILATION: + return "Compiling classes"; + case OPTIMIZATION: + return "Optimizing code"; + case RENDERING: + return "Building JS file"; + default: + throw new AssertionError(); + } + } +} diff --git a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuilder.java b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuilder.java index ffead272e..0d5499cc1 100644 --- a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuilder.java +++ b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuilder.java @@ -33,10 +33,13 @@ import org.jetbrains.jps.incremental.ProjectBuildException; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import org.jetbrains.jps.model.module.JpsModule; +import org.teavm.idea.jps.model.TeaVMBuildStrategy; import org.teavm.idea.jps.remote.TeaVMBuilderAssistant; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildService; public class TeaVMBuilder extends ModuleLevelBuilder { - private static TeaVMBuilderAssistant assistant; + private TeaVMBuilderAssistant assistant; + private TeaVMRemoteBuildService buildService; public TeaVMBuilder() { super(BuilderCategory.CLASS_POST_PROCESSOR); @@ -50,6 +53,16 @@ public class TeaVMBuilder extends ModuleLevelBuilder { e.printStackTrace(); } } + + String daemonPortString = System.getProperty(TeaVMRemoteBuildService.REMOTE_PORT); + if (daemonPortString != null) { + try { + Registry registry = LocateRegistry.getRegistry(Integer.parseInt(daemonPortString)); + buildService = (TeaVMRemoteBuildService) registry.lookup(TeaVMRemoteBuildService.ID); + } catch (NumberFormatException | RemoteException | NotBoundException e) { + e.printStackTrace(); + } + } } @Override @@ -63,7 +76,10 @@ public class TeaVMBuilder extends ModuleLevelBuilder { boolean doneSomething = false; - TeaVMBuild build = new TeaVMBuild(context, assistant, new InProcessBuildStrategy(context)); + TeaVMBuildStrategy buildStrategy = buildService != null + ? new RemoteBuildStrategy(context, buildService) + : new InProcessBuildStrategy(context); + TeaVMBuild build = new TeaVMBuild(context, assistant, buildStrategy); for (JpsModule module : chunk.getModules()) { doneSomething |= build.perform(module, chunk.representativeTarget()); if (context.getCancelStatus().isCanceled()) { diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java new file mode 100644 index 000000000..51a61e0d9 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 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; + +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.components.ServiceManager; +import java.io.IOException; +import org.jetbrains.annotations.NotNull; +import org.teavm.idea.daemon.TeaVMBuildDaemon; +import org.teavm.idea.daemon.TeaVMDaemonInfo; +import org.teavm.idea.jps.model.TeaVMJpsWorkspaceConfiguration; + +public class TeaVMDaemonComponent implements ApplicationComponent { + private TeaVMDaemonInfo daemonInfo; + + @Override + public void initComponent() { + TeaVMWorkspaceConfigurationStorage configurationStorage = ServiceManager.getService( + TeaVMWorkspaceConfigurationStorage.class); + if (configurationStorage != null) { + TeaVMJpsWorkspaceConfiguration configuration = configurationStorage.getState(); + if (configuration.isDaemonEnabled()) { + startDaemon(); + } + } + } + + private TeaVMWorkspaceConfigurationStorage getConfigurationStorage() { + return ServiceManager.getService(TeaVMWorkspaceConfigurationStorage.class); + } + + @Override + public void disposeComponent() { + if (daemonInfo != null) { + daemonInfo.getProcess().destroy(); + } + } + + public boolean isDaemonRunning() { + return daemonInfo != null; + } + + public int getDaemonPort() { + return daemonInfo.getPort(); + } + + public void startDaemon() { + if (daemonInfo == null) { + try { + daemonInfo = TeaVMBuildDaemon.start(); + } catch (IOException e) { + throw new RuntimeException(e); + } + updateConfiguration(true); + } + } + + public void stopDaemon() { + if (daemonInfo != null) { + daemonInfo.getProcess().destroy(); + daemonInfo = null; + updateConfiguration(false); + } + } + + private void updateConfiguration(boolean daemonEnabled) { + TeaVMWorkspaceConfigurationStorage configurationStorage = getConfigurationStorage(); + TeaVMJpsWorkspaceConfiguration configuration = configurationStorage.getState(); + configuration.setDaemonEnabled(daemonEnabled); + configurationStorage.loadState(configuration); + } + + @NotNull + @Override + public String getComponentName() { + return "TeaVM daemon"; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java index 9c67deffd..507964634 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java @@ -17,20 +17,28 @@ package org.teavm.idea; import static org.teavm.idea.jps.remote.TeaVMBuilderAssistant.REMOTE_PORT; import com.intellij.compiler.server.BuildProcessParametersProvider; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.NotNull; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildService; public class TeaVMJPSConfigurator extends BuildProcessParametersProvider { private TeaVMJPSRemoteService remoteService; + private TeaVMDaemonComponent daemonComponent; - public TeaVMJPSConfigurator(TeaVMJPSRemoteService remoteService) { + public TeaVMJPSConfigurator(TeaVMJPSRemoteService remoteService, TeaVMDaemonComponent daemonComponent) { this.remoteService = remoteService; + this.daemonComponent = daemonComponent; } @NotNull @Override public List getVMArguments() { - return Collections.singletonList("-D" + REMOTE_PORT + "=" + remoteService.getPort()); + List result = new ArrayList<>(); + result.add("-D" + REMOTE_PORT + "=" + remoteService.getPort()); + if (daemonComponent.isDaemonRunning()) { + result.add("-D" + TeaVMRemoteBuildService.REMOTE_PORT + "=" + daemonComponent.getDaemonPort()); + } + return result; } } diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMConfigurationStorage.java b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMWorkspaceConfigurationStorage.java similarity index 62% rename from tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMConfigurationStorage.java rename to tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMWorkspaceConfigurationStorage.java index 25afe8ecd..f8e5cd7f1 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMConfigurationStorage.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMWorkspaceConfigurationStorage.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Alexey Andreev. + * Copyright 2017 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,20 +19,20 @@ import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import org.jetbrains.annotations.Nullable; -import org.teavm.idea.jps.model.TeaVMJpsConfiguration; +import org.teavm.idea.jps.model.TeaVMJpsWorkspaceConfiguration; -@State(name = "teavm", storages = @Storage(value = "$MODULE_FILE$")) -public class TeaVMConfigurationStorage implements PersistentStateComponent { - private TeaVMJpsConfiguration state = new TeaVMJpsConfiguration(); +@State(name = "teavm", storages = @Storage(value = "teavm.xml")) +public class TeaVMWorkspaceConfigurationStorage implements PersistentStateComponent { + private TeaVMJpsWorkspaceConfiguration state = new TeaVMJpsWorkspaceConfiguration(); @Nullable @Override - public TeaVMJpsConfiguration getState() { + public TeaVMJpsWorkspaceConfiguration getState() { return state.createCopy(); } @Override - public void loadState(TeaVMJpsConfiguration state) { - this.state.applyChanges(state); + public void loadState(TeaVMJpsWorkspaceConfiguration configuration) { + state.applyChanges(configuration); } } diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMBuildDaemon.java b/tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMBuildDaemon.java new file mode 100644 index 000000000..bca429acb --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMBuildDaemon.java @@ -0,0 +1,242 @@ +/* + * Copyright 2017 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.daemon; + +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManager; +import com.intellij.openapi.extensions.PluginId; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.rmi.AlreadyBoundException; +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.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildCallback; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildRequest; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildResponse; +import org.teavm.idea.jps.remote.TeaVMRemoteBuildService; +import org.teavm.tooling.EmptyTeaVMToolLog; +import org.teavm.tooling.TeaVMTool; +import org.teavm.tooling.TeaVMToolException; +import org.teavm.tooling.sources.DirectorySourceFileProvider; +import org.teavm.tooling.sources.JarSourceFileProvider; +import org.teavm.vm.TeaVMPhase; +import org.teavm.vm.TeaVMProgressFeedback; +import org.teavm.vm.TeaVMProgressListener; + +public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemoteBuildService { + private static final int MIN_PORT = 10000; + private static final int MAX_PORT = 1 << 16; + private static final Set KOTLIN_FILES = new HashSet<>(Arrays.asList("teavm-jps-common.jar", + "teavm-plugin.jar", "teavm.jar")); + private static final String DAEMON_CLASS = TeaVMBuildDaemon.class.getName().replace('.', '/') + ".class"; + private static final int DAEMON_CLASS_DEPTH; + private static final String DAEMON_MESSAGE_PREFIX = "TeaVM daemon port: "; + private int port; + private Registry registry; + + static { + int depth = 0; + for (int i = 0; i < DAEMON_CLASS.length(); ++i) { + if (DAEMON_CLASS.charAt(i) == '/') { + depth++; + } + } + DAEMON_CLASS_DEPTH = depth; + } + + TeaVMBuildDaemon() 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(TeaVMRemoteBuildService.ID, this); + } catch (RemoteException | AlreadyBoundException e) { + throw new IllegalStateException("Could not bind remote build assistant service", e); + } + return; + } + throw new IllegalStateException("Could not create RMI registry"); + } + + public static void main(String[] args) throws RemoteException { + TeaVMBuildDaemon daemon = new TeaVMBuildDaemon(); + System.out.println(DAEMON_MESSAGE_PREFIX + daemon.port); + while (true) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + } + + @Override + public TeaVMRemoteBuildResponse build(TeaVMRemoteBuildRequest request, TeaVMRemoteBuildCallback callback) + throws RemoteException { + TeaVMTool tool = new TeaVMTool(); + tool.setProgressListener(createProgressListener(callback)); + tool.setLog(new EmptyTeaVMToolLog()); + tool.setTargetType(request.targetType); + tool.setMainClass(request.mainClass); + tool.setTargetDirectory(new File(request.targetDirectory)); + tool.setClassLoader(buildClassLoader(request.classPath)); + + tool.setSourceMapsFileGenerated(request.sourceMapsFileGenerated); + tool.setDebugInformationGenerated(request.debugInformationGenerated); + tool.setSourceFilesCopied(request.sourceFilesCopied); + + for (String sourceDirectory : request.sourceDirectories) { + tool.addSourceFileProvider(new DirectorySourceFileProvider(new File(sourceDirectory))); + } + for (String sourceJar : request.sourceJarFiles) { + tool.addSourceFileProvider(new JarSourceFileProvider(new File(sourceJar))); + } + + boolean errorOccurred = false; + try { + tool.generate(); + } catch (TeaVMToolException | RuntimeException | Error e) { + e.printStackTrace(System.err); + errorOccurred = true; + } + + TeaVMRemoteBuildResponse response = new TeaVMRemoteBuildResponse(); + response.errorOccurred = errorOccurred; + response.callGraph = tool.getDependencyInfo().getCallGraph(); + response.problems.addAll(tool.getProblemProvider().getProblems()); + response.severeProblems.addAll(tool.getProblemProvider().getSevereProblems()); + response.classes.addAll(tool.getClasses()); + response.usedResources.addAll(tool.getUsedResources()); + + return response; + } + + private ClassLoader buildClassLoader(List classPathEntries) { + URL[] urls = classPathEntries.stream().map(entry -> { + try { + return new File(entry).toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(entry); + } + }).toArray(URL[]::new); + + return new URLClassLoader(urls); + } + + private TeaVMProgressListener createProgressListener(TeaVMRemoteBuildCallback callback) { + return new TeaVMProgressListener() { + private long lastReportedTime; + + @Override + public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) { + if ((System.currentTimeMillis() - lastReportedTime) > 100) { + lastReportedTime = System.currentTimeMillis(); + try { + return callback.phaseStarted(phase, count); + } catch (RemoteException e) { + return TeaVMProgressFeedback.CANCEL; + } + } else { + return TeaVMProgressFeedback.CONTINUE; + } + } + + @Override + public TeaVMProgressFeedback progressReached(int progress) { + if ((System.currentTimeMillis() - lastReportedTime) > 100) { + lastReportedTime = System.currentTimeMillis(); + try { + return callback.progressReached(progress); + } catch (RemoteException e) { + return TeaVMProgressFeedback.CANCEL; + } + } else { + return TeaVMProgressFeedback.CONTINUE; + } + } + }; + } + + public static TeaVMDaemonInfo start() throws IOException { + String javaHome = System.getProperty("java.home"); + String javaCommand = javaHome + "/bin/java"; + String classPath = detectClassPath().stream().collect(Collectors.joining(File.pathSeparator)); + ProcessBuilder builder = new ProcessBuilder(javaCommand, "-cp", classPath, TeaVMBuildDaemon.class.getName()); + Process process = builder.start(); + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); + String line = reader.readLine(); + if (line == null || !line.startsWith(DAEMON_MESSAGE_PREFIX)) { + StringBuilder sb = new StringBuilder(); + reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); + while (true) { + line = reader.readLine(); + if (line == null) { + break; + } + sb.append(line).append('\n'); + } + throw new IllegalStateException("Could not start daemon. Stderr: " + sb); + } + int port = Integer.parseInt(line.substring(DAEMON_MESSAGE_PREFIX.length())); + return new TeaVMDaemonInfo(port, process); + } + + private static List detectClassPath() { + IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId("org.teavm.idea")); + Set visited = new HashSet<>(); + List classPath = new ArrayList<>(); + findInHierarchy(plugin.getPath(), classPath, visited); + return classPath; + } + + private static void findInHierarchy(File file, List targetFiles, Set visited) { + if (!visited.add(file)) { + return; + } + if (file.isFile() && KOTLIN_FILES.contains(file.getName())) { + targetFiles.add(file.getAbsolutePath()); + } else if (file.getPath().endsWith(DAEMON_CLASS)) { + for (int i = 0; i <= DAEMON_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/daemon/TeaVMDaemonInfo.java b/tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMDaemonInfo.java new file mode 100644 index 000000000..2354f6733 --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/daemon/TeaVMDaemonInfo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 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.daemon; + +public class TeaVMDaemonInfo { + private int port; + private Process process; + + TeaVMDaemonInfo(int port, Process process) { + this.port = port; + this.process = process; + } + + public int getPort() { + return port; + } + + public Process getProcess() { + return process; + } +} diff --git a/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMSettingsEditorTab.java b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMSettingsEditorTab.java new file mode 100644 index 000000000..3433aea0a --- /dev/null +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/ui/TeaVMSettingsEditorTab.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 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.ui; + +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SearchableConfigurable; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.teavm.idea.TeaVMDaemonComponent; + +public class TeaVMSettingsEditorTab implements SearchableConfigurable { + private JPanel contentPane; + private JCheckBox daemonCheckBox; + private TeaVMDaemonComponent daemonComponent; + + public TeaVMSettingsEditorTab(TeaVMDaemonComponent daemonComponent) { + this.daemonComponent = daemonComponent; + + contentPane = new JPanel(); + daemonCheckBox = new JCheckBox("use build daemon (can increase performance in most cases)"); + contentPane.setLayout(new GridBagLayout()); + + GridBagConstraints labelConstraints = new GridBagConstraints(); + labelConstraints.gridwidth = GridBagConstraints.REMAINDER; + labelConstraints.anchor = GridBagConstraints.BASELINE_LEADING; + labelConstraints.weightx = 1; + labelConstraints.weighty = 1; + labelConstraints.insets.left = 5; + labelConstraints.insets.right = 5; + + contentPane.add(daemonCheckBox, labelConstraints); + } + + @NotNull + @Override + public String getId() { + return "project.teavm.settings"; + } + + @Nls + @Override + public String getDisplayName() { + return "TeaVM"; + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Nullable + @Override + public JComponent createComponent() { + return contentPane; + } + + @Override + public boolean isModified() { + return daemonCheckBox.isSelected() != daemonComponent.isDaemonRunning(); + } + + @Override + public void apply() throws ConfigurationException { + if (daemonCheckBox.isSelected()) { + daemonComponent.startDaemon(); + } else { + daemonComponent.stopDaemon(); + } + } + + @Override + public void reset() { + daemonCheckBox.setSelected(daemonComponent.isDaemonRunning()); + } +} 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 93d383452..c20a87731 100644 --- a/tools/idea/plugin/src/main/resources/META-INF/plugin.xml +++ b/tools/idea/plugin/src/main/resources/META-INF/plugin.xml @@ -7,13 +7,10 @@ org.jetbrains.idea.maven - most HTML tags may be used + Plugin that allows to run TeaVM compiler and debug applications generated by TeaVM. ]]> - most HTML tags may be used ]]> @@ -23,14 +20,24 @@ org.teavm.idea.TeaVMJPSRemoteService + + org.teavm.idea.TeaVMDaemonComponent + + + + + diff --git a/tools/idea/plugin/teavm-idea.iml b/tools/idea/plugin/teavm-idea.iml index d73ac84b0..4dfe2e46d 100644 --- a/tools/idea/plugin/teavm-idea.iml +++ b/tools/idea/plugin/teavm-idea.iml @@ -19,7 +19,9 @@ - + + +