From 7f379eaeb7b470607aa4b15bbab0a794209a3ff7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 20 Apr 2016 23:40:17 +0300 Subject: [PATCH] Improving build diagnostics in IDEA --- .../java/org/teavm/idea/jps/TeaVMBuild.java | 199 +++++++++++++----- .../java/org/teavm/idea/jps/TeaVMBuilder.java | 27 ++- .../model/TeaVMJpsRemoteConfiguration.java | 58 ----- .../jps/remote/TeaVMBuilderAssistant.java | 2 + .../org/teavm/idea/TeaVMJPSConfigurator.java | 36 ++++ .../org/teavm/idea/TeaVMJPSRemoteService.java | 56 ++--- .../idea/TeaVMRemoteConfigurationStorage.java | 39 ---- .../src/main/resources/META-INF/plugin.xml | 7 + 8 files changed, 248 insertions(+), 176 deletions(-) delete mode 100644 tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/model/TeaVMJpsRemoteConfiguration.java create mode 100644 tools/idea/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java delete mode 100644 tools/idea/src/main/java/org/teavm/idea/TeaVMRemoteConfigurationStorage.java diff --git a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuild.java b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuild.java index 92ef91ff2..f2b61ed26 100644 --- a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuild.java +++ b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/TeaVMBuild.java @@ -18,13 +18,18 @@ package org.teavm.idea.jps; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -33,6 +38,7 @@ import org.jetbrains.jps.incremental.ModuleBuildTarget; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import org.jetbrains.jps.incremental.messages.ProgressMessage; +import org.jetbrains.jps.model.JpsProject; import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.JpsOrderRootType; @@ -40,10 +46,21 @@ import org.jetbrains.jps.model.module.JpsDependencyElement; import org.jetbrains.jps.model.module.JpsLibraryDependency; import org.jetbrains.jps.model.module.JpsModule; import org.jetbrains.jps.model.module.JpsModuleDependency; +import org.jetbrains.jps.model.module.JpsModuleSourceRoot; +import org.teavm.common.IntegerArray; +import org.teavm.diagnostics.DefaultProblemTextConsumer; +import org.teavm.diagnostics.Problem; +import org.teavm.diagnostics.ProblemProvider; import org.teavm.idea.jps.model.TeaVMJpsConfiguration; +import org.teavm.idea.jps.remote.TeaVMBuilderAssistant; +import org.teavm.idea.jps.remote.TeaVMElementLocation; +import org.teavm.model.CallLocation; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; +import org.teavm.tooling.EmptyTeaVMToolLog; import org.teavm.tooling.TeaVMTool; import org.teavm.tooling.TeaVMToolException; -import org.teavm.tooling.TeaVMToolLog; import org.teavm.vm.TeaVMPhase; import org.teavm.vm.TeaVMProgressFeedback; import org.teavm.vm.TeaVMProgressListener; @@ -54,9 +71,13 @@ public class TeaVMBuild { private List classPathEntries = new ArrayList<>(); private List directoryClassPathEntries; private TeaVMStorage storage; + private TeaVMBuilderAssistant assistant; + private Map sourceFileCache = new HashMap<>(); + private Map fileLineCache = new HashMap<>(); - public TeaVMBuild(CompileContext context) { + public TeaVMBuild(CompileContext context, TeaVMBuilderAssistant assistant) { this.context = context; + this.assistant = assistant; } public boolean perform(JpsModule module, ModuleBuildTarget target) throws IOException { @@ -78,7 +99,7 @@ public class TeaVMBuild { TeaVMTool tool = new TeaVMTool(); tool.setProgressListener(createProgressListener(context)); - tool.setLog(createLog(context)); + tool.setLog(new EmptyTeaVMToolLog()); tool.setMainClass(config.getMainClass()); tool.setSourceMapsFileGenerated(true); tool.setTargetDirectory(new File(config.getTargetDirectory())); @@ -98,9 +119,133 @@ public class TeaVMBuild { updateStorage(tool); } + reportProblems(tool.getProblemProvider()); + return true; } + private void reportProblems(ProblemProvider problemProvider) { + for (Problem problem : problemProvider.getProblems()) { + BuildMessage.Kind kind; + switch (problem.getSeverity()) { + case ERROR: + kind = BuildMessage.Kind.ERROR; + break; + case WARNING: + kind = BuildMessage.Kind.WARNING; + break; + default: + continue; + } + + String path = null; + File file = null; + int line = -1; + long startOffset = -1; + long endOffset = -1; + + if (problem.getLocation() != null) { + CallLocation callLocation = problem.getLocation(); + InstructionLocation insnLocation = problem.getLocation().getSourceLocation(); + if (insnLocation != null) { + path = insnLocation.getFileName(); + line = insnLocation.getLine(); + } + + if (line <= 0 && assistant != null && callLocation != null && callLocation.getMethod() != null) { + MethodReference method = callLocation.getMethod(); + try { + TeaVMElementLocation location = assistant.getMethodLocation(method.getClassName(), + method.getName(), ValueType.methodTypeToString(method.getSignature())); + line = location.getLine(); + startOffset = location.getStartOffset(); + endOffset = location.getEndOffset(); + } catch (Exception e) { + // just don't fill location fields + } + } + } + + DefaultProblemTextConsumer textConsumer = new DefaultProblemTextConsumer(); + problem.render(textConsumer); + + if (path != null) { + file = lookupSource(path); + path = file != null ? file.getPath() : null; + } + + if (startOffset < 0 && file != null && line > 0) { + int[] lines = getLineOffsets(file); + if (lines != null && line < lines.length) { + startOffset = lines[line - 1]; + endOffset = lines[line] - 1; + } + } + + context.processMessage(new CompilerMessage("TeaVM", kind, textConsumer.getText(), path, + startOffset, endOffset, startOffset, line, 0)); + } + } + + private File lookupSource(String relativePath) { + return sourceFileCache.computeIfAbsent(relativePath, this::lookupSourceCacheMiss); + } + + private File lookupSourceCacheMiss(String relativePath) { + JpsProject project = context.getProjectDescriptor().getModel().getProject(); + for (JpsModule module : project.getModules()) { + for (JpsModuleSourceRoot sourceRoot : module.getSourceRoots()) { + File fullPath = new File(sourceRoot.getFile(), relativePath); + if (fullPath.exists()) { + return fullPath; + } + } + } + return null; + } + + private int[] getLineOffsets(File file) { + return fileLineCache.computeIfAbsent(file, this::getLineOffsetsCacheMiss); + } + + private int[] getLineOffsetsCacheMiss(File file) { + IntegerArray lines = new IntegerArray(50); + try (Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8")) { + int offset = 0; + lines.add(0); + + boolean expectingLf = false; + while (true) { + int c = reader.read(); + if (c == -1) { + break; + } + if (c == '\n') { + expectingLf = false; + lines.add(offset + 1); + } else { + if (expectingLf) { + expectingLf = false; + lines.add(offset); + } + if (c == '\r') { + lines.add(offset + 1); + expectingLf = true; + } + } + ++offset; + } + + if (expectingLf) { + lines.add(offset); + } + lines.add(offset + 1); + } catch (IOException e) { + return null; + } + return lines.getAll(); + } + private boolean hasChanges(ModuleBuildTarget target) { if (!context.getScope().isBuildIncrementally(target.getTargetType()) || context.getScope().isBuildForced(target)) { @@ -145,54 +290,6 @@ public class TeaVMBuild { return null; } - private TeaVMToolLog createLog(CompileContext context) { - return new TeaVMToolLog() { - @Override - public void info(String text) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.INFO, text)); - } - - @Override - public void debug(String text) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.INFO, text)); - } - - @Override - public void warning(String text) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.WARNING, text)); - } - - @Override - public void error(String text) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.ERROR, text)); - } - - @Override - public void info(String text, Throwable e) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.INFO, text + "\n" - + CompilerMessage.getTextFromThrowable(e))); - } - - @Override - public void debug(String text, Throwable e) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.INFO, text + "\n" - + CompilerMessage.getTextFromThrowable(e))); - } - - @Override - public void warning(String text, Throwable e) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.WARNING, text + "\n" - + CompilerMessage.getTextFromThrowable(e))); - } - - @Override - public void error(String text, Throwable e) { - context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.ERROR, text + "\n" - + CompilerMessage.getTextFromThrowable(e))); - } - }; - } - private TeaVMProgressListener createProgressListener(CompileContext context) { return new TeaVMProgressListener() { private TeaVMPhase currentPhase; 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 d41da4d05..ac9bedc3d 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 @@ -16,6 +16,10 @@ package org.teavm.idea.jps; import java.io.IOException; +import java.rmi.NotBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.ModuleChunk; import org.jetbrains.jps.builders.DirtyFilesHolder; @@ -25,20 +29,41 @@ import org.jetbrains.jps.incremental.CompileContext; import org.jetbrains.jps.incremental.ModuleBuildTarget; import org.jetbrains.jps.incremental.ModuleLevelBuilder; 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.remote.TeaVMBuilderAssistant; public class TeaVMBuilder extends ModuleLevelBuilder { + public static final String REMOTE_PORT = "teavm.jps.remote-port"; + private static TeaVMBuilderAssistant assistant; + public TeaVMBuilder() { super(BuilderCategory.CLASS_POST_PROCESSOR); + + String portString = System.getProperty(REMOTE_PORT); + if (portString != null) { + try { + Registry registry = LocateRegistry.getRegistry(Integer.parseInt(portString)); + assistant = (TeaVMBuilderAssistant) registry.lookup(TeaVMBuilderAssistant.ID); + } catch (NumberFormatException | RemoteException | NotBoundException e) { + e.printStackTrace(); + } + } } @Override public ExitCode build(CompileContext context, ModuleChunk chunk, DirtyFilesHolder dirtyFilesHolder, OutputConsumer outputConsumer) throws ProjectBuildException, IOException { + if (assistant == null) { + context.processMessage(new CompilerMessage("TeaVM", BuildMessage.Kind.WARNING, + "No TeaVM builder assistant available. Diagnostic messages will be less informative")); + } + boolean doneSomething = false; - TeaVMBuild build = new TeaVMBuild(context); + TeaVMBuild build = new TeaVMBuild(context, assistant); for (JpsModule module : chunk.getModules()) { doneSomething |= build.perform(module, chunk.representativeTarget()); if (context.getCancelStatus().isCanceled()) { diff --git a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/model/TeaVMJpsRemoteConfiguration.java b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/model/TeaVMJpsRemoteConfiguration.java deleted file mode 100644 index 4a25e06e9..000000000 --- a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/model/TeaVMJpsRemoteConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.idea.jps.model; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.jps.model.JpsElementChildRole; -import org.jetbrains.jps.model.JpsProject; -import org.jetbrains.jps.model.ex.JpsElementBase; -import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; -import org.jetbrains.jps.model.module.JpsModule; - -public class TeaVMJpsRemoteConfiguration extends JpsElementBase { - private static final JpsElementChildRole ROLE = JpsElementChildRoleBase.create( - "TeaVM remote configuration"); - private int port; - - public static TeaVMJpsRemoteConfiguration get(JpsProject project) { - return project.getContainer().getChild(ROLE); - } - - public void setTo(JpsModule project) { - project.getContainer().setChild(ROLE, this); - } - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - @NotNull - @Override - public TeaVMJpsRemoteConfiguration createCopy() { - TeaVMJpsRemoteConfiguration copy = new TeaVMJpsRemoteConfiguration(); - copy.port = port; - return copy; - } - - @Override - public void applyChanges(@NotNull TeaVMJpsRemoteConfiguration modified) { - port = modified.port; - } -} diff --git a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/remote/TeaVMBuilderAssistant.java b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/remote/TeaVMBuilderAssistant.java index fd4ae05d7..0a9741b8a 100644 --- a/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/remote/TeaVMBuilderAssistant.java +++ b/tools/idea/jps-plugin/src/main/java/org/teavm/idea/jps/remote/TeaVMBuilderAssistant.java @@ -19,6 +19,8 @@ import java.rmi.Remote; import java.rmi.RemoteException; public interface TeaVMBuilderAssistant extends Remote { + String ID = "TeaVM-JPS-Assistant"; + TeaVMElementLocation getMethodLocation(String className, String methodName, String methodDesc) throws RemoteException; } diff --git a/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java b/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java new file mode 100644 index 000000000..a03f8ed06 --- /dev/null +++ b/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSConfigurator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.idea; + +import com.intellij.compiler.server.BuildProcessParametersProvider; +import java.util.Collections; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.teavm.idea.jps.TeaVMBuilder; + +public class TeaVMJPSConfigurator extends BuildProcessParametersProvider { + private TeaVMJPSRemoteService remoteService; + + public TeaVMJPSConfigurator(TeaVMJPSRemoteService remoteService) { + this.remoteService = remoteService; + } + + @NotNull + @Override + public List getVMArguments() { + return Collections.singletonList("-D" + TeaVMBuilder.REMOTE_PORT + "=" + remoteService.getPort()); + } +} diff --git a/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSRemoteService.java b/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSRemoteService.java index 2098741c0..c13279dd1 100644 --- a/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSRemoteService.java +++ b/tools/idea/src/main/java/org/teavm/idea/TeaVMJPSRemoteService.java @@ -18,7 +18,6 @@ package org.teavm.idea; import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ProjectManagerAdapter; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; @@ -29,50 +28,53 @@ import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; +import java.util.Random; import org.jetbrains.annotations.NotNull; -import org.teavm.idea.jps.model.TeaVMJpsRemoteConfiguration; import org.teavm.idea.jps.remote.TeaVMBuilderAssistant; import org.teavm.idea.jps.remote.TeaVMElementLocation; -public class TeaVMJPSRemoteService implements ApplicationComponent, TeaVMBuilderAssistant { +public class TeaVMJPSRemoteService extends UnicastRemoteObject implements ApplicationComponent, TeaVMBuilderAssistant { + private static final int MIN_PORT = 10000; + private static final int MAX_PORT = 1 << 16; private ProjectManager projectManager = ProjectManager.getInstance(); private int port; private Registry registry; - @Override - public void initComponent() { - - for (Project project : projectManager.getOpenProjects()) { - configureProject(project); - } - projectManager.addProjectManagerListener(new ProjectManagerAdapter() { - @Override - public void projectOpened(Project project) { - configureProject(project); - } - }); + public TeaVMJPSRemoteService() throws RemoteException { + super(); } - private void configureProject(Project project) { - try { - registry = LocateRegistry.createRegistry(0); - registry.bind("TeaVM", this); - } catch (RemoteException | AlreadyBoundException e) { - e.printStackTrace(); + @Override + public void initComponent() { + 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(TeaVMBuilderAssistant.ID, this); + } catch (RemoteException | AlreadyBoundException e) { + throw new IllegalStateException("Could not bind remote build assistant service", e); + } + return; } - TeaVMRemoteConfigurationStorage storage = project.getComponent(TeaVMRemoteConfigurationStorage.class); - TeaVMJpsRemoteConfiguration config = storage.getState(); - config.setPort(port); - storage.loadState(config); + throw new IllegalStateException("Could not create RMI registry"); + } + + public int getPort() { + return port; } @Override public void disposeComponent() { try { - registry.unbind("TeaVM"); + registry.unbind(TeaVMBuilderAssistant.ID); UnicastRemoteObject.unexportObject(registry, true); } catch (RemoteException | NotBoundException e) { - e.printStackTrace(); + throw new IllegalStateException("Could not clean-up RMI server", e); } } diff --git a/tools/idea/src/main/java/org/teavm/idea/TeaVMRemoteConfigurationStorage.java b/tools/idea/src/main/java/org/teavm/idea/TeaVMRemoteConfigurationStorage.java deleted file mode 100644 index de8021013..000000000 --- a/tools/idea/src/main/java/org/teavm/idea/TeaVMRemoteConfigurationStorage.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2016 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.idea; - -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.teavm.idea.jps.model.TeaVMJpsRemoteConfiguration; - -@State(name = "teavm", storages = @Storage(id = "other", file = "$PROJECT_FILE$")) -public class TeaVMRemoteConfigurationStorage implements PersistentStateComponent { - private TeaVMJpsRemoteConfiguration state = new TeaVMJpsRemoteConfiguration(); - - @NotNull - @Override - public TeaVMJpsRemoteConfiguration getState() { - return state.createCopy(); - } - - @Override - public void loadState(TeaVMJpsRemoteConfiguration state) { - this.state.applyChanges(state); - } -} diff --git a/tools/idea/src/main/resources/META-INF/plugin.xml b/tools/idea/src/main/resources/META-INF/plugin.xml index b5dadbfbb..7a7b6b784 100644 --- a/tools/idea/src/main/resources/META-INF/plugin.xml +++ b/tools/idea/src/main/resources/META-INF/plugin.xml @@ -17,10 +17,17 @@ + + + org.teavm.idea.TeaVMJPSRemoteService + + + + \ No newline at end of file