IDEA-142099 ComiplingEvaluator does not use dependencies
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / ui / impl / watch / CompilingEvaluatorImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.debugger.ui.impl.watch;
17
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;
50
51 import javax.tools.*;
52 import java.io.File;
53 import java.io.IOException;
54 import java.util.*;
55
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 {
58
59   private final EvaluationContextImpl myEvaluationContext;
60
61   public CompilingEvaluatorImpl(EvaluationContextImpl evaluationContext, @NotNull PsiElement context, @NotNull ExtractLightMethodObjectHandler.ExtractedData data) {
62     super(context, data);
63     myEvaluationContext = evaluationContext;
64   }
65
66   @Override
67   @NotNull
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>() {
71       @Override
72       public Module compute() {
73         return ModuleUtilCore.findModuleForPsiElement(myPsiContext);
74       }
75     });
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();
81     }
82     if (javaHome == null) {
83       throw new EvaluateException("Was not able to determine JDK for current evaluation context");
84     }
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");
88     options.add("UTF-8");
89     final List<File> platformClasspath = new ArrayList<File>();
90     final List<File> classpath = new ArrayList<File>();
91     if (module != null) {
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));
95       }
96       for (String s : rootManager.orderEntries().compileOnly().sdkOnly().getPathsList().getPathList()) {
97         platformClasspath.add(new File(s));
98       }
99     }
100
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);
110     }
111
112     File sourceFile = null;
113     final OutputCollector outputSink = new OutputCollector();
114     try {
115       final ExternalJavacManager javacManager = getJavacManager();
116       if (javacManager == null) {
117         throw new EvaluateException("Cannot compile java code");
118       }
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
128       );
129
130       if (!compiledOK) {
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));
135           }
136         }
137         throw new EvaluateException(res.toString());
138       }
139     }
140     catch (EvaluateException e) {
141       throw e;
142     }
143     catch (Exception e) {
144       throw new EvaluateException(e.getMessage());
145     }
146     finally {
147       if (sourceFile != null) {
148         FileUtil.delete(sourceFile);
149       }
150     }
151     return outputSink.getCompiledClasses();
152   }
153
154   @NotNull
155   private static String getSourceOption(@NotNull LanguageLevel languageLevel) {
156     return "1." + Integer.valueOf(3 + languageLevel.ordinal());
157   }
158
159   private File generateTempSourceFile(File workingDir) throws IOException {
160     final Pair<String, String> fileData = ApplicationManager.getApplication().runReadAction(new Computable<Pair<String, String>>() {
161       @Override
162       public Pair<String, String> compute() {
163         final PsiFile file = myData.getGeneratedInnerClass().getContainingFile();
164         return Pair.create(file.getName(), file.getText());
165       }
166     });
167     if (fileData.first == null) {
168       throw new IOException("Class file name not specified");
169     }
170     if (fileData.second == null) {
171       throw new IOException("Class source code not specified");
172     }
173     final File file = new File(workingDir, "src/"+fileData.first);
174     FileUtil.writeToFile(file, fileData.second);
175     return file;
176   }
177
178   private static final Key<ExternalJavacManager> JAVAC_MANAGER_KEY = Key.create("_external_java_compiler_manager_");
179
180   @Nullable
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();
184
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
191       }
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) {
199             _manager.stop();
200           }
201         }
202       });
203       JAVAC_MANAGER_KEY.set(debugProcess, manager);
204     }
205     return manager;
206   }
207
208   @Nullable
209   private File getCompilerWorkingDir() {
210     final File projectBuildDir = BuildManager.getInstance().getProjectSystemDirectory(myEvaluationContext.getProject());
211     if (projectBuildDir == null) {
212       return null;
213     }
214     final File root = new File(projectBuildDir, "debugger");
215     root.mkdirs();
216     return root;
217   }
218
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);
224     }
225
226     public void registerImports(String className, Collection<String> imports, Collection<String> staticImports) {
227       // ignore
228     }
229
230     public void javaFileLoaded(File file) {
231       // ignore
232     }
233
234     public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
235       myDiagnostics.add(diagnostic);
236     }
237
238     public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() {
239       return myDiagnostics;
240     }
241   }
242
243   private static class OutputCollector implements OutputFileConsumer {
244     private List<OutputFileObject> myClasses = new ArrayList<OutputFileObject>();
245
246     public void save(@NotNull OutputFileObject fileObject) {
247       myClasses.add(fileObject);
248     }
249
250     public List<OutputFileObject> getCompiledClasses() {
251       return myClasses;
252     }
253   }
254 }