Merge branch 'idea-plugin'

This commit is contained in:
Alexey Andreev 2016-04-23 16:16:14 +03:00
commit 87e1ae6fb8
12 changed files with 867 additions and 187 deletions

View File

@ -4,6 +4,9 @@
<root url="jar://$PROJECT_DIR$/tools/idea/jps-plugin/lib/teavm-all.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
<SOURCES>
<root url="file://$PROJECT_DIR$/core/src/main/java" />
<root url="file://$PROJECT_DIR$/tools/core/src/main/java" />
</SOURCES>
</library>
</component>

View File

@ -329,11 +329,11 @@ class JSClassProcessor {
}
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
MethodReader overriden = getOverridenMethod(method);
if (overriden != null) {
MethodReader overridden = getOverridenMethod(method);
if (overridden != null) {
diagnostics.error(callLocation, "JS final method {{m0}} overrides {{m1}}. "
+ "Overriding final method of overlay types is prohibited.",
method.getReference(), overriden.getReference());
method.getReference(), overridden.getReference());
}
if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
@ -545,7 +545,7 @@ class JSClassProcessor {
int jsParamCount = bodyAnnot.getValue("params").getList().size();
if (methodToProcess.parameterCount() != jsParamCount) {
diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
+ " parameters, but annotation specifies " + jsParamCount, methodToProcess);
+ " parameters, but annotation specifies " + jsParamCount, methodToProcess.getReference());
return;
}
@ -554,11 +554,8 @@ class JSClassProcessor {
if (!isStatic) {
++paramCount;
}
ValueType[] paramTypes = new ValueType[paramCount];
int offset = 0;
if (!isStatic) {
ValueType paramType = ValueType.object(methodToProcess.getOwnerName());
paramTypes[offset++] = paramType;
if (!typeHelper.isSupportedType(paramType)) {
diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
methodToProcess.getReference(), methodToProcess.getOwnerName());
@ -571,9 +568,6 @@ class JSClassProcessor {
}
// generate parameter types for proxy method
for (int i = 0; i < methodToProcess.parameterCount(); ++i) {
paramTypes[offset++] = methodToProcess.parameterType(i);
}
ValueType[] proxyParamTypes = new ValueType[paramCount + 1];
for (int i = 0; i < paramCount; ++i) {
proxyParamTypes[i] = ValueType.parse(JSObject.class);
@ -587,7 +581,7 @@ class JSClassProcessor {
methodToProcess.getName() + "$js_body$_" + methodIndexGenerator++, proxyParamTypes);
String script = bodyAnnot.getValue("script").getString();
String[] parameterNames = bodyAnnot.getValue("params").getList().stream()
.map(ann -> ann.getString())
.map(AnnotationValue::getString)
.toArray(String[]::new);
// Parse JS script
@ -598,15 +592,13 @@ class JSClassProcessor {
env.setLanguageVersion(Context.VERSION_1_8);
env.setIdeMode(true);
JSParser parser = new JSParser(env, errorReporter);
//parser.enterFunction();
AstRoot rootNode;
try {
rootNode = parser.parse(new StringReader("function(){" + script + "}"), null, 0);
} catch (IOException e) {
throw new RuntimeException("IO Error occured", e);
throw new RuntimeException("IO Error occurred", e);
}
AstNode body = ((FunctionNode) rootNode.getFirstChild()).getBody();
//parser.exitFunction();
repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
if (errorReporter.hasErrors()) {

View File

@ -0,0 +1,423 @@
/*
* 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;
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.Arrays;
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.Collectors;
import java.util.stream.Stream;
import org.jetbrains.jps.incremental.CompileContext;
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.JpsLibraryRoot;
import org.jetbrains.jps.model.library.JpsOrderRootType;
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.sources.DirectorySourceFileProvider;
import org.teavm.tooling.sources.JarSourceFileProvider;
import org.teavm.tooling.sources.SourceFileProvider;
import org.teavm.vm.TeaVMPhase;
import org.teavm.vm.TeaVMProgressFeedback;
import org.teavm.vm.TeaVMProgressListener;
public class TeaVMBuild {
private final CompileContext context;
private final TeaVMStorageProvider storageProvider = new TeaVMStorageProvider();
private final List<String> classPathEntries = new ArrayList<>();
private List<String> directoryClassPathEntries;
private TeaVMStorage storage;
private final TeaVMBuilderAssistant assistant;
private final Map<String, File> sourceFileCache = new HashMap<>();
private final Map<File, int[]> fileLineCache = new HashMap<>();
private final List<SourceFileProvider> sourceFileProviders = new ArrayList<>();
public TeaVMBuild(CompileContext context, TeaVMBuilderAssistant assistant) {
this.context = context;
this.assistant = assistant;
}
public boolean perform(JpsModule module, ModuleBuildTarget target) throws IOException {
storage = context.getProjectDescriptor().dataManager.getStorage(target, storageProvider);
TeaVMJpsConfiguration config = TeaVMJpsConfiguration.get(module);
if (config == null || !config.isEnabled()) {
return false;
}
classPathEntries.clear();
buildClassPath(module, new HashSet<>());
directoryClassPathEntries = classPathEntries.stream().filter(name -> new File(name).isDirectory())
.collect(toList());
if (!hasChanges(target)) {
return false;
}
TeaVMTool tool = new TeaVMTool();
tool.setProgressListener(createProgressListener(context));
tool.setLog(new EmptyTeaVMToolLog());
tool.setMainClass(config.getMainClass());
tool.setSourceMapsFileGenerated(config.isSourceMapsFileGenerated());
tool.setTargetDirectory(new File(config.getTargetDirectory()));
tool.setClassLoader(buildClassLoader());
tool.setSourceFilesCopied(config.isSourceFilesCopied());
tool.setMinifying(config.isMinifying());
for (SourceFileProvider fileProvider : sourceFileProviders) {
tool.addSourceFileProvider(fileProvider);
}
boolean errorOccurred = false;
try {
tool.generate();
} catch (TeaVMToolException | RuntimeException | Error e) {
e.printStackTrace(System.err);
context.processMessage(new CompilerMessage("TeaVM", e));
errorOccurred = true;
}
if (!errorOccurred && tool.getProblemProvider().getSevereProblems().isEmpty()) {
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();
file = new File(location.getPath());
if (line <= 0) {
int[] lines = getLineOffsets(file);
if (lines != null) {
line = Arrays.binarySearch(lines, (int) startOffset + 1);
if (line < 0) {
line = -line - 1;
}
}
}
} catch (Exception e) {
// just don't fill location fields
}
}
}
DefaultProblemTextConsumer textConsumer = new DefaultProblemTextConsumer();
problem.render(textConsumer);
if (file == null) {
if (path != null) {
file = lookupSource(path);
path = file != null ? file.getPath() : null;
}
} else {
path = file.getPath();
}
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)) {
return true;
}
List<TeaVMStorage.Entry> filesToWatch = storage.getParticipatingFiles();
if (filesToWatch == null) {
return true;
}
for (TeaVMStorage.Entry fileToWatch : filesToWatch) {
Long actualTimestamp = getTimestamp(fileToWatch.path);
if (actualTimestamp == null || actualTimestamp > fileToWatch.timestamp) {
return true;
}
}
return false;
}
private void updateStorage(TeaVMTool tool) {
Set<String> resources = Stream.concat(tool.getClasses().stream().map(cls -> cls.replace('.', '/') + ".class"),
tool.getUsedResources().stream())
.sorted()
.collect(toSet());
List<TeaVMStorage.Entry> participatingFiles = resources.stream()
.map(path -> {
Long timestamp = getTimestamp(path);
return timestamp != null ? new TeaVMStorage.Entry(path, timestamp) : null;
})
.filter(Objects::nonNull)
.collect(toList());
storage.setParticipatingFiles(participatingFiles);
}
private Long getTimestamp(String path) {
for (String classPathEntry : directoryClassPathEntries) {
File file = new File(classPathEntry, path);
if (file.exists()) {
return file.lastModified();
}
}
return null;
}
private TeaVMProgressListener createProgressListener(CompileContext context) {
return new TeaVMProgressListener() {
private TeaVMPhase currentPhase;
int expectedCount;
@Override
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
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) {
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 DEVIRTUALIZATION:
return "Eliminating virtual calls";
case DECOMPILATION:
return "Compiling classes";
case RENDERING:
return "Building JS file";
default:
throw new AssertionError();
}
}
private ClassLoader buildClassLoader() {
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, TeaVMBuilder.class.getClassLoader());
}
private void buildClassPath(JpsModule module, Set<JpsModule> visited) {
if (!visited.add(module)) {
return;
}
File output = JpsJavaExtensionService.getInstance().getOutputDirectory(module, false);
if (output != null) {
classPathEntries.add(output.getPath());
}
sourceFileProviders.addAll(module.getSourceRoots().stream()
.map(sourceRoot -> new DirectorySourceFileProvider(sourceRoot.getFile()))
.collect(Collectors.toList()));
for (JpsDependencyElement dependency : module.getDependenciesList().getDependencies()) {
if (dependency instanceof JpsModuleDependency) {
JpsModuleDependency moduleDependency = (JpsModuleDependency) dependency;
buildClassPath(moduleDependency.getModule(), visited);
} else if (dependency instanceof JpsLibraryDependency) {
JpsLibrary library = ((JpsLibraryDependency) dependency).getLibrary();
if (library == null) {
continue;
}
classPathEntries.addAll(library.getFiles(JpsOrderRootType.COMPILED).stream().map(File::getPath)
.collect(toList()));
for (JpsLibraryRoot libraryRoot : library.getRoots(JpsOrderRootType.SOURCES)) {
File file = getFileFromUrl(libraryRoot.getUrl());
if (file != null) {
if (file.isDirectory()) {
sourceFileProviders.add(new DirectorySourceFileProvider(file));
} else {
sourceFileProviders.add(new JarSourceFileProvider(file));
}
}
}
}
}
}
private File getFileFromUrl(String url) {
if (url.startsWith("file://")) {
return new File(url.substring("file://".length()));
} else if (url.startsWith("jar://")) {
int index = url.indexOf('!');
return new File(url.substring("file://".length(), index));
} else if (url.startsWith("jar:file://")) {
int index = url.indexOf('!');
return new File(url.substring("jar:file://".length(), index));
} else {
return null;
}
}
}

View File

@ -15,14 +15,11 @@
*/
package org.teavm.idea.jps;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
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;
@ -34,34 +31,41 @@ 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.incremental.messages.ProgressMessage;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.model.library.JpsLibrary;
import org.jetbrains.jps.model.library.JpsOrderRootType;
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.teavm.idea.jps.model.TeaVMJpsConfiguration;
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;
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, assistant);
for (JpsModule module : chunk.getModules()) {
doneSomething |= buildModule(module, context);
doneSomething |= build.perform(module, chunk.representativeTarget());
if (context.getCancelStatus().isCanceled()) {
return ExitCode.ABORT;
}
@ -70,155 +74,6 @@ public class TeaVMBuilder extends ModuleLevelBuilder {
return doneSomething ? ExitCode.OK : ExitCode.NOTHING_DONE;
}
private boolean buildModule(JpsModule module, CompileContext context) {
TeaVMJpsConfiguration config = TeaVMJpsConfiguration.get(module);
if (config == null || !config.isEnabled()) {
return false;
}
TeaVMTool tool = new TeaVMTool();
tool.setProgressListener(createProgressListener(context));
tool.setLog(createLog(context));
tool.setMainClass(config.getMainClass());
tool.setSourceMapsFileGenerated(true);
tool.setTargetDirectory(new File(config.getTargetDirectory()));
tool.setClassLoader(buildClassLoader(module));
tool.setMinifying(false);
try {
tool.generate();
} catch (TeaVMToolException | RuntimeException | Error e) {
e.printStackTrace(System.err);
context.processMessage(new CompilerMessage("TeaVM", e));
}
return true;
}
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;
int expectedCount;
@Override
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
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) {
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 DEVIRTUALIZATION:
return "Eliminating virtual calls";
case DECOMPILATION:
return "Compiling classes";
case RENDERING:
return "Building JS file";
default:
throw new AssertionError();
}
}
private ClassLoader buildClassLoader(JpsModule module) {
Set<String> classPathEntries = new HashSet<>();
buildClassPath(module, new HashSet<>(), 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, TeaVMBuilder.class.getClassLoader());
}
private void buildClassPath(JpsModule module, Set<JpsModule> visited, Set<String> classPathEntries) {
if (!visited.add(module)) {
return;
}
File output = JpsJavaExtensionService.getInstance().getOutputDirectory(module, false);
if (output != null) {
classPathEntries.add(output.getPath());
}
for (JpsDependencyElement dependency : module.getDependenciesList().getDependencies()) {
if (dependency instanceof JpsModuleDependency) {
buildClassPath(((JpsModuleDependency) dependency).getModule(), visited, classPathEntries);
} else if (dependency instanceof JpsLibraryDependency) {
JpsLibrary library = ((JpsLibraryDependency) dependency).getLibrary();
if (library == null) {
continue;
}
classPathEntries.addAll(library.getFiles(JpsOrderRootType.COMPILED).stream().map(File::getPath)
.collect(Collectors.toList()));
}
}
}
@NotNull
@Override

View File

@ -0,0 +1,122 @@
/*
* 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;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.jps.incremental.storage.StorageOwner;
public class TeaVMStorage implements StorageOwner {
private File file;
private List<Entry> participatingFiles;
private boolean dirty;
TeaVMStorage(File file) throws IOException {
file = new File(file, "teavm.storage");
this.file = file;
if (file.exists()) {
participatingFiles = new ArrayList<>();
try (Reader innerReader = new InputStreamReader(new FileInputStream(file), "UTF-8");
BufferedReader reader = new BufferedReader(innerReader)) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
int index = line.lastIndexOf(':');
if (index < 0) {
participatingFiles = null;
file.delete();
break;
}
participatingFiles.add(new Entry(line.substring(0, index),
Long.parseLong(line.substring(index + 1))));
}
}
}
}
public void setParticipatingFiles(List<Entry> participatingFiles) {
if (participatingFiles == null) {
this.participatingFiles = null;
} else {
this.participatingFiles = new ArrayList<>(participatingFiles);
}
dirty = true;
}
public List<Entry> getParticipatingFiles() {
return participatingFiles != null ? new ArrayList<>(participatingFiles) : null;
}
@Override
public void flush(boolean b) {
}
@Override
public void clean() throws IOException {
file.delete();
participatingFiles = null;
}
@Override
public void close() throws IOException {
if (dirty) {
if (participatingFiles == null) {
if (file.exists()) {
file.delete();
}
} else {
try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
BufferedWriter writer = new BufferedWriter(innerWriter)) {
for (Entry participatingFile : participatingFiles) {
writer.append(participatingFile.path + ":" + participatingFile.timestamp);
writer.newLine();
}
}
}
}
}
public boolean causesBuild(String file) {
return participatingFiles == null || participatingFiles.contains(file);
}
public static class Entry {
public final String path;
public final long timestamp;
public Entry(String path, long timestamp) {
this.path = path;
this.timestamp = timestamp;
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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;
import java.io.File;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.storage.StorageProvider;
public class TeaVMStorageProvider extends StorageProvider<TeaVMStorage> {
@NotNull
@Override
public TeaVMStorage createStorage(File file) throws IOException {
return new TeaVMStorage(file);
}
}

View File

@ -0,0 +1,26 @@
/*
* 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.remote;
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,54 @@
/*
* 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.remote;
import java.io.Serializable;
public class TeaVMElementLocation implements Serializable {
private int startOffset;
private int endOffset;
private int line;
private int column;
private String path;
public TeaVMElementLocation(int startOffset, int endOffset, int line, int column, String path) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.line = line;
this.column = column;
this.path = path;
}
public int getStartOffset() {
return startOffset;
}
public int getEndOffset() {
return endOffset;
}
public int getLine() {
return line;
}
public int getColumn() {
return column;
}
public String getPath() {
return path;
}
}

View File

@ -21,7 +21,7 @@ import com.intellij.openapi.components.Storage;
import org.jetbrains.annotations.Nullable;
import org.teavm.idea.jps.model.TeaVMJpsConfiguration;
@State(name = "teavm", storages = @Storage(id = "other", file = "$MODULE_FILE$"))
@State(name = "teavm", storages = @Storage(id = "other", file = "$MODULE_FILE$"))
public class TeaVMConfigurationStorage implements PersistentStateComponent<TeaVMJpsConfiguration> {
private TeaVMJpsConfiguration state = new TeaVMJpsConfiguration();

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

@ -0,0 +1,133 @@
/*
* 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.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.intellij.psi.search.GlobalSearchScope;
import com.siyeh.ig.fixes.MemberSignature;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;
import org.jetbrains.annotations.NotNull;
import org.teavm.idea.jps.remote.TeaVMBuilderAssistant;
import org.teavm.idea.jps.remote.TeaVMElementLocation;
public class TeaVMJPSRemoteService extends UnicastRemoteObject implements ApplicationComponent, TeaVMBuilderAssistant {
private static final int MIN_PORT = 10000;
private static final int MAX_PORT = 1 << 16;
private final ProjectManager projectManager = ProjectManager.getInstance();
private int port;
private Registry registry;
public TeaVMJPSRemoteService() throws RemoteException {
super();
}
@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;
}
throw new IllegalStateException("Could not create RMI registry");
}
public int getPort() {
return port;
}
@Override
public void disposeComponent() {
try {
registry.unbind(TeaVMBuilderAssistant.ID);
UnicastRemoteObject.unexportObject(registry, true);
} catch (RemoteException | NotBoundException e) {
throw new IllegalStateException("Could not clean-up RMI server", e);
}
}
@NotNull
@Override
public String getComponentName() {
return "TeaVM JPS service";
}
@Override
public TeaVMElementLocation getMethodLocation(String className, String methodName, String methodDesc)
throws RemoteException {
TeaVMElementLocation[] resultHolder = new TeaVMElementLocation[1];
ApplicationManager.getApplication().runReadAction(() -> {
for (Project project : projectManager.getOpenProjects()) {
JavaPsiFacade psi = JavaPsiFacade.getInstance(project);
PsiClass cls = psi.findClass(className, GlobalSearchScope.allScope(project));
if (cls == null) {
continue;
}
for (PsiMethod method : cls.getAllMethods()) {
if (!method.getName().equals(methodName) || !getMethodSignature(method).equals(methodDesc)) {
continue;
}
resultHolder[0] = getMethodLocation(method);
return;
}
}
});
return resultHolder[0];
}
private String getMethodSignature(PsiMethod method) {
StringBuilder sb = new StringBuilder("(");
for (PsiParameter parameter : method.getParameterList().getParameters()) {
sb.append(MemberSignature.createTypeSignature(parameter.getType()));
}
sb.append(")");
PsiType returnType = method.getReturnType();
sb.append(MemberSignature.createTypeSignature(returnType != null ? returnType : PsiType.VOID));
return sb.toString();
}
private TeaVMElementLocation getMethodLocation(PsiMethod method) {
return new TeaVMElementLocation(method.getTextOffset(), method.getTextOffset() + method.getTextLength(),
-1, -1, method.getContainingFile().getVirtualFile().getPath());
}
}

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>