diff --git a/tools/idea/idea-artifacts/dep-pom.xml b/tools/idea/idea-artifacts/dep-pom.xml index 87840e85f..d52e6c7e6 100644 --- a/tools/idea/idea-artifacts/dep-pom.xml +++ b/tools/idea/idea-artifacts/dep-pom.xml @@ -251,6 +251,17 @@ jps-builders + + log4j + + install-file + + prepare-package + + dependencies/idea/lib/commons-logging-1.2.jar + commons-logging + + dependencies/maven diff --git a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMBuildStrategy.java b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMBuildStrategy.java index 91efb2ce0..c9cc9ca9e 100644 --- a/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMBuildStrategy.java +++ b/tools/idea/jps-common/src/main/java/org/teavm/idea/jps/model/TeaVMBuildStrategy.java @@ -42,5 +42,7 @@ public interface TeaVMBuildStrategy { void setProgressListener(TeaVMProgressListener progressListener); + void setIncremental(boolean incremental); + TeaVMBuildResult build(); } 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 index d6e57cc1f..da8ceb373 100644 --- 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 @@ -20,6 +20,7 @@ import org.jetbrains.jps.model.ex.JpsElementBase; public class TeaVMJpsWorkspaceConfiguration extends JpsElementBase { private boolean daemonEnabled; + private boolean incremental; public boolean isDaemonEnabled() { return daemonEnabled; @@ -29,6 +30,14 @@ public class TeaVMJpsWorkspaceConfiguration extends JpsElementBase${idea.version} provided + + org.teavm.idea + commons-logging + ${idea.version} + provided + org.teavm.idea teavm 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 index 51a61e0d9..27f22fb80 100644 --- a/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java +++ b/tools/idea/plugin/src/main/java/org/teavm/idea/TeaVMDaemonComponent.java @@ -25,6 +25,7 @@ import org.teavm.idea.jps.model.TeaVMJpsWorkspaceConfiguration; public class TeaVMDaemonComponent implements ApplicationComponent { private TeaVMDaemonInfo daemonInfo; + private boolean incremental; @Override public void initComponent() { @@ -32,6 +33,7 @@ public class TeaVMDaemonComponent implements ApplicationComponent { TeaVMWorkspaceConfigurationStorage.class); if (configurationStorage != null) { TeaVMJpsWorkspaceConfiguration configuration = configurationStorage.getState(); + incremental = configuration.isIncremental(); if (configuration.isDaemonEnabled()) { startDaemon(); } @@ -60,7 +62,7 @@ public class TeaVMDaemonComponent implements ApplicationComponent { public void startDaemon() { if (daemonInfo == null) { try { - daemonInfo = TeaVMBuildDaemon.start(); + daemonInfo = TeaVMBuildDaemon.start(incremental); } catch (IOException e) { throw new RuntimeException(e); } @@ -76,6 +78,21 @@ public class TeaVMDaemonComponent implements ApplicationComponent { } } + public boolean isIncremental() { + return incremental; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public void applyChanges() { + TeaVMWorkspaceConfigurationStorage configurationStorage = getConfigurationStorage(); + TeaVMJpsWorkspaceConfiguration configuration = configurationStorage.getState(); + configuration.setIncremental(incremental); + configurationStorage.loadState(configuration); + } + private void updateConfiguration(boolean daemonEnabled) { TeaVMWorkspaceConfigurationStorage configurationStorage = getConfigurationStorage(); TeaVMJpsWorkspaceConfiguration configuration = configurationStorage.getState(); 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 index 361d19205..d650be9b8 100644 --- 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 @@ -25,6 +25,7 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; @@ -37,6 +38,9 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.teavm.idea.jps.remote.TeaVMRemoteBuildCallback; import org.teavm.idea.jps.remote.TeaVMRemoteBuildRequest; import org.teavm.idea.jps.remote.TeaVMRemoteBuildResponse; @@ -58,8 +62,11 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote 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 static final String INCREMENTAL_PROPERTY = "teavm.daemon.incremental"; + private boolean incremental; private int port; private Registry registry; + private File incrementalCache; static { int depth = 0; @@ -71,8 +78,9 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote DAEMON_CLASS_DEPTH = depth; } - TeaVMBuildDaemon() throws RemoteException { + TeaVMBuildDaemon(boolean incremental) throws RemoteException { super(); + this.incremental = incremental; Random random = new Random(); for (int i = 0; i < 20; ++i) { port = random.nextInt(MAX_PORT - MIN_PORT) + MIN_PORT; @@ -86,14 +94,36 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote } catch (RemoteException | AlreadyBoundException e) { throw new IllegalStateException("Could not bind remote build assistant service", e); } + + setupIncrementalCache(); + return; } throw new IllegalStateException("Could not create RMI registry"); } + private void setupIncrementalCache() { + if (!incremental) { + return; + } + + try { + incrementalCache = Files.createTempDirectory("teavm-cache").toFile(); + incrementalCache.deleteOnExit(); + } catch (IOException e) { + System.err.println("Could not setup incremental cache"); + e.printStackTrace(System.err); + incremental = false; + } + } + public static void main(String[] args) throws RemoteException { - TeaVMBuildDaemon daemon = new TeaVMBuildDaemon(); + boolean incremental = Boolean.parseBoolean(System.getProperty(INCREMENTAL_PROPERTY, "false")); + TeaVMBuildDaemon daemon = new TeaVMBuildDaemon(incremental); System.out.println(DAEMON_MESSAGE_PREFIX + daemon.port); + if (daemon.incrementalCache != null) { + System.out.println("Incremental cache set up in " + daemon.incrementalCache); + } while (true) { try { Thread.sleep(1000); @@ -106,7 +136,22 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote @Override public TeaVMRemoteBuildResponse build(TeaVMRemoteBuildRequest request, TeaVMRemoteBuildCallback callback) throws RemoteException { + System.out.println("Build started"); + + if (!request.incremental && incremental) { + try { + System.out.println("Dropping incremental cache"); + FileUtils.cleanDirectory(incrementalCache); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + TeaVMTool tool = new TeaVMTool(); + tool.setIncremental(incremental); + if (tool.isIncremental()) { + tool.setCacheDirectory(incrementalCache); + } tool.setProgressListener(createProgressListener(callback)); tool.setLog(new EmptyTeaVMToolLog()); tool.setTargetType(request.targetType); @@ -130,6 +175,7 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote boolean errorOccurred = false; try { tool.generate(); + System.out.println("Build complete"); } catch (TeaVMToolException | RuntimeException | Error e) { e.printStackTrace(System.err); errorOccurred = true; @@ -190,19 +236,24 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote }; } - public static TeaVMDaemonInfo start() throws IOException { + public static TeaVMDaemonInfo start(boolean incremental) 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()); + ProcessBuilder builder = new ProcessBuilder(javaCommand, "-cp", classPath, + "-D" + INCREMENTAL_PROPERTY + "=" + incremental, + TeaVMBuildDaemon.class.getName()); Process process = builder.start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); - String line = reader.readLine(); + + Log log = LogFactory.getLog(TeaVMBuildDaemon.class); + + BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); + BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); + String line = stdoutReader.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(); + line = stderrReader.readLine(); if (line == null) { break; } @@ -211,9 +262,41 @@ public class TeaVMBuildDaemon extends UnicastRemoteObject implements TeaVMRemote throw new IllegalStateException("Could not start daemon. Stderr: " + sb); } int port = Integer.parseInt(line.substring(DAEMON_MESSAGE_PREFIX.length())); + + new Thread(new DaemonProcessOutputWatcher(log, stdoutReader, "stdout", false)).start(); + new Thread(new DaemonProcessOutputWatcher(log, stderrReader, "stderr", true)).start(); + return new TeaVMDaemonInfo(port, process); } + static class DaemonProcessOutputWatcher implements Runnable { + private Log log; + private BufferedReader reader; + private String name; + private boolean isError; + + public DaemonProcessOutputWatcher(Log log, BufferedReader reader, String name, boolean isError) { + this.log = log; + this.reader = reader; + this.name = name; + this.isError = isError; + } + + @Override + public void run() { + try { + String line = reader.readLine(); + if (isError) { + log.error("Build daemon [" + name + "]: " + line); + } else { + log.info("Build daemon [" + name + "]: " + line); + } + } catch (IOException e) { + log.error("Error reading build daemon output", e); + } + } + } + private static List detectClassPath() { IdeaPluginDescriptor plugin = PluginManager.getPlugin(PluginId.getId("org.teavm.idea")); Set visited = new HashSet<>(); 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 index 3433aea0a..9dce07478 100644 --- 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 @@ -30,6 +30,7 @@ import org.teavm.idea.TeaVMDaemonComponent; public class TeaVMSettingsEditorTab implements SearchableConfigurable { private JPanel contentPane; private JCheckBox daemonCheckBox; + private JCheckBox incrementalCheckBox; private TeaVMDaemonComponent daemonComponent; public TeaVMSettingsEditorTab(TeaVMDaemonComponent daemonComponent) { @@ -37,8 +38,11 @@ public class TeaVMSettingsEditorTab implements SearchableConfigurable { contentPane = new JPanel(); daemonCheckBox = new JCheckBox("use build daemon (can increase performance in most cases)"); + incrementalCheckBox = new JCheckBox("incremental build (only available with daemon)"); contentPane.setLayout(new GridBagLayout()); + daemonCheckBox.addActionListener(e -> incrementalCheckBox.setEnabled(daemonCheckBox.isSelected())); + GridBagConstraints labelConstraints = new GridBagConstraints(); labelConstraints.gridwidth = GridBagConstraints.REMAINDER; labelConstraints.anchor = GridBagConstraints.BASELINE_LEADING; @@ -48,6 +52,13 @@ public class TeaVMSettingsEditorTab implements SearchableConfigurable { labelConstraints.insets.right = 5; contentPane.add(daemonCheckBox, labelConstraints); + contentPane.add(incrementalCheckBox, labelConstraints); + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.BOTH; + constraints.weighty = 100; + constraints.weightx = 1; + contentPane.add(new JPanel(), constraints); } @NotNull @@ -76,20 +87,40 @@ public class TeaVMSettingsEditorTab implements SearchableConfigurable { @Override public boolean isModified() { - return daemonCheckBox.isSelected() != daemonComponent.isDaemonRunning(); + return daemonCheckBox.isSelected() != daemonComponent.isDaemonRunning() + || incrementalCheckBox.isSelected() != daemonComponent.isIncremental(); } @Override public void apply() throws ConfigurationException { + boolean shouldRestartDaemon = true; + + if (incrementalCheckBox.isSelected() && !daemonComponent.isIncremental()) { + shouldRestartDaemon = true; + daemonComponent.setIncremental(incrementalCheckBox.isSelected()); + } + if (daemonCheckBox.isSelected()) { - daemonComponent.startDaemon(); + if (!daemonComponent.isDaemonRunning()) { + daemonComponent.startDaemon(); + shouldRestartDaemon = false; + } } else { daemonComponent.stopDaemon(); + shouldRestartDaemon = false; } + + if (shouldRestartDaemon) { + daemonComponent.stopDaemon(); + daemonComponent.startDaemon(); + } + + daemonComponent.applyChanges(); } @Override public void reset() { daemonCheckBox.setSelected(daemonComponent.isDaemonRunning()); + incrementalCheckBox.setSelected(daemonComponent.isIncremental()); } }