From d65d5720715809bbdf87fff28134ff7e5f6f686c Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Fri, 11 Sep 2015 12:01:52 +0200 Subject: [PATCH] Running java scratches (IDEA-140228) --- .../compiler/CompilerManagerImpl.java | 201 ++++++++++++++++++ .../openapi/compiler/ClassObject.java | 27 +++ .../compiler/CompilationException.java | 87 ++++++++ .../openapi/compiler/CompilerManager.java | 14 ++ .../ui/impl/watch/CompilingEvaluator.java | 17 +- .../ui/impl/watch/CompilingEvaluatorImpl.java | 147 ++----------- .../impl/JavaScratchCompilationSupport.java | 170 +++++++++++++++ .../JavaScratchRunConfigurationExtension.java | 137 ++++++++++++ resources/src/META-INF/IdeaPlugin.xml | 5 + 9 files changed, 668 insertions(+), 137 deletions(-) create mode 100644 java/compiler/openapi/src/com/intellij/openapi/compiler/ClassObject.java create mode 100644 java/compiler/openapi/src/com/intellij/openapi/compiler/CompilationException.java create mode 100644 java/execution/impl/src/com/intellij/execution/impl/JavaScratchCompilationSupport.java create mode 100644 java/execution/impl/src/com/intellij/execution/impl/JavaScratchRunConfigurationExtension.java diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java index 32615305b913..36112dbbb558 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java @@ -17,6 +17,7 @@ package com.intellij.compiler; import com.intellij.codeInspection.InspectionManager; import com.intellij.compiler.impl.*; +import com.intellij.compiler.server.BuildManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.*; @@ -29,20 +30,38 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.JavaSdkVersion; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkTypeId; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiManager; +import com.intellij.util.SmartList; import com.intellij.util.messages.MessageBus; import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.net.NetUtils; +import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.api.CanceledStatus; +import org.jetbrains.jps.builders.impl.java.JavacCompilerTool; +import org.jetbrains.jps.incremental.BinaryContent; +import org.jetbrains.jps.javac.DiagnosticOutputConsumer; +import org.jetbrains.jps.javac.ExternalJavacManager; +import org.jetbrains.jps.javac.OutputFileConsumer; +import org.jetbrains.jps.javac.OutputFileObject; +import javax.tools.*; import java.io.File; +import java.io.IOException; import java.lang.reflect.Array; +import java.net.URI; import java.util.*; import java.util.concurrent.Semaphore; @@ -58,6 +77,7 @@ public class CompilerManagerImpl extends CompilerManager { private final Semaphore myCompilationSemaphore = new Semaphore(1, true); private final Set myValidationDisabledModuleTypes = new HashSet(); private final Set myWatchRoots; + private volatile ExternalJavacManager myExternalJavacManager; public CompilerManagerImpl(final Project project, MessageBus messageBus) { myProject = project; @@ -85,6 +105,11 @@ public class CompilerManagerImpl extends CompilerManager { myWatchRoots = lfs.addRootsToWatch(Collections.singletonList(FileUtil.toCanonicalPath(projectGeneratedSrcRoot.getPath())), true); Disposer.register(project, new Disposable() { public void dispose() { + final ExternalJavacManager manager = myExternalJavacManager; + myExternalJavacManager = null; + if (manager != null) { + manager.stop(); + } lfs.removeWatchedRoots(myWatchRoots); if (ApplicationManager.getApplication().isUnitTestMode()) { // force cleanup for created compiler system directory with generated sources FileUtil.delete(CompilerPaths.getCompilerSystemDirectory(project)); @@ -310,6 +335,144 @@ public class CompilerManagerImpl extends CompilerManager { return !myValidationDisabledModuleTypes.contains(ModuleType.get(module)); } + @Override + public Collection compileJavaCode(List options, + Collection platformCp, + Collection classpath, + Collection sourcePath, + Collection files, + File outputDir) throws IOException, CompilationException { + + final Pair runtime = BuildManager.getBuildProcessRuntimeSdk(myProject); + + String javaHome = null; + final Sdk sdk = runtime.getFirst(); + final SdkTypeId type = sdk.getSdkType(); + if (type instanceof JavaSdkType) { + javaHome = sdk.getHomePath(); + } + if (javaHome == null) { + throw new IOException("Was not able to determine JDK for project " + myProject.getName()); + } + + final OutputCollector outputCollector = new OutputCollector(); + DiagnosticCollector diagnostic = new DiagnosticCollector(); + + final Set sourceRoots = new THashSet(FileUtil.FILE_HASHING_STRATEGY); + if (!sourcePath.isEmpty()) { + for (File file : sourcePath) { + sourceRoots.add(file); + } + } + else { + for (File file : files) { + final File parentFile = file.getParentFile(); + if (parentFile != null) { + sourceRoots.add(parentFile); + } + } + } + final Map> outs = Collections.singletonMap(outputDir, sourceRoots); + + final ExternalJavacManager javacManager = getJavacManager(); + boolean compiledOk = javacManager != null && javacManager.forkJavac( + javaHome, -1, Collections.emptyList(), options, platformCp, classpath, sourcePath, files, outs, diagnostic, outputCollector, + new JavacCompilerTool(), CanceledStatus.NULL + ); + + if (!compiledOk) { + final List messages = new SmartList(); + for (Diagnostic d : diagnostic.getDiagnostics()) { + final JavaFileObject source = d.getSource(); + final URI uri = source != null ? source.toUri() : null; + messages.add(new CompilationException.Message( + kindToCategory(d.getKind()), d.getMessage(Locale.US), uri != null? uri.toURL().toString() : null, (int)d.getLineNumber(), (int)d.getColumnNumber() + )); + } + throw new CompilationException("Compilation failed", messages); + } + + final List result = new ArrayList(); + for (OutputFileObject fileObject : outputCollector.getCompiledClasses()) { + final BinaryContent content = fileObject.getContent(); + result.add(new CompiledClass(fileObject.getName(), fileObject.getClassName(), content != null ? content.toByteArray() : null)); + } + return result; + } + + private static CompilerMessageCategory kindToCategory(Diagnostic.Kind kind) { + switch (kind) { + case ERROR: return CompilerMessageCategory.ERROR; + case MANDATORY_WARNING: return CompilerMessageCategory.WARNING; + case WARNING: return CompilerMessageCategory.WARNING; + case NOTE: return CompilerMessageCategory.INFORMATION; + default: + return CompilerMessageCategory.INFORMATION; + } + } + + + @Nullable + private ExternalJavacManager getJavacManager() throws IOException { + ExternalJavacManager manager = myExternalJavacManager; + if (manager == null) { + synchronized (this) { + manager = myExternalJavacManager; + if (manager == null) { + final File compilerWorkingDir = getJavacCompilerWorkingDir(); + if (compilerWorkingDir == null) { + return null; // should not happen for real projects + } + final int listenPort = NetUtils.findAvailableSocketPort(); + manager = new ExternalJavacManager(compilerWorkingDir); + manager.start(listenPort); + myExternalJavacManager = manager; + } + } + } + return manager; + } + + @Override + @Nullable + public File getJavacCompilerWorkingDir() { + final File projectBuildDir = BuildManager.getInstance().getProjectSystemDirectory(myProject); + if (projectBuildDir == null) { + return null; + } + projectBuildDir.mkdirs(); + return projectBuildDir; + } + + private static class CompiledClass implements ClassObject { + private final String myPath; + private final String myClassName; + private final byte[] myBytes; + + public CompiledClass(String path, String className, byte[] bytes) { + myPath = path; + myClassName = className; + myBytes = bytes; + } + + @Override + public String getPath() { + return myPath; + } + + @Override + public String getClassName() { + return myClassName; + } + + @Nullable + @Override + public byte[] getContent() { + return myBytes; + } + } + + private class ListenerNotificator implements CompileStatusNotification { private final @Nullable CompileStatusNotification myDelegate; @@ -326,4 +489,42 @@ public class CompilerManagerImpl extends CompilerManager { } } } + + private static class DiagnosticCollector implements DiagnosticOutputConsumer { + private final List> myDiagnostics = new ArrayList>(); + public void outputLineAvailable(String line) { + // for debugging purposes uncomment this line + //System.out.println(line); + } + + public void registerImports(String className, Collection imports, Collection staticImports) { + // ignore + } + + public void javaFileLoaded(File file) { + // ignore + } + + public void report(Diagnostic diagnostic) { + myDiagnostics.add(diagnostic); + } + + public List> getDiagnostics() { + return myDiagnostics; + } + } + + + private static class OutputCollector implements OutputFileConsumer { + private List myClasses = new ArrayList(); + + public void save(@NotNull OutputFileObject fileObject) { + myClasses.add(fileObject); + } + + public List getCompiledClasses() { + return myClasses; + } + } + } diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/ClassObject.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/ClassObject.java new file mode 100644 index 000000000000..5f16d11a1e86 --- /dev/null +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/ClassObject.java @@ -0,0 +1,27 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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 com.intellij.openapi.compiler; + +import org.jetbrains.annotations.Nullable; + +public interface ClassObject { + String getPath(); + + String getClassName(); + + @Nullable + byte[] getContent(); +} diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilationException.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilationException.java new file mode 100644 index 000000000000..9e0ab845fdc9 --- /dev/null +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilationException.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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 com.intellij.openapi.compiler; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; + +public class CompilationException extends Exception { + + private final Collection myMessages; + + public static class Message { + @NotNull + private final CompilerMessageCategory myCategory; + @NotNull + private final String myMessage; + @Nullable + private final String myUrl; + private final int myLine; + private final int myColumn; + + public Message(CompilerMessageCategory category, String message) { + this(category, message, null, -1, -1); + } + + public Message(@NotNull CompilerMessageCategory category, @NotNull String message, @Nullable String url, int line, int column) { + myCategory = category; + myMessage = message; + myUrl = url; + myLine = line; + myColumn = column; + } + + @NotNull + public CompilerMessageCategory getCategory() { + return myCategory; + } + + @NotNull + public String getText() { + return myMessage; + } + + @Nullable + public String getUrl() { + return myUrl; + } + + public int getLine() { + return myLine; + } + + public int getColumn() { + return myColumn; + } + } + + public CompilationException(String message) { + this(message, Collections.emptyList()); + } + + public CompilationException(String message, Collection messages) { + super(message); + myMessages = messages; + } + + @NotNull + public Collection getMessages() { + return myMessages; + } +} diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java index 055233b90c02..0cb844369fd3 100644 --- a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerManager.java @@ -27,6 +27,10 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; import java.util.Set; /** @@ -289,4 +293,14 @@ public abstract class CompilerManager { public abstract void setValidationEnabled(ModuleType moduleType, boolean enabled); public abstract boolean isValidationEnabled(Module moduleType); + + public abstract Collection compileJavaCode(List options, + Collection platformCp, + Collection classpath, + Collection sourcePath, + Collection files, + File outputDir) throws IOException, CompilationException; + + @Nullable + public abstract File getJavacCompilerWorkingDir(); } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java index a40a3ba71dc5..f92aa24d3fdc 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluator.java @@ -27,6 +27,7 @@ import com.intellij.debugger.impl.ClassLoadingUtils; import com.intellij.debugger.impl.DebuggerUtilsEx; import com.intellij.debugger.jdi.VirtualMachineProxyImpl; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.ClassObject; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.JdkVersionUtil; @@ -38,8 +39,6 @@ import com.sun.jdi.ClassType; import com.sun.jdi.Value; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.incremental.BinaryContent; -import org.jetbrains.jps.javac.OutputFileObject; import org.jetbrains.org.objectweb.asm.ClassReader; import org.jetbrains.org.objectweb.asm.ClassVisitor; import org.jetbrains.org.objectweb.asm.ClassWriter; @@ -80,7 +79,7 @@ public abstract class CompilingEvaluator implements ExpressionEvaluator { ClassLoaderReference classLoader = ClassLoadingUtils.getClassLoader(evaluationContext, process); String version = ((VirtualMachineProxyImpl)process.getVirtualMachineProxy()).version(); - Collection classes = compile(JdkVersionUtil.getVersion(version)); + Collection classes = compile(JdkVersionUtil.getVersion(version)); defineClasses(classes, evaluationContext, process, classLoader); @@ -112,15 +111,15 @@ public abstract class CompilingEvaluator implements ExpressionEvaluator { } } - private ClassType defineClasses(Collection classes, + private ClassType defineClasses(Collection classes, EvaluationContext context, DebugProcess process, ClassLoaderReference classLoader) throws EvaluateException { - for (OutputFileObject cls : classes) { - if (cls.getName().contains(GEN_CLASS_NAME)) { - final BinaryContent content = cls.getContent(); + for (ClassObject cls : classes) { + if (cls.getPath().contains(GEN_CLASS_NAME)) { + final byte[] content = cls.getContent(); if (content != null) { - byte[] bytes = changeSuperToMagicAccessor(content.toByteArray()); + final byte[] bytes = changeSuperToMagicAccessor(content); ClassLoadingUtils.defineClass(cls.getClassName(), bytes, context, process, classLoader); } } @@ -165,6 +164,6 @@ public abstract class CompilingEvaluator implements ExpressionEvaluator { ///////////////// Compiler stuff @NotNull - protected abstract Collection compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException; + protected abstract Collection compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException; } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluatorImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluatorImpl.java index 7e0d3d671ca3..c41a0d269abc 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluatorImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/CompilingEvaluatorImpl.java @@ -16,39 +16,28 @@ package com.intellij.debugger.ui.impl.watch; import com.intellij.compiler.server.BuildManager; -import com.intellij.debugger.engine.DebugProcess; -import com.intellij.debugger.engine.DebugProcessAdapter; -import com.intellij.debugger.engine.DebugProcessImpl; -import com.intellij.debugger.engine.DebuggerManagerThreadImpl; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.ClassObject; +import com.intellij.openapi.compiler.CompilationException; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; -import com.intellij.openapi.projectRoots.JavaSdkType; import com.intellij.openapi.projectRoots.JavaSdkVersion; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.projectRoots.SdkTypeId; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler; -import com.intellij.util.net.NetUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.api.CanceledStatus; -import org.jetbrains.jps.builders.impl.java.JavacCompilerTool; -import org.jetbrains.jps.javac.DiagnosticOutputConsumer; -import org.jetbrains.jps.javac.ExternalJavacManager; -import org.jetbrains.jps.javac.OutputFileConsumer; -import org.jetbrains.jps.javac.OutputFileObject; -import javax.tools.*; import java.io.File; import java.io.IOException; import java.util.*; @@ -65,23 +54,13 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator { @Override @NotNull - protected Collection compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException { - final Pair runtime = BuildManager.getBuildProcessRuntimeSdk(myEvaluationContext.getProject()); + protected Collection compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException { final Module module = ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Module compute() { return ModuleUtilCore.findModuleForPsiElement(myPsiContext); } }); - String javaHome = null; - final Sdk sdk = runtime.getFirst(); - final SdkTypeId type = sdk.getSdkType(); - if (type instanceof JavaSdkType) { - javaHome = sdk.getHomePath(); - } - if (javaHome == null) { - throw new EvaluateException("Was not able to determine JDK for current evaluation context"); - } final List options = new ArrayList(); options.add("-proc:none"); // for our purposes annotation processing is not needed options.add("-encoding"); @@ -98,6 +77,7 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator { } } + final Pair runtime = BuildManager.getBuildProcessRuntimeSdk(myEvaluationContext.getProject()); final JavaSdkVersion buildRuntimeVersion = runtime.getSecond(); // if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults if (buildRuntimeVersion != null && debuggeeVersion != null) { @@ -109,36 +89,25 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator { options.add(sourceOption); } + final CompilerManager compilerManager = CompilerManager.getInstance(myEvaluationContext.getProject()); + File sourceFile = null; - final OutputCollector outputSink = new OutputCollector(); try { - final ExternalJavacManager javacManager = getJavacManager(); - if (javacManager == null) { - throw new EvaluateException("Cannot compile java code"); - } - sourceFile = generateTempSourceFile(javacManager.getWorkingDir()); + sourceFile = generateTempSourceFile(compilerManager.getJavacCompilerWorkingDir()); final File srcDir = sourceFile.getParentFile(); - final Map> output = Collections.singletonMap(srcDir, Collections.singleton(srcDir)); - DiagnosticCollector diagnostic = new DiagnosticCollector(); - final List vmOptions = Collections.emptyList(); final List sourcePath = Collections.emptyList(); final Set sources = Collections.singleton(sourceFile); - boolean compiledOK = javacManager.forkJavac( - javaHome, -1, vmOptions, options, platformClasspath, classpath, sourcePath, sources, output, diagnostic, outputSink, new JavacCompilerTool(), CanceledStatus.NULL - ); - if (!compiledOK) { - final StringBuilder res = new StringBuilder("Compilation failed:\n"); - for (Diagnostic d : diagnostic.getDiagnostics()) { - if (d.getKind() == Diagnostic.Kind.ERROR) { - res.append(d.getMessage(Locale.US)); - } - } - throw new EvaluateException(res.toString()); - } + return compilerManager.compileJavaCode(options, platformClasspath, classpath, sourcePath, sources, srcDir); } - catch (EvaluateException e) { - throw e; + catch (CompilationException e) { + final StringBuilder res = new StringBuilder("Compilation failed:\n"); + for (CompilationException.Message m : e.getMessages()) { + if (m.getCategory() == CompilerMessageCategory.ERROR) { + res.append(m.getText()).append("\n"); + } + } + throw new EvaluateException(res.toString()); } catch (Exception e) { throw new EvaluateException(e.getMessage()); @@ -148,7 +117,6 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator { FileUtil.delete(sourceFile); } } - return outputSink.getCompiledClasses(); } @NotNull @@ -170,85 +138,8 @@ public class CompilingEvaluatorImpl extends CompilingEvaluator { if (fileData.second == null) { throw new IOException("Class source code not specified"); } - final File file = new File(workingDir, "src/"+fileData.first); + final File file = new File(workingDir, "debugger/src/"+fileData.first); FileUtil.writeToFile(file, fileData.second); return file; } - - private static final Key JAVAC_MANAGER_KEY = Key.create("_external_java_compiler_manager_"); - - @Nullable - private ExternalJavacManager getJavacManager() throws IOException { - // need dedicated thread access to be able to cache the manager in the user data - DebuggerManagerThreadImpl.assertIsManagerThread(); - - final DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess(); - ExternalJavacManager manager = JAVAC_MANAGER_KEY.get(debugProcess); - if (manager == null && debugProcess.isAttached()) { - final File compilerWorkingDir = getCompilerWorkingDir(); - if (compilerWorkingDir == null) { - return null; // should not happen for real projects - } - final int listenPort = NetUtils.findAvailableSocketPort(); - manager = new ExternalJavacManager(compilerWorkingDir); - manager.start(listenPort); - final ExternalJavacManager _manager = manager; - debugProcess.addDebugProcessListener(new DebugProcessAdapter() { - public void processDetached(DebugProcess process, boolean closedByUser) { - if (process == debugProcess) { - _manager.stop(); - } - } - }); - JAVAC_MANAGER_KEY.set(debugProcess, manager); - } - return manager; - } - - @Nullable - private File getCompilerWorkingDir() { - final File projectBuildDir = BuildManager.getInstance().getProjectSystemDirectory(myEvaluationContext.getProject()); - if (projectBuildDir == null) { - return null; - } - final File root = new File(projectBuildDir, "debugger"); - root.mkdirs(); - return root; - } - - private static class DiagnosticCollector implements DiagnosticOutputConsumer { - private final List> myDiagnostics = new ArrayList>(); - public void outputLineAvailable(String line) { - // for debugging purposes uncomment this line - //System.out.println(line); - } - - public void registerImports(String className, Collection imports, Collection staticImports) { - // ignore - } - - public void javaFileLoaded(File file) { - // ignore - } - - public void report(Diagnostic diagnostic) { - myDiagnostics.add(diagnostic); - } - - public List> getDiagnostics() { - return myDiagnostics; - } - } - - private static class OutputCollector implements OutputFileConsumer { - private List myClasses = new ArrayList(); - - public void save(@NotNull OutputFileObject fileObject) { - myClasses.add(fileObject); - } - - public List getCompiledClasses() { - return myClasses; - } - } } diff --git a/java/execution/impl/src/com/intellij/execution/impl/JavaScratchCompilationSupport.java b/java/execution/impl/src/com/intellij/execution/impl/JavaScratchCompilationSupport.java new file mode 100644 index 000000000000..394f3b9f47fd --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/impl/JavaScratchCompilationSupport.java @@ -0,0 +1,170 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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 com.intellij.execution.impl; + +import com.intellij.compiler.options.CompileStepBeforeRun; +import com.intellij.execution.configurations.ModuleBasedConfiguration; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdk; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.JavaSdkVersion; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFileManager; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * @author Eugene Zhuravlev + * Date: 08-Sep-15 + */ +public class JavaScratchCompilationSupport implements ProjectComponent, CompileTask{ + + public JavaScratchCompilationSupport(Project project, CompilerManager compileManager) { + compileManager.addAfterTask(this); + } + + @Override + public boolean execute(CompileContext context) { + final Project project = context.getProject(); + + File outputDir = null; + File srcDir = null; + + if (context.isRebuild()) { + // perform cleanup + outputDir = JavaScratchRunConfigurationExtension.getScratchOutputDirectory(project); + if (outputDir == null) { // should not happen for normal projects + return true; + } + FileUtil.delete(outputDir); + srcDir = JavaScratchRunConfigurationExtension.getScratchTempDirectory(project); + if (srcDir != null) { + FileUtil.delete(srcDir); + } + } + + final RunConfiguration configuration = CompileStepBeforeRun.getRunConfiguration(context); + if (!(configuration instanceof ModuleBasedConfiguration)) { + return true; + } + final String scratchUrl = JavaScratchRunConfigurationExtension.getScratchFileUrl(configuration); + if (scratchUrl == null) { + return true; + } + final Module configModule = ((ModuleBasedConfiguration)configuration).getConfigurationModule().getModule(); + if (configModule == null) { + return true; // todo: show error? + } + final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(configModule); + final Sdk targetSdk = moduleRootManager.getSdk(); + if (targetSdk == null || !(targetSdk.getSdkType() instanceof JavaSdkType)) { + return true; // todo: show error? + } + if (outputDir == null) { + outputDir = JavaScratchRunConfigurationExtension.getScratchOutputDirectory(project); + if (outputDir == null) { // should not happen for normal projects + return true; + } + } + try { + final File scratchFile = new File(VirtualFileManager.extractPath(scratchUrl)); + File srcFile = scratchFile; + if (!StringUtil.endsWith(srcFile.getName(), ".java")) { + if (srcDir == null) { + srcDir = JavaScratchRunConfigurationExtension.getScratchTempDirectory(project); + if (srcDir == null) { // should not happen for normal projects + return true; + } + } + srcFile = new File(srcDir, FileUtil.getNameWithoutExtension(scratchFile) + ".java"); + FileUtil.copy(scratchFile, srcFile); + } + + final Collection files = Collections.singleton(srcFile); + + final Set cp = new LinkedHashSet(); + for (String s : moduleRootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().getPathList()) { + cp.add(new File(s)); + } + final List platformCp = new ArrayList(); + for (String s : moduleRootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) { + platformCp.add(new File(s)); + } + + final List options = new ArrayList(); + final JavaSdkVersion sdkVersion = JavaSdk.getInstance().getVersion(targetSdk); + if (sdkVersion != null) { + final String langLevel = "1." + Integer.valueOf(3 + sdkVersion.getMaxLanguageLevel().ordinal()); + options.add("-source"); + options.add(langLevel); + options.add("-target"); + options.add(langLevel); + } + options.add("-proc:none"); // disable annotation processing + + final Collection result = CompilerManager.getInstance(project).compileJavaCode( + options, platformCp, cp, Collections.emptyList(), files, outputDir + ); + for (ClassObject classObject : result) { + final byte[] bytes = classObject.getContent(); + if (bytes != null) { + FileUtil.writeToFile(new File(classObject.getPath()), bytes); + } + } + } + catch (CompilationException e) { + for (CompilationException.Message m : e.getMessages()) { + context.addMessage(m.getCategory(), m.getText(), scratchUrl, m.getLine(), m.getColumn()); + } + } + catch (IOException e) { + context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), scratchUrl, -1, -1); + } + return true; + } + + @Override + public void projectOpened() { + } + + @Override + public void projectClosed() { + } + + @Override + public void initComponent() { + } + + @Override + public void disposeComponent() { + } + + @NotNull + @Override + public String getComponentName() { + return "JavaScratchCompilationSupport"; + } +} diff --git a/java/execution/impl/src/com/intellij/execution/impl/JavaScratchRunConfigurationExtension.java b/java/execution/impl/src/com/intellij/execution/impl/JavaScratchRunConfigurationExtension.java new file mode 100644 index 000000000000..5b6d9bf23b06 --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/impl/JavaScratchRunConfigurationExtension.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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 com.intellij.execution.impl; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Location; +import com.intellij.execution.RunConfigurationExtension; +import com.intellij.execution.application.ApplicationConfiguration; +import com.intellij.execution.configurations.JavaParameters; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunConfigurationBase; +import com.intellij.execution.configurations.RunnerSettings; +import com.intellij.ide.scratch.ScratchFileType; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.compiler.CompilerManager; +import com.intellij.openapi.options.SettingsEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + + +/* +execution-impl -> lang-impl(scratches are here) +compiler-impl -> lang-impl + +java-impl ->execution-impl, compiler-impl + */ +public class JavaScratchRunConfigurationExtension extends RunConfigurationExtension{ + + public static final Key SCRATCH_FILE_URL = Key.create("_scratch_file_path_"); + + public void cleanUserData(RunConfigurationBase configuration) { + super.cleanUserData(configuration); + configuration.putCopyableUserData(SCRATCH_FILE_URL, null); + } + + protected void extendCreatedConfiguration(@NotNull RunConfigurationBase configuration, @NotNull Location location) { + final VirtualFile vFile = location.getVirtualFile(); + if (vFile != null && vFile.getFileType() == ScratchFileType.INSTANCE) { + final PsiFile psiFile = location.getPsiElement().getContainingFile(); + if (psiFile != null && psiFile.getLanguage() == JavaLanguage.INSTANCE) { + configuration.putCopyableUserData(SCRATCH_FILE_URL, vFile.getUrl()); + } + } + } + + protected void validateConfiguration(@NotNull RunConfigurationBase configuration, boolean isExecution) throws Exception { + super.validateConfiguration(configuration, isExecution); + } + + public void updateJavaParameters(T configuration, JavaParameters params, RunnerSettings runnerSettings) throws ExecutionException { + final File scrachesOutput = getScratchOutputDirectory(configuration.getProject()); + if (scrachesOutput != null) { + params.getClassPath().add(scrachesOutput); + } + } + + @NotNull + protected String getSerializationId() { + return "java-scratch-properties"; + } + + @Nullable + public static String getScratchFileUrl(RunConfiguration configuration) { + return configuration instanceof RunConfigurationBase? ((RunConfigurationBase)configuration).getCopyableUserData(SCRATCH_FILE_URL) : null; + } + + @Nullable + public static File getScratchOutputDirectory(Project project) { + final File root = CompilerManager.getInstance(project).getJavacCompilerWorkingDir(); + return root != null? new File(root, "scratches/out") : null; + } + + @Nullable + public static File getScratchTempDirectory(Project project) { + final File root = CompilerManager.getInstance(project).getJavacCompilerWorkingDir(); + return root != null? new File(root, "scratches/src") : null; + } + + + protected void readExternal(@NotNull RunConfigurationBase runConfiguration, @NotNull Element element) throws InvalidDataException { + final Element sourceElement = element.getChild("source"); + if (sourceElement != null) { + final String scratchUrl = sourceElement.getAttributeValue("url"); + if (scratchUrl != null) { + runConfiguration.putCopyableUserData(SCRATCH_FILE_URL, scratchUrl); + } + } + } + + protected void writeExternal(@NotNull RunConfigurationBase runConfiguration, @NotNull Element element) throws WriteExternalException { + final String url = getScratchFileUrl(runConfiguration); + if (url == null) { + super.writeExternal(runConfiguration, element); + } + else { + final Element urlElement = new Element("source"); + urlElement.setAttribute("url", url); + element.addContent(urlElement); + } + } + + @Nullable + protected

SettingsEditor

createEditor(@NotNull P configuration) { + return null; + } + + @Nullable + protected String getEditorTitle() { + return null; + } + + protected boolean isApplicableFor(@NotNull RunConfigurationBase configuration) { + return configuration instanceof ApplicationConfiguration; + } +} diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index f56ab058f842..4fcf29eddd27 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -66,6 +66,10 @@ com.intellij.execution.testDiscovery.TestDiscoveryIndex + + + com.intellij.execution.impl.JavaScratchCompilationSupport + @@ -1616,6 +1620,7 @@ +