Groovy-Eclipse compiler support back-end (IDEA-52379)
[idea/community.git] / plugins / groovy / jps-plugin / src / org / jetbrains / jps / incremental / groovy / GroovyBuilder.java
1 /*
2  * Copyright 2000-2012 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 org.jetbrains.jps.incremental.groovy;
17
18
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.util.SystemInfo;
23 import com.intellij.openapi.util.io.FileUtil;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.util.ArrayUtil;
26 import com.intellij.util.Consumer;
27 import com.intellij.util.Function;
28 import com.intellij.util.SystemProperties;
29 import com.intellij.util.containers.ContainerUtilRt;
30 import com.intellij.util.lang.UrlClassLoader;
31 import gnu.trove.THashMap;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 import org.jetbrains.groovy.compiler.rt.GroovyRtConstants;
35 import org.jetbrains.jps.ModuleChunk;
36 import org.jetbrains.jps.ProjectPaths;
37 import org.jetbrains.jps.builders.BuildRootIndex;
38 import org.jetbrains.jps.builders.DirtyFilesHolder;
39 import org.jetbrains.jps.builders.FileProcessor;
40 import org.jetbrains.jps.builders.java.JavaBuilderUtil;
41 import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
42 import org.jetbrains.jps.builders.java.dependencyView.Callbacks;
43 import org.jetbrains.jps.builders.java.dependencyView.Mappings;
44 import org.jetbrains.jps.builders.storage.SourceToOutputMapping;
45 import org.jetbrains.jps.cmdline.ClasspathBootstrap;
46 import org.jetbrains.jps.cmdline.ProjectDescriptor;
47 import org.jetbrains.jps.incremental.*;
48 import org.jetbrains.jps.incremental.java.ClassPostProcessor;
49 import org.jetbrains.jps.incremental.java.JavaBuilder;
50 import org.jetbrains.jps.incremental.messages.BuildMessage;
51 import org.jetbrains.jps.incremental.messages.CompilerMessage;
52 import org.jetbrains.jps.incremental.messages.ProgressMessage;
53 import org.jetbrains.jps.javac.OutputFileObject;
54 import org.jetbrains.jps.model.JpsDummyElement;
55 import org.jetbrains.jps.model.java.JpsJavaExtensionService;
56 import org.jetbrains.jps.model.java.JpsJavaSdkType;
57 import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
58 import org.jetbrains.jps.model.library.sdk.JpsSdk;
59 import org.jetbrains.jps.service.JpsServiceManager;
60 import org.jetbrains.jps.service.SharedThreadPool;
61 import org.jetbrains.org.objectweb.asm.ClassReader;
62 import org.jetbrains.org.objectweb.asm.ClassVisitor;
63 import org.jetbrains.org.objectweb.asm.Opcodes;
64
65 import java.io.File;
66 import java.io.IOException;
67 import java.util.*;
68 import java.util.concurrent.Future;
69
70 /**
71  * @author Eugene Zhuravlev
72  *         Date: 10/25/11
73  */
74 public class GroovyBuilder extends ModuleLevelBuilder {
75   private static final int ourOptimizeThreshold = Integer.parseInt(System.getProperty("groovyc.optimized.class.loading.threshold", "10"));
76   private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.groovy.GroovyBuilder");
77   private static final Key<Boolean> CHUNK_REBUILD_ORDERED = Key.create("CHUNK_REBUILD_ORDERED");
78   private static final Key<Map<String, String>> STUB_TO_SRC = Key.create("STUB_TO_SRC");
79   private static final Key<Boolean> FILES_MARKED_DIRTY_FOR_NEXT_ROUND = Key.create("SRC_MARKED_DIRTY");
80   private static final String GROOVY_EXTENSION = "groovy";
81   private static final String GPP_EXTENSION = "gpp";
82   private final boolean myForStubs;
83   private final String myBuilderName;
84
85   public GroovyBuilder(boolean forStubs) {
86     super(forStubs ? BuilderCategory.SOURCE_GENERATOR : BuilderCategory.OVERWRITING_TRANSLATOR);
87     myForStubs = forStubs;
88     myBuilderName = "Groovy " + (forStubs ? "stub generator" : "compiler");
89   }
90
91   static {
92     JavaBuilder.registerClassPostProcessor(new RecompileStubSources());
93   }
94
95   public ModuleLevelBuilder.ExitCode build(final CompileContext context,
96                                            ModuleChunk chunk,
97                                            DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
98                                            OutputConsumer outputConsumer) throws ProjectBuildException {
99     long start = 0;
100     try {
101       JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject());
102
103       final List<File> toCompile = collectChangedFiles(context, dirtyFilesHolder, myForStubs, false);
104       if (toCompile.isEmpty()) {
105         return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.NOTHING_DONE;
106       }
107       if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
108         LOG.info("forStubs=" + myForStubs);
109       }
110
111       Map<ModuleBuildTarget, String> finalOutputs = getCanonicalModuleOutputs(context, chunk, this);
112       if (finalOutputs == null) {
113         return ExitCode.ABORT;
114       }
115
116       start = System.currentTimeMillis();
117       final Set<String> toCompilePaths = getPathsToCompile(toCompile);
118
119       JpsSdk<JpsDummyElement> jdk = getJdk(chunk);
120       String version = jdk == null ? SystemInfo.JAVA_RUNTIME_VERSION : jdk.getVersionString();
121       boolean mayDependOnUtilJar = version != null && StringUtil.compareVersionNumbers(version, "1.6") >= 0;
122       boolean optimizeClassLoading = mayDependOnUtilJar && ourOptimizeThreshold != 0 && toCompilePaths.size() >= ourOptimizeThreshold;
123
124       Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs);
125
126       final String encoding = context.getProjectDescriptor().getEncodingConfiguration().getPreferredModuleChunkEncoding(chunk);
127       List<String> patchers = new ArrayList<String>();
128
129       for (GroovyBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) {
130         patchers.addAll(extension.getCompilationUnitPatchers(context, chunk));
131       }
132
133       Map<ModuleBuildTarget, String> generationOutputs = myForStubs ? getStubGenerationOutputs(chunk, context) : finalOutputs;
134       String compilerOutput = generationOutputs.get(chunk.representativeTarget());
135
136       Collection<String> classpath = generateClasspath(context, chunk);
137       if (LOG.isDebugEnabled()) {
138         LOG.debug("Optimized class loading: " + optimizeClassLoading);
139         LOG.debug("Groovyc classpath: " + classpath);
140       }
141
142       final File tempFile = GroovycOSProcessHandler.fillFileWithGroovycParameters(
143         compilerOutput, toCompilePaths, finalOutputs.values(), class2Src, encoding, patchers,
144         optimizeClassLoading ? StringUtil.join(classpath, File.pathSeparator) : ""
145       );
146       final GroovycOSProcessHandler handler = runGroovyc(context, chunk, tempFile, settings, classpath, optimizeClassLoading);
147
148       Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>>
149         compiled = processCompiledFiles(context, chunk, generationOutputs, compilerOutput, handler.getSuccessfullyCompiled());
150
151       if (checkChunkRebuildNeeded(context, handler)) {
152         return ExitCode.CHUNK_REBUILD_REQUIRED;
153       }
154
155       if (myForStubs) {
156         addStubRootsToJavacSourcePath(context, generationOutputs);
157         rememberStubSources(context, compiled);
158       }
159
160       for (CompilerMessage message : handler.getCompilerMessages(chunk.representativeTarget().getModule().getName())) {
161         context.processMessage(message);
162       }
163
164       if (!myForStubs && updateDependencies(context, chunk, dirtyFilesHolder, toCompile, compiled, outputConsumer, this)) {
165         return ExitCode.ADDITIONAL_PASS_REQUIRED;
166       }
167       return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.OK;
168     }
169     catch (Exception e) {
170       throw new ProjectBuildException(e);
171     }
172     finally {
173       if (start > 0 && LOG.isDebugEnabled()) {
174         LOG.debug(myBuilderName + " took " + (System.currentTimeMillis() - start) + " on " + chunk.getName());
175       }
176       if (!myForStubs) {
177         FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, null);
178       }
179     }
180   }
181
182   private Boolean hasFilesToCompileForNextRound(CompileContext context) {
183     return !myForStubs && FILES_MARKED_DIRTY_FOR_NEXT_ROUND.get(context, Boolean.FALSE);
184   }
185
186   private static Set<String> getPathsToCompile(List<File> toCompile) {
187     final Set<String> toCompilePaths = new LinkedHashSet<String>();
188     for (File file : toCompile) {
189       if (LOG.isDebugEnabled()) {
190         LOG.debug("Path to compile: " + file.getPath());
191       }
192       toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
193     }
194     return toCompilePaths;
195   }
196
197   private GroovycOSProcessHandler runGroovyc(final CompileContext context,
198                                              final ModuleChunk chunk,
199                                              File tempFile,
200                                              final JpsGroovySettings settings,
201                                              Collection<String> compilationClassPath,
202                                              boolean optimizeClassLoading) throws IOException {
203     List<String> classpath = new ArrayList<String>();
204     if (optimizeClassLoading) {
205       classpath.add(getGroovyRtRoot().getPath());
206       classpath.add(ClasspathBootstrap.getResourcePath(Function.class));
207       classpath.add(ClasspathBootstrap.getResourcePath(UrlClassLoader.class));
208       classpath.add(ClasspathBootstrap.getResourceFile(THashMap.class).getPath());
209     } else {
210       classpath.addAll(compilationClassPath);
211     }
212
213     List<String> programParams = ContainerUtilRt.newArrayList(optimizeClassLoading ? GroovyRtConstants.OPTIMIZE : "do_not_optimize",
214                                                               myForStubs ? "stubs" : "groovyc",
215                                                               tempFile.getPath());
216     if (settings.invokeDynamic) {
217       programParams.add("--indy");
218     }
219
220     List<String> vmParams = ContainerUtilRt.newArrayList();
221     vmParams.add("-Xmx" + settings.heapSize + "m");
222     vmParams.add("-Dfile.encoding=" + System.getProperty("file.encoding"));
223     //vmParams.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239");
224
225     String grapeRoot = System.getProperty(GroovycOSProcessHandler.GRAPE_ROOT);
226     if (grapeRoot != null) {
227       vmParams.add("-D" + GroovycOSProcessHandler.GRAPE_ROOT + "=" + grapeRoot);
228     }
229
230     final List<String> cmd = ExternalProcessUtil.buildJavaCommandLine(
231       getJavaExecutable(chunk),
232       "org.jetbrains.groovy.compiler.rt.GroovycRunner",
233       Collections.<String>emptyList(), classpath,
234       vmParams,
235       programParams
236     );
237
238     final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmd));
239     final Consumer<String> updater = new Consumer<String>() {
240       public void consume(String s) {
241         context.processMessage(new ProgressMessage(s + " [" + chunk.getPresentableShortName() + "]"));
242       }
243     };
244     final GroovycOSProcessHandler handler = new GroovycOSProcessHandler(process, updater) {
245       @Override
246       protected Future<?> executeOnPooledThread(Runnable task) {
247         return SharedThreadPool.getInstance().executeOnPooledThread(task);
248       }
249     };
250
251     handler.startNotify();
252     handler.waitFor();
253     return handler;
254   }
255
256   private static boolean checkChunkRebuildNeeded(CompileContext context, GroovycOSProcessHandler handler) {
257     if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context) || !handler.shouldRetry()) {
258       return false;
259     }
260
261     if (CHUNK_REBUILD_ORDERED.get(context) != null) {
262       CHUNK_REBUILD_ORDERED.set(context, null);
263       return false;
264     }
265
266     CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE);
267     LOG.info("Order chunk rebuild");
268     return true;
269   }
270
271   private static void rememberStubSources(CompileContext context, Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> compiled) {
272     Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
273     if (stubToSrc == null) {
274       STUB_TO_SRC.set(context, stubToSrc = new HashMap<String, String>());
275     }
276     for (Collection<GroovycOSProcessHandler.OutputItem> items : compiled.values()) {
277       for (GroovycOSProcessHandler.OutputItem item : items) {
278         stubToSrc.put(FileUtil.toSystemIndependentName(item.outputPath), item.sourcePath);
279       }
280     }
281   }
282
283   private static void addStubRootsToJavacSourcePath(CompileContext context, Map<ModuleBuildTarget, String> generationOutputs) {
284     final BuildRootIndex rootsIndex = context.getProjectDescriptor().getBuildRootIndex();
285     for (ModuleBuildTarget target : generationOutputs.keySet()) {
286       File root = new File(generationOutputs.get(target));
287       rootsIndex.associateTempRoot(context, target, new JavaSourceRootDescriptor(root, target, true, true, "", Collections.<File>emptySet()));
288     }
289   }
290
291   public static Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> processCompiledFiles(CompileContext context,
292                                                                                                              ModuleChunk chunk,
293                                                                                                              Map<ModuleBuildTarget, String> generationOutputs,
294                                                                                                              String compilerOutput,
295                                                                                                              List<GroovycOSProcessHandler.OutputItem> successfullyCompiled)
296     throws IOException {
297     ProjectDescriptor pd = context.getProjectDescriptor();
298
299     final Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> compiled = new THashMap<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>>();
300     for (final GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
301       if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
302         LOG.info("compiled=" + item);
303       }
304       final JavaSourceRootDescriptor rd = pd.getBuildRootIndex().findJavaRootDescriptor(context, new File(item.sourcePath));
305       if (rd != null) {
306         final String outputPath = ensureCorrectOutput(chunk, item, generationOutputs, compilerOutput, rd.target);
307
308         Collection<GroovycOSProcessHandler.OutputItem> items = compiled.get(rd.target);
309         if (items == null) {
310           items = new ArrayList<GroovycOSProcessHandler.OutputItem>();
311           compiled.put(rd.target, items);
312         }
313
314         items.add(new GroovycOSProcessHandler.OutputItem(outputPath, item.sourcePath));
315       }
316       else {
317         if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
318           LOG.info("No java source root descriptor for the item found =" + item);
319         }
320       }
321     }
322     if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
323       LOG.info("Chunk " + chunk + " compilation finished");
324     }
325     return compiled;
326   }
327
328   @Override
329   public void chunkBuildFinished(CompileContext context, ModuleChunk chunk) {
330     JavaBuilderUtil.cleanupChunkResources(context);
331     STUB_TO_SRC.set(context, null);
332   }
333
334   private static Map<ModuleBuildTarget, String> getStubGenerationOutputs(ModuleChunk chunk, CompileContext context) throws IOException {
335     Map<ModuleBuildTarget, String> generationOutputs = new HashMap<ModuleBuildTarget, String>();
336     File commonRoot = new File(context.getProjectDescriptor().dataManager.getDataPaths().getDataStorageRoot(), "groovyStubs");
337     for (ModuleBuildTarget target : chunk.getTargets()) {
338       File targetRoot = new File(commonRoot, target.getModule().getName() + File.separator + target.getTargetType().getTypeId());
339       if (!FileUtil.delete(targetRoot)) {
340         throw new IOException("External make cannot clean " + targetRoot.getPath());
341       }
342       if (!targetRoot.mkdirs()) {
343         throw new IOException("External make cannot create " + targetRoot.getPath());
344       }
345       generationOutputs.put(target, targetRoot.getPath());
346     }
347     return generationOutputs;
348   }
349
350   @Nullable
351   public static Map<ModuleBuildTarget, String> getCanonicalModuleOutputs(CompileContext context, ModuleChunk chunk, Builder builder) {
352     Map<ModuleBuildTarget, String> finalOutputs = new HashMap<ModuleBuildTarget, String>();
353     for (ModuleBuildTarget target : chunk.getTargets()) {
354       File moduleOutputDir = target.getOutputDir();
355       if (moduleOutputDir == null) {
356         context.processMessage(new CompilerMessage(builder.getPresentableName(), BuildMessage.Kind.ERROR, "Output directory not specified for module " + target.getModule().getName()));
357         return null;
358       }
359       //noinspection ResultOfMethodCallIgnored
360       moduleOutputDir.mkdirs();
361       String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
362       assert moduleOutputPath != null;
363       finalOutputs.put(target, moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/");
364     }
365     return finalOutputs;
366   }
367
368   private static String ensureCorrectOutput(ModuleChunk chunk,
369                                             GroovycOSProcessHandler.OutputItem item,
370                                             Map<ModuleBuildTarget, String> generationOutputs,
371                                             String compilerOutput,
372                                             @NotNull ModuleBuildTarget srcTarget) throws IOException {
373     if (chunk.getModules().size() > 1 && !srcTarget.equals(chunk.representativeTarget())) {
374       File output = new File(item.outputPath);
375
376       String srcTargetOutput = generationOutputs.get(srcTarget);
377       if (srcTargetOutput == null) {
378         LOG.info("No output for " + srcTarget + "; outputs=" + generationOutputs + "; targets = " + chunk.getTargets());
379         return item.outputPath;
380       }
381
382       //todo honor package prefixes
383       File correctRoot = new File(srcTargetOutput);
384       File correctOutput = new File(correctRoot, FileUtil.getRelativePath(new File(compilerOutput), output));
385
386       FileUtil.rename(output, correctOutput);
387       return correctOutput.getPath();
388     }
389     return item.outputPath;
390   }
391
392   private static String getJavaExecutable(ModuleChunk chunk) {
393     JpsSdk<?> sdk = getJdk(chunk);
394     return sdk != null ? JpsJavaSdkType.getJavaExecutable(sdk) : SystemProperties.getJavaHome() + "/bin/java";
395   }
396
397   private static JpsSdk<JpsDummyElement> getJdk(ModuleChunk chunk) {
398     return chunk.getModules().iterator().next().getSdk(JpsJavaSdkType.INSTANCE);
399   }
400
401   static List<File> collectChangedFiles(CompileContext context,
402                                         DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
403                                         final boolean forStubs, final boolean forEclipse)
404     throws IOException {
405
406     final JpsJavaCompilerConfiguration configuration =
407       JpsJavaExtensionService.getInstance().getCompilerConfiguration(context.getProjectDescriptor().getProject());
408     assert configuration != null;
409
410     final JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject());
411
412     final List<File> toCompile = new ArrayList<File>();
413     dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
414       public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor sourceRoot) throws IOException {
415         final String path = file.getPath();
416         //todo file type check
417         if ((isGroovyFile(path) || forEclipse && path.endsWith(".java")) &&
418             !configuration.isResourceFile(file, sourceRoot.root)) {
419           if (forStubs && settings.isExcludedFromStubGeneration(file)) {
420             return true;
421           }
422
423           toCompile.add(file);
424         }
425         return true;
426       }
427     });
428     return toCompile;
429   }
430
431   public static boolean updateDependencies(CompileContext context,
432                                             ModuleChunk chunk,
433                                             DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
434                                             List<File> toCompile,
435                                             Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> successfullyCompiled,
436                                             OutputConsumer outputConsumer, Builder builder) throws IOException {
437     final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
438     final List<File> successfullyCompiledFiles = new ArrayList<File>();
439     if (!successfullyCompiled.isEmpty()) {
440
441       final Callbacks.Backend callback = delta.getCallback();
442
443       for (Map.Entry<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> entry : successfullyCompiled.entrySet()) {
444         final ModuleBuildTarget target = entry.getKey();
445         final Collection<GroovycOSProcessHandler.OutputItem> compiled = entry.getValue();
446         for (GroovycOSProcessHandler.OutputItem item : compiled) {
447           final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
448           final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
449           final File outputFile = new File(outputPath);
450           final File srcFile = new File(sourcePath);
451           try {
452             final byte[] bytes = FileUtil.loadFileBytes(outputFile);
453             outputConsumer.registerCompiledClass(
454               target,
455               new CompiledClass(outputFile, srcFile, readClassName(bytes), new BinaryContent(bytes))
456             );
457             callback.associate(outputPath, sourcePath, new ClassReader(bytes));
458           }
459           catch (Throwable e) {
460             // need this to make sure that unexpected errors in, for example, ASM will not ruin the compilation
461             final String message = "Class dependency information may be incomplete! Error parsing generated class " + item.outputPath;
462             LOG.info(message, e);
463             context.processMessage(new CompilerMessage(
464               builder.getPresentableName(), BuildMessage.Kind.WARNING, message + "\n" + CompilerMessage.getTextFromThrowable(e), sourcePath)
465             );
466           }
467           successfullyCompiledFiles.add(srcFile);
468         }
469       }
470     }
471
472     return JavaBuilderUtil.updateMappings(context, delta, dirtyFilesHolder, chunk, toCompile, successfullyCompiledFiles);
473   }
474
475   private static String readClassName(byte[] classBytes) throws IOException{
476     final Ref<String> nameRef = Ref.create(null);
477     new ClassReader(classBytes).accept(new ClassVisitor(Opcodes.ASM5) {
478       public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
479         nameRef.set(name.replace('/', '.'));
480       }
481     }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
482     return nameRef.get();
483   }
484
485   private static Collection<String> generateClasspath(CompileContext context, ModuleChunk chunk) {
486     final Set<String> cp = new LinkedHashSet<String>();
487     //groovy_rt.jar
488     // IMPORTANT! must be the first in classpath
489     cp.add(getGroovyRtRoot().getPath());
490
491     for (File file : ProjectPaths.getCompilationClasspathFiles(chunk, chunk.containsTests(), false, false)) {
492       cp.add(FileUtil.toCanonicalPath(file.getPath()));
493     }
494
495     for (GroovyBuilderExtension extension : JpsServiceManager.getInstance().getExtensions(GroovyBuilderExtension.class)) {
496       cp.addAll(extension.getCompilationClassPath(context, chunk));
497     }
498
499     return cp;
500   }
501
502   private static File getGroovyRtRoot() {
503     File root = ClasspathBootstrap.getResourceFile(GroovyBuilder.class);
504     if (root.isFile()) {
505       return new File(root.getParentFile(), "groovy_rt.jar");
506     }
507     return new File(root.getParentFile(), "groovy_rt");
508   }
509
510   public static boolean isGroovyFile(String path) {
511     return path.endsWith("." + GROOVY_EXTENSION) || path.endsWith("." + GPP_EXTENSION);
512   }
513
514   @Override
515   public List<String> getCompilableFileExtensions() {
516     return Arrays.asList(GROOVY_EXTENSION, GPP_EXTENSION);
517   }
518
519   private static Map<String, String> buildClassToSourceMap(ModuleChunk chunk, CompileContext context, Set<String> toCompilePaths, Map<ModuleBuildTarget, String> finalOutputs) throws IOException {
520     final Map<String, String> class2Src = new HashMap<String, String>();
521     JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(
522       context.getProjectDescriptor().getProject());
523     for (ModuleBuildTarget target : chunk.getTargets()) {
524       String moduleOutputPath = finalOutputs.get(target);
525       final SourceToOutputMapping srcToOut = context.getProjectDescriptor().dataManager.getSourceToOutputMap(target);
526       for (String src : srcToOut.getSources()) {
527         if (!toCompilePaths.contains(src) && isGroovyFile(src) &&
528             !configuration.getCompilerExcludes().isExcluded(new File(src))) {
529           final Collection<String> outs = srcToOut.getOutputs(src);
530           if (outs != null) {
531             for (String out : outs) {
532               if (out.endsWith(".class") && out.startsWith(moduleOutputPath)) {
533                 final String className = out.substring(moduleOutputPath.length(), out.length() - ".class".length()).replace('/', '.');
534                 class2Src.put(className, src);
535               }
536             }
537           }
538         }
539       }
540     }
541     return class2Src;
542   }
543
544   @Override
545   public String toString() {
546     return myBuilderName;
547   }
548
549   @NotNull
550   public String getPresentableName() {
551     return myBuilderName;
552   }
553
554   private static class RecompileStubSources implements ClassPostProcessor {
555
556     public void process(CompileContext context, OutputFileObject out) {
557       Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
558       if (stubToSrc == null) {
559         return;
560       }
561       File src = out.getSourceFile();
562       if (src == null) {
563         return;
564       }
565       String groovy = stubToSrc.get(FileUtil.toSystemIndependentName(src.getPath()));
566       if (groovy == null) {
567         return;
568       }
569       try {
570         final File groovyFile = new File(groovy);
571         if (!FSOperations.isMarkedDirty(context, groovyFile)) {
572           FSOperations.markDirty(context, groovyFile);
573           FILES_MARKED_DIRTY_FOR_NEXT_ROUND.set(context, Boolean.TRUE);
574         }
575       }
576       catch (IOException e) {
577         LOG.error(e);
578       }
579     }
580   }
581 }