2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.debugger.ui.impl.watch;
18 import com.intellij.compiler.server.BuildManager;
19 import com.intellij.debugger.engine.DebugProcess;
20 import com.intellij.debugger.engine.DebugProcessAdapter;
21 import com.intellij.debugger.engine.DebugProcessImpl;
22 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
23 import com.intellij.debugger.engine.evaluation.EvaluateException;
24 import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.module.Module;
27 import com.intellij.openapi.module.ModuleUtilCore;
28 import com.intellij.openapi.projectRoots.JavaSdkType;
29 import com.intellij.openapi.projectRoots.JavaSdkVersion;
30 import com.intellij.openapi.projectRoots.Sdk;
31 import com.intellij.openapi.projectRoots.SdkTypeId;
32 import com.intellij.openapi.roots.ModuleRootManager;
33 import com.intellij.openapi.util.Computable;
34 import com.intellij.openapi.util.Key;
35 import com.intellij.openapi.util.Pair;
36 import com.intellij.openapi.util.io.FileUtil;
37 import com.intellij.pom.java.LanguageLevel;
38 import com.intellij.psi.PsiElement;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.refactoring.extractMethodObject.ExtractLightMethodObjectHandler;
41 import com.intellij.util.net.NetUtils;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44 import org.jetbrains.jps.api.CanceledStatus;
45 import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
46 import org.jetbrains.jps.javac.DiagnosticOutputConsumer;
47 import org.jetbrains.jps.javac.ExternalJavacManager;
48 import org.jetbrains.jps.javac.OutputFileConsumer;
49 import org.jetbrains.jps.javac.OutputFileObject;
53 import java.io.IOException;
56 // todo: consider batching compilations in order not to start a separate process for every class that needs to be compiled
57 public class CompilingEvaluatorImpl extends CompilingEvaluator {
59 private final EvaluationContextImpl myEvaluationContext;
61 public CompilingEvaluatorImpl(EvaluationContextImpl evaluationContext, @NotNull PsiElement context, @NotNull ExtractLightMethodObjectHandler.ExtractedData data) {
63 myEvaluationContext = evaluationContext;
68 protected Collection<OutputFileObject> compile(@Nullable JavaSdkVersion debuggeeVersion) throws EvaluateException {
69 final Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getBuildProcessRuntimeSdk(myEvaluationContext.getProject());
70 final Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
72 public Module compute() {
73 return ModuleUtilCore.findModuleForPsiElement(myPsiContext);
76 String javaHome = null;
77 final Sdk sdk = runtime.getFirst();
78 final SdkTypeId type = sdk.getSdkType();
79 if (type instanceof JavaSdkType) {
80 javaHome = sdk.getHomePath();
82 if (javaHome == null) {
83 throw new EvaluateException("Was not able to determine JDK for current evaluation context");
85 final List<String> options = new ArrayList<String>();
86 options.add("-proc:none"); // for our purposes annotation processing is not needed
87 options.add("-encoding");
89 final List<File> platformClasspath = new ArrayList<File>();
90 final List<File> classpath = new ArrayList<File>();
92 final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
93 for (String s : rootManager.orderEntries().compileOnly().recursively().exportedOnly().withoutSdk().getPathsList().getPathList()) {
94 classpath.add(new File(s));
96 for (String s : rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) {
97 platformClasspath.add(new File(s));
101 final JavaSdkVersion buildRuntimeVersion = runtime.getSecond();
102 // if compiler or debuggee version or both are unknown, let source and target be the compiler's defaults
103 if (buildRuntimeVersion != null && debuggeeVersion != null) {
104 final JavaSdkVersion minVersion = buildRuntimeVersion.ordinal() > debuggeeVersion.ordinal() ? debuggeeVersion : buildRuntimeVersion;
105 final String sourceOption = getSourceOption(minVersion.getMaxLanguageLevel());
106 options.add("-source");
107 options.add(sourceOption);
108 options.add("-target");
109 options.add(sourceOption);
112 File sourceFile = null;
113 final OutputCollector outputSink = new OutputCollector();
115 final ExternalJavacManager javacManager = getJavacManager();
116 if (javacManager == null) {
117 throw new EvaluateException("Cannot compile java code");
119 sourceFile = generateTempSourceFile(javacManager.getWorkingDir());
120 final File srcDir = sourceFile.getParentFile();
121 final Map<File, Set<File>> output = Collections.singletonMap(srcDir, Collections.singleton(srcDir));
122 DiagnosticCollector diagnostic = new DiagnosticCollector();
123 final List<String> vmOptions = Collections.emptyList();
124 final List<File> sourcePath = Collections.emptyList();
125 final Set<File> sources = Collections.singleton(sourceFile);
126 boolean compiledOK = javacManager.forkJavac(
127 javaHome, -1, vmOptions, options, platformClasspath, classpath, sourcePath, sources, output, diagnostic, outputSink, new JavacCompilerTool(), CanceledStatus.NULL
131 final StringBuilder res = new StringBuilder("Compilation failed:\n");
132 for (Diagnostic<? extends JavaFileObject> d : diagnostic.getDiagnostics()) {
133 if (d.getKind() == Diagnostic.Kind.ERROR) {
134 res.append(d.getMessage(Locale.US));
137 throw new EvaluateException(res.toString());
140 catch (EvaluateException e) {
143 catch (Exception e) {
144 throw new EvaluateException(e.getMessage());
147 if (sourceFile != null) {
148 FileUtil.delete(sourceFile);
151 return outputSink.getCompiledClasses();
155 private static String getSourceOption(@NotNull LanguageLevel languageLevel) {
156 return "1." + Integer.valueOf(3 + languageLevel.ordinal());
159 private File generateTempSourceFile(File workingDir) throws IOException {
160 final Pair<String, String> fileData = ApplicationManager.getApplication().runReadAction(new Computable<Pair<String, String>>() {
162 public Pair<String, String> compute() {
163 final PsiFile file = myData.getGeneratedInnerClass().getContainingFile();
164 return Pair.create(file.getName(), file.getText());
167 if (fileData.first == null) {
168 throw new IOException("Class file name not specified");
170 if (fileData.second == null) {
171 throw new IOException("Class source code not specified");
173 final File file = new File(workingDir, "src/"+fileData.first);
174 FileUtil.writeToFile(file, fileData.second);
178 private static final Key<ExternalJavacManager> JAVAC_MANAGER_KEY = Key.create("_external_java_compiler_manager_");
181 private ExternalJavacManager getJavacManager() throws IOException {
182 // need dedicated thread access to be able to cache the manager in the user data
183 DebuggerManagerThreadImpl.assertIsManagerThread();
185 final DebugProcessImpl debugProcess = myEvaluationContext.getDebugProcess();
186 ExternalJavacManager manager = JAVAC_MANAGER_KEY.get(debugProcess);
187 if (manager == null && debugProcess.isAttached()) {
188 final File compilerWorkingDir = getCompilerWorkingDir();
189 if (compilerWorkingDir == null) {
190 return null; // should not happen for real projects
192 final int listenPort = NetUtils.findAvailableSocketPort();
193 manager = new ExternalJavacManager(compilerWorkingDir);
194 manager.start(listenPort);
195 final ExternalJavacManager _manager = manager;
196 debugProcess.addDebugProcessListener(new DebugProcessAdapter() {
197 public void processDetached(DebugProcess process, boolean closedByUser) {
198 if (process == debugProcess) {
203 JAVAC_MANAGER_KEY.set(debugProcess, manager);
209 private File getCompilerWorkingDir() {
210 final File projectBuildDir = BuildManager.getInstance().getProjectSystemDirectory(myEvaluationContext.getProject());
211 if (projectBuildDir == null) {
214 final File root = new File(projectBuildDir, "debugger");
219 private static class DiagnosticCollector implements DiagnosticOutputConsumer {
220 private final List<Diagnostic<? extends JavaFileObject>> myDiagnostics = new ArrayList<Diagnostic<? extends JavaFileObject>>();
221 public void outputLineAvailable(String line) {
222 // for debugging purposes uncomment this line
223 //System.out.println(line);
226 public void registerImports(String className, Collection<String> imports, Collection<String> staticImports) {
230 public void javaFileLoaded(File file) {
234 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
235 myDiagnostics.add(diagnostic);
238 public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
239 return myDiagnostics;
243 private static class OutputCollector implements OutputFileConsumer {
244 private List<OutputFileObject> myClasses = new ArrayList<OutputFileObject>();
246 public void save(@NotNull OutputFileObject fileObject) {
247 myClasses.add(fileObject);
250 public List<OutputFileObject> getCompiledClasses() {