remove some more duplication
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / compiler / GroovyCompilerBase.java
1 /*
2  * Copyright 2000-2009 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
17 package org.jetbrains.plugins.groovy.compiler;
18
19 import com.intellij.compiler.CompilerConfiguration;
20 import com.intellij.compiler.impl.CompilerUtil;
21 import com.intellij.compiler.impl.FileSetCompileScope;
22 import com.intellij.compiler.impl.TranslatingCompilerFilesMonitor;
23 import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
24 import com.intellij.compiler.impl.javaCompiler.OutputItemImpl;
25 import com.intellij.compiler.make.CacheCorruptedException;
26 import com.intellij.compiler.make.DependencyCache;
27 import com.intellij.execution.ExecutionException;
28 import com.intellij.execution.configurations.JavaParameters;
29 import com.intellij.openapi.application.AccessToken;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.application.PathManager;
32 import com.intellij.openapi.compiler.CompileContext;
33 import com.intellij.openapi.compiler.CompilerMessageCategory;
34 import com.intellij.openapi.compiler.CompilerPaths;
35 import com.intellij.openapi.compiler.TranslatingCompiler;
36 import com.intellij.openapi.compiler.ex.CompileContextEx;
37 import com.intellij.openapi.diagnostic.Logger;
38 import com.intellij.openapi.fileTypes.FileType;
39 import com.intellij.openapi.fileTypes.StdFileTypes;
40 import com.intellij.openapi.module.Module;
41 import com.intellij.openapi.module.ModuleType;
42 import com.intellij.openapi.progress.ProgressIndicator;
43 import com.intellij.openapi.progress.ProgressManager;
44 import com.intellij.openapi.project.Project;
45 import com.intellij.openapi.projectRoots.JavaSdkType;
46 import com.intellij.openapi.projectRoots.JdkUtil;
47 import com.intellij.openapi.projectRoots.Sdk;
48 import com.intellij.openapi.projectRoots.SdkType;
49 import com.intellij.openapi.roots.ContentIterator;
50 import com.intellij.openapi.roots.ModuleFileIndex;
51 import com.intellij.openapi.roots.ModuleRootManager;
52 import com.intellij.openapi.roots.OrderRootType;
53 import com.intellij.openapi.roots.libraries.Library;
54 import com.intellij.openapi.util.Comparing;
55 import com.intellij.openapi.util.io.FileUtil;
56 import com.intellij.openapi.vfs.CharsetToolkit;
57 import com.intellij.openapi.vfs.LocalFileSystem;
58 import com.intellij.openapi.vfs.VfsUtil;
59 import com.intellij.openapi.vfs.VirtualFile;
60 import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
61 import com.intellij.psi.PsiFile;
62 import com.intellij.psi.PsiManager;
63 import com.intellij.util.*;
64 import com.intellij.util.cls.ClsFormatException;
65 import com.intellij.util.containers.ContainerUtil;
66 import gnu.trove.THashSet;
67 import org.jetbrains.annotations.Nullable;
68 import org.jetbrains.groovy.compiler.rt.GroovycRunner;
69 import org.jetbrains.jps.incremental.groovy.GroovycOSProcessHandler;
70 import org.jetbrains.jps.incremental.messages.BuildMessage;
71 import org.jetbrains.jps.incremental.messages.CompilerMessage;
72 import org.jetbrains.plugins.groovy.GroovyFileType;
73 import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
74 import org.jetbrains.plugins.groovy.extensions.GroovyScriptType;
75 import org.jetbrains.plugins.groovy.extensions.GroovyScriptTypeDetector;
76 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
77 import org.jetbrains.plugins.groovy.util.GroovyUtils;
78
79 import java.io.File;
80 import java.io.FileNotFoundException;
81 import java.io.IOException;
82 import java.nio.charset.Charset;
83 import java.util.*;
84
85 /**
86  * @author peter
87  */
88 public abstract class GroovyCompilerBase implements TranslatingCompiler {
89   private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.compiler.GroovyCompilerBase");
90   protected final Project myProject;
91
92   public GroovyCompilerBase(Project project) {
93     myProject = project;
94   }
95
96   protected void runGroovycCompiler(final CompileContext compileContext, final Module module,
97                                     final List<VirtualFile> toCompile,
98                                     boolean forStubs,
99                                     VirtualFile outputDir,
100                                     OutputSink sink, boolean tests) {
101     //assert !ApplicationManager.getApplication().isDispatchThread();
102     final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
103     assert sdk != null; //verified before
104     SdkType sdkType = sdk.getSdkType();
105     assert sdkType instanceof JavaSdkType;
106     final String exePath = ((JavaSdkType)sdkType).getVMExecutablePath(sdk);
107
108     final JavaParameters parameters = new JavaParameters();
109     final PathsList classPathBuilder = parameters.getClassPath();
110
111     // IMPORTANT: must be the first entry to avoid collisions
112     classPathBuilder.add(PathUtil.getJarPathForClass(GroovycRunner.class));
113
114     final ModuleChunk chunk = createChunk(module, compileContext);
115
116     final Library[] libraries = GroovyConfigUtils.getInstance().getSDKLibrariesByModule(module);
117     if (libraries.length > 0) {
118       classPathBuilder.addVirtualFiles(Arrays.asList(libraries[0].getFiles(OrderRootType.CLASSES)));
119     }
120
121     classPathBuilder.addVirtualFiles(chunk.getCompilationBootClasspathFiles(false));
122     classPathBuilder.addVirtualFiles(chunk.getCompilationClasspathFiles(false));
123     appendOutputPath(module, classPathBuilder, false);
124     if (tests) {
125       appendOutputPath(module, classPathBuilder, true);
126     }
127
128     final List<String> patchers = new SmartList<String>();
129
130     AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
131     try {
132       for (final GroovyCompilerExtension extension : GroovyCompilerExtension.EP_NAME.getExtensions()) {
133         extension.enhanceCompilationClassPath(chunk, classPathBuilder);
134         patchers.addAll(extension.getCompilationUnitPatchers(chunk));
135       }
136     }
137     finally {
138       accessToken.finish();
139     }
140
141     final boolean profileGroovyc = "true".equals(System.getProperty("profile.groovy.compiler"));
142     if (profileGroovyc) {
143       parameters.getVMParametersList().defineProperty("java.library.path", PathManager.getBinPath());
144       parameters.getVMParametersList().defineProperty("profile.groovy.compiler", "true");
145       parameters.getVMParametersList().add("-agentlib:yjpagent=disablej2ee,disablecounts,disablealloc,sessionname=GroovyCompiler");
146       classPathBuilder.add(PathManager.findFileInLibDirectory("yjp-controller-api-redist.jar").getAbsolutePath());
147     }
148
149     parameters.getVMParametersList().add("-Xmx" + GroovyCompilerConfiguration.getInstance(myProject).getHeapSize() + "m");
150     if (profileGroovyc) {
151       parameters.getVMParametersList().add("-XX:+HeapDumpOnOutOfMemoryError");
152     }
153
154     //debug
155     //parameters.getVMParametersList().add("-Xdebug"); parameters.getVMParametersList().add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239");
156
157     // Setting up process encoding according to locale
158     final ArrayList<String> list = new ArrayList<String>();
159     CompilerUtil.addLocaleOptions(list, false);
160     for (String s : list) {
161       parameters.getVMParametersList().add(s);
162     }
163
164     parameters.setMainClass(GroovycRunner.class.getName());
165
166     try {
167       final VirtualFile finalOutputDir = getMainOutput(compileContext, module, tests);
168       LOG.assertTrue(finalOutputDir != null, "No output directory for module " + module.getName() + (tests ? " tests" : " production"));
169       final Charset ideCharset = EncodingProjectManager.getInstance(myProject).getDefaultCharset();
170       String encoding = ideCharset != null && !Comparing.equal(CharsetToolkit.getDefaultSystemCharset(), ideCharset) ? ideCharset.name() : null;
171       Set<String> paths2Compile = ContainerUtil.map2Set(toCompile, new Function<VirtualFile, String>() {
172         @Override
173         public String fun(VirtualFile file) {
174           return file.getPath();
175         }
176       });
177       Map<String, String> class2Src = new HashMap<String, String>();
178
179       for (VirtualFile file : enumerateGroovyFiles(module)) {
180         if (!paths2Compile.contains(file.getPath())) {
181           for (String name : TranslatingCompilerFilesMonitor.getCompiledClassNames(file, myProject)) {
182             class2Src.put(name, file.getPath());
183           }
184         }
185       }
186
187       File fileWithParameters = GroovycOSProcessHandler
188         .fillFileWithGroovycParameters(outputDir.getPath(), paths2Compile, FileUtil.toSystemDependentName(finalOutputDir.getPath()),
189                                        class2Src, encoding, patchers);
190
191       parameters.getProgramParametersList().add(forStubs ? "stubs" : "groovyc");
192       parameters.getProgramParametersList().add(fileWithParameters.getPath());
193     }
194     catch (IOException e) {
195       LOG.error(e);
196     }
197
198
199     try {
200       Process process = JdkUtil.setupJVMCommandLine(exePath, parameters, true).createProcess();
201       GroovycOSProcessHandler processHandler = GroovycOSProcessHandler.runGroovyc(process, new Consumer<String>() {
202         @Override
203         public void consume(String s) {
204           compileContext.getProgressIndicator().setText(s);
205         }
206       });
207
208       final List<VirtualFile> toRecompile = new ArrayList<VirtualFile>();
209       for (File toRecompileFile : processHandler.getToRecompileFiles()) {
210         final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(toRecompileFile);
211         LOG.assertTrue(vFile != null);
212         toRecompile.add(vFile);
213       }
214
215       for (CompilerMessage compilerMessage : processHandler.getCompilerMessages()) {
216         final String url = compilerMessage.getSourcePath();
217         compileContext.addMessage(getMessageCategory(compilerMessage), compilerMessage.getMessageText(),
218                                   url == null ? null : VfsUtil.pathToUrl(FileUtil.toSystemIndependentName(url)),
219                                   (int)compilerMessage.getLine(),
220                                   (int)compilerMessage.getColumn());
221       }
222
223       List<GroovycOSProcessHandler.OutputItem> outputItems = processHandler.getSuccessfullyCompiled();
224       ArrayList<OutputItem> items = new ArrayList<OutputItem>();
225       if (forStubs) {
226         List<String> outputPaths = new ArrayList<String>();
227         for (final GroovycOSProcessHandler.OutputItem outputItem : outputItems) {
228           outputPaths.add(outputItem.outputPath);
229         }
230         addStubsToCompileScope(outputPaths, compileContext, module);
231       }
232       else {
233         final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
234         if (indicator != null) {
235           indicator.setText("Updating caches...");
236         }
237
238         final DependencyCache dependencyCache = ((CompileContextEx)compileContext).getDependencyCache();
239         for (GroovycOSProcessHandler.OutputItem outputItem : outputItems) {
240           final VirtualFile sourceVirtualFile = LocalFileSystem.getInstance().findFileByIoFile(new File(outputItem.sourcePath));
241           if (sourceVirtualFile == null) {
242             continue;
243           }
244           
245           if (indicator != null) {
246             indicator.setText2(sourceVirtualFile.getName());
247           }
248
249           LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(outputItem.outputPath));
250           items.add(new OutputItemImpl(outputItem.outputPath, sourceVirtualFile));
251
252           final File classFile = new File(outputItem.outputPath);
253           try {
254             dependencyCache.reparseClassFile(classFile, FileUtil.loadFileBytes(classFile));
255           }
256           catch (ClsFormatException e) {
257             LOG.error(e);
258           }
259           catch (CacheCorruptedException e) {
260             LOG.error(e);
261           }
262           catch (FileNotFoundException ignored) {
263           }
264           catch (IOException e) {
265             LOG.error(e);
266           }
267         }
268       }
269
270       sink.add(outputDir.getPath(), items, VfsUtil.toVirtualFileArray(toRecompile));
271     }
272     catch (ExecutionException e) {
273       LOG.error(e);
274     }
275   }
276
277   protected Set<VirtualFile> enumerateGroovyFiles(final Module module) {
278     final Set<VirtualFile> moduleClasses = new THashSet<VirtualFile>();
279     ModuleRootManager.getInstance(module).getFileIndex().iterateContent(new ContentIterator() {
280       public boolean processFile(final VirtualFile vfile) {
281         if (!vfile.isDirectory() &&
282             GroovyFileType.GROOVY_FILE_TYPE.equals(vfile.getFileType())) {
283
284           AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
285
286           try {
287             if (PsiManager.getInstance(myProject).findFile(vfile) instanceof GroovyFile) {
288               moduleClasses.add(vfile);
289             }
290           }
291           finally {
292             accessToken.finish();
293           }
294         }
295         return true;
296       }
297     });
298     return moduleClasses;
299   }
300
301   protected static void addStubsToCompileScope(List<String> outputPaths, CompileContext compileContext, Module module) {
302     List<VirtualFile> stubFiles = new ArrayList<VirtualFile>();
303     for (String outputPath : outputPaths) {
304       final File stub = new File(outputPath);
305       CompilerUtil.refreshIOFile(stub);
306       final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(stub);
307       ContainerUtil.addIfNotNull(file, stubFiles);
308     }
309     ((CompileContextEx)compileContext).addScope(new FileSetCompileScope(stubFiles, new Module[]{module}));
310   }
311
312   @Nullable
313   protected static VirtualFile getMainOutput(CompileContext compileContext, Module module, boolean tests) {
314     return tests ? compileContext.getModuleOutputDirectoryForTests(module) : compileContext.getModuleOutputDirectory(module);
315   }
316
317   private static CompilerMessageCategory getMessageCategory(CompilerMessage compilerMessage) {
318     BuildMessage.Kind category = compilerMessage.getKind();
319
320     if (BuildMessage.Kind.ERROR.equals(category)) return CompilerMessageCategory.ERROR;
321     if (BuildMessage.Kind.INFO.equals(category)) return CompilerMessageCategory.INFORMATION;
322     if (BuildMessage.Kind.WARNING.equals(category)) return CompilerMessageCategory.WARNING;
323
324     return CompilerMessageCategory.ERROR;
325   }
326
327   private static void appendOutputPath(Module module, PathsList compileClasspath, final boolean forTestClasses) {
328     String output = CompilerPaths.getModuleOutputPath(module, forTestClasses);
329     if (output != null) {
330       compileClasspath.add(FileUtil.toSystemDependentName(output));
331     }
332   }
333
334   private static ModuleChunk createChunk(Module module, CompileContext context) {
335     return new ModuleChunk((CompileContextEx)context, new Chunk<Module>(module), Collections.<Module, List<VirtualFile>>emptyMap());
336   }
337
338   public void compile(final CompileContext compileContext, Chunk<Module> moduleChunk, final VirtualFile[] virtualFiles, OutputSink sink) {
339     Map<Module, List<VirtualFile>> mapModulesToVirtualFiles;
340     if (moduleChunk.getNodes().size() == 1) {
341       mapModulesToVirtualFiles = Collections.singletonMap(moduleChunk.getNodes().iterator().next(), Arrays.asList(virtualFiles));
342     }
343     else {
344       mapModulesToVirtualFiles = CompilerUtil.buildModuleToFilesMap(compileContext, virtualFiles);
345     }
346     for (final Module module : moduleChunk.getNodes()) {
347       final List<VirtualFile> moduleFiles = mapModulesToVirtualFiles.get(module);
348       if (moduleFiles == null) {
349         continue;
350       }
351
352       final ModuleFileIndex index = ModuleRootManager.getInstance(module).getFileIndex();
353       final List<VirtualFile> toCompile = new ArrayList<VirtualFile>();
354       final List<VirtualFile> toCompileTests = new ArrayList<VirtualFile>();
355       final CompilerConfiguration configuration = CompilerConfiguration.getInstance(myProject);
356       final PsiManager psiManager = PsiManager.getInstance(myProject);
357
358       if (GroovyUtils.isAcceptableModuleType(ModuleType.get(module))) {
359         for (final VirtualFile file : moduleFiles) {
360           if (shouldCompile(file, configuration, psiManager)) {
361             (index.isInTestSourceContent(file) ? toCompileTests : toCompile).add(file);
362           }
363         }
364       }
365
366       if (!toCompile.isEmpty()) {
367         compileFiles(compileContext, module, toCompile, sink, false);
368       }
369       if (!toCompileTests.isEmpty()) {
370         compileFiles(compileContext, module, toCompileTests, sink, true);
371       }
372
373     }
374
375   }
376
377   private static boolean shouldCompile(final VirtualFile file, CompilerConfiguration configuration, final PsiManager manager) {
378     if (configuration.isResourceFile(file)) {
379       return false;
380     }
381
382     final FileType fileType = file.getFileType();
383     if (fileType == GroovyFileType.GROOVY_FILE_TYPE) {
384       AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock();
385
386       try {
387         PsiFile psiFile = manager.findFile(file);
388         if (psiFile instanceof GroovyFile && ((GroovyFile)psiFile).isScript()) {
389           final GroovyScriptType scriptType = GroovyScriptTypeDetector.getScriptType((GroovyFile)psiFile);
390           return scriptType.shouldBeCompiled((GroovyFile)psiFile);
391         }
392         return true;
393       }
394       finally {
395         accessToken.finish();
396       }
397     }
398
399     return fileType == StdFileTypes.JAVA;
400   }
401
402   protected abstract void compileFiles(CompileContext compileContext, Module module,
403                                        List<VirtualFile> toCompile, OutputSink sink, boolean tests);
404
405   public boolean isCompilableFile(VirtualFile file, CompileContext context) {
406     final boolean result = GroovyFileType.GROOVY_FILE_TYPE.equals(file.getFileType());
407     if (result && LOG.isDebugEnabled()) {
408       LOG.debug("compilable file: " + file.getPath());
409     }
410     return result;
411   }
412 }