Improving build diagnostics in IDEA

This commit is contained in:
Alexey Andreev 2016-04-20 23:40:17 +03:00
parent 93cc51c575
commit 7f379eaeb7
8 changed files with 248 additions and 176 deletions

View File

@ -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<String> classPathEntries = new ArrayList<>();
private List<String> directoryClassPathEntries;
private TeaVMStorage storage;
private TeaVMBuilderAssistant assistant;
private Map<String, File> sourceFileCache = new HashMap<>();
private Map<File, int[]> 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;

View File

@ -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<JavaSourceRootDescriptor, ModuleBuildTarget> 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()) {

View File

@ -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<TeaVMJpsRemoteConfiguration> {
private static final JpsElementChildRole<TeaVMJpsRemoteConfiguration> 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;
}
}

View File

@ -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;
}

View File

@ -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<String> getVMArguments() {
return Collections.singletonList("-D" + TeaVMBuilder.REMOTE_PORT + "=" + remoteService.getPort());
}
}

View File

@ -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);
}
}

View File

@ -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<TeaVMJpsRemoteConfiguration> {
private TeaVMJpsRemoteConfiguration state = new TeaVMJpsRemoteConfiguration();
@NotNull
@Override
public TeaVMJpsRemoteConfiguration getState() {
return state.createCopy();
}
@Override
public void loadState(TeaVMJpsRemoteConfiguration state) {
this.state.applyChanges(state);
}
}

View File

@ -17,10 +17,17 @@
<idea-version since-build="141.0"/>
<application-components>
<component>
<implementation-class>org.teavm.idea.TeaVMJPSRemoteService</implementation-class>
</component>
</application-components>
<extensions defaultExtensionNs="com.intellij">
<moduleConfigurable instance="org.teavm.idea.ui.TeaVMConfigurable"/>
<moduleService serviceInterface="org.teavm.idea.TeaVMConfigurationStorage"
serviceImplementation="org.teavm.idea.TeaVMConfigurationStorage"/>
<compileServer.plugin classpath="jps/teavm-jps-plugin.jar;teavm-all.jar"/>
<buildProcess.parametersProvider implementation="org.teavm.idea.TeaVMJPSConfigurator"/>
</extensions>
</idea-plugin>