0364cd2f4dd3bef74c505800f96493ecaa265b81
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / incremental / groovy / GroovyBuilder.java
1 package org.jetbrains.jps.incremental.groovy;
2
3
4 import com.intellij.openapi.util.Comparing;
5 import com.intellij.openapi.util.io.FileUtil;
6 import com.intellij.util.ArrayUtil;
7 import com.intellij.util.Consumer;
8 import com.intellij.util.SystemProperties;
9 import groovy.util.CharsetToolkit;
10 import org.jetbrains.ether.dependencyView.Callbacks;
11 import org.jetbrains.ether.dependencyView.Mappings;
12 import org.jetbrains.groovy.compiler.rt.GroovyCompilerWrapper;
13 import org.jetbrains.jps.ClasspathKind;
14 import org.jetbrains.jps.Module;
15 import org.jetbrains.jps.ModuleChunk;
16 import org.jetbrains.jps.incremental.*;
17 import org.jetbrains.jps.incremental.java.JavaBuilder;
18 import org.jetbrains.jps.incremental.messages.CompilerMessage;
19 import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
20 import org.jetbrains.jps.incremental.messages.ProgressMessage;
21 import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
22 import org.jetbrains.jps.server.ClasspathBootstrap;
23 import org.objectweb.asm.ClassReader;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.*;
28
29 /**
30  * @author Eugene Zhuravlev
31  *         Date: 10/25/11
32  */
33 public class GroovyBuilder extends ModuleLevelBuilder {
34   public static final String BUILDER_NAME = "groovy";
35   private final boolean myForStubs;
36   private final String myBuilderName;
37
38   public GroovyBuilder(boolean forStubs) {
39     myForStubs = forStubs;
40     myBuilderName = BUILDER_NAME + (forStubs ? "-stubs" : "-classes");
41   }
42
43   public String getName() {
44     return myBuilderName;
45   }
46
47   public ModuleLevelBuilder.ExitCode build(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
48     ExitCode exitCode = ExitCode.OK;
49     try {
50       final List<File> toCompile = collectChangedFiles(context, chunk);
51       if (toCompile.isEmpty()) {
52         return exitCode;
53       }
54
55       String moduleOutput = getModuleOutput(context, chunk);
56       String compilerOutput = getCompilerOutput(context, moduleOutput);
57
58       final Set<String> toCompilePaths = new LinkedHashSet<String>();
59       for (File file : toCompile) {
60         toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
61       }
62       
63       Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, moduleOutput);
64
65       String ideCharset = chunk.getProject().getProjectCharset();
66       String encoding = !Comparing.equal(CharsetToolkit.getDefaultSystemCharset().name(), ideCharset) ? ideCharset : null;
67       List<String> patchers = Collections.emptyList(); //todo patchers
68       final File tempFile = GroovycOSProcessHandler.fillFileWithGroovycParameters(
69         compilerOutput, toCompilePaths, FileUtil.toSystemDependentName(moduleOutput), class2Src, encoding, patchers
70       );
71
72       // todo CompilerUtil.addLocaleOptions()
73       //todo different outputs in a chunk
74       //todo xmx
75       //todo module jdk path
76       final List<String> cmd = ExternalProcessUtil.buildJavaCommandLine(
77         SystemProperties.getJavaHome() + "/bin/java",
78         "org.jetbrains.groovy.compiler.rt.GroovycRunner",
79         Collections.<String>emptyList(), new ArrayList<String>(generateClasspath(context, chunk)),
80         Arrays.asList("-Xmx384m"/*, "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239"*/),
81         Arrays.<String>asList(myForStubs ? "stubs" : "groovyc", tempFile.getPath())
82       );
83
84       List<GroovycOSProcessHandler.OutputItem> successfullyCompiled = Collections.emptyList();
85       try {
86         final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmd));
87         GroovycOSProcessHandler handler = GroovycOSProcessHandler.runGroovyc(process, new Consumer<String>() {
88           @Override
89           public void consume(String s) {
90             context.processMessage(new ProgressMessage(s));
91           }
92         });
93
94         if (myForStubs && handler.shouldRetry()) {
95           File marker = new File(moduleOutput, "groovy_stubs_retry");
96           if (marker.exists()) {
97             FileUtil.delete(marker);
98           } else {
99             FileUtil.createIfDoesntExist(marker);
100             exitCode = ExitCode.CHUNK_REBUILD_REQUIRED;
101             return exitCode;
102           }
103         }
104
105         successfullyCompiled = handler.getSuccessfullyCompiled();
106
107         for (CompilerMessage message : handler.getCompilerMessages()) {
108           context.processMessage(message);
109         }
110       }
111       finally {
112         if (!myForStubs) {
113           if (updateDependencies(context, chunk, toCompile, moduleOutput, successfullyCompiled)) {
114             exitCode = ExitCode.ADDITIONAL_PASS_REQUIRED;
115           }
116         }
117       }
118
119       return exitCode;
120     }
121     catch (Exception e) {
122       throw new ProjectBuildException(e);
123     }
124   }
125
126   private static String getModuleOutput(CompileContext context, ModuleChunk chunk) {
127     final Module representativeModule = chunk.getModules().iterator().next();
128     File moduleOutputDir = context.getProjectPaths().getModuleOutputDir(representativeModule, context.isCompilingTests());
129     assert moduleOutputDir != null;
130     String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
131     return moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/";
132   }
133
134   private String getCompilerOutput(CompileContext context, String moduleOutputDir) throws IOException {
135     final File dir = myForStubs ? FileUtil.createTempDirectory("groovyStubs", null) : new File(moduleOutputDir);
136     if (myForStubs) {
137       JavaBuilder.addTempSourcePathRoot(context, dir);
138     }
139     return FileUtil.toCanonicalPath(dir.getPath());
140   }
141
142   private static List<File> collectChangedFiles(CompileContext context, ModuleChunk chunk) throws Exception {
143     final List<File> toCompile = new ArrayList<File>();
144     context.processFilesToRecompile(chunk, new FileProcessor() {
145       @Override
146       public boolean apply(Module module, File file, String sourceRoot) throws Exception {
147         final String path = file.getPath();
148         if (isGroovyFile(path)) { //todo file type check
149           toCompile.add(file);
150         }
151         return true;
152       }
153     });
154     return toCompile;
155   }
156
157   private boolean updateDependencies(CompileContext context,
158                                   ModuleChunk chunk,
159                                   List<File> toCompile,
160                                   String moduleOutputPath,
161                                   List<GroovycOSProcessHandler.OutputItem> successfullyCompiled) throws Exception {
162     final Mappings delta = context.createDelta();
163     final List<File> successfullyCompiledFiles = new ArrayList<File>();
164     if (!successfullyCompiled.isEmpty()) {
165
166       final Callbacks.Backend callback = delta.getCallback();
167       final FileGeneratedEvent generatedEvent = new FileGeneratedEvent();
168
169       for (GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
170         final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
171         final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
172         final RootDescriptor moduleAndRoot = context.getModuleAndRoot(new File(sourcePath));
173         if (moduleAndRoot != null) {
174           final String moduleName = moduleAndRoot.module.getName().toLowerCase(Locale.US);
175           context.getDataManager().getSourceToOutputMap(moduleName, moduleAndRoot.isTestRoot).appendData(sourcePath, outputPath);
176         }
177         callback.associate(outputPath, Callbacks.getDefaultLookup(sourcePath), new ClassReader(FileUtil.loadFileBytes(new File(outputPath))));
178         successfullyCompiledFiles.add(new File(sourcePath));
179
180         generatedEvent.add(moduleOutputPath, FileUtil.getRelativePath(moduleOutputPath, outputPath, '/'));
181       }
182
183       context.processMessage(generatedEvent);
184     }
185
186
187     return updateMappings(context, delta, chunk, toCompile, successfullyCompiledFiles);
188   }
189
190   private static List<String> generateClasspath(CompileContext context, ModuleChunk chunk) {
191     final Set<String> cp = new LinkedHashSet<String>();
192     //groovy_rt.jar
193     // IMPORTANT! must be the first in classpath
194     cp.add(ClasspathBootstrap.getResourcePath(GroovyCompilerWrapper.class).getPath());
195
196     for (File file : context.getProjectPaths().getClasspathFiles(chunk, ClasspathKind.compile(context.isCompilingTests()), false)) {
197       cp.add(FileUtil.toCanonicalPath(file.getPath()));
198     }
199     for (File file : context.getProjectPaths().getClasspathFiles(chunk, ClasspathKind.runtime(context.isCompilingTests()), false)) {
200       cp.add(FileUtil.toCanonicalPath(file.getPath()));
201     }
202     return new ArrayList<String>(cp);
203   }
204
205   private static boolean isGroovyFile(String path) {
206     return path.endsWith(".groovy") || path.endsWith(".gpp");
207   }
208
209   private static Map<String, String> buildClassToSourceMap(ModuleChunk chunk, CompileContext context, Set<String> toCompilePaths, String moduleOutputPath) throws Exception {
210     final Map<String, String> class2Src = new HashMap<String, String>();
211     for (Module module : chunk.getModules()) {
212       final String moduleName = module.getName().toLowerCase(Locale.US);
213       final SourceToOutputMapping srcToOut = context.getDataManager().getSourceToOutputMap(moduleName, context.isCompilingTests());
214       for (String src : srcToOut.getKeys()) {
215         if (!toCompilePaths.contains(src) && isGroovyFile(src)) {
216           final Collection<String> outs = srcToOut.getState(src);
217           if (outs != null) {
218             for (String out : outs) {
219               if (out.endsWith(".class") && out.startsWith(moduleOutputPath)) {
220                 final String className = out.substring(moduleOutputPath.length(), out.length() - ".class".length()).replace('/', '.');
221                 class2Src.put(className, src);
222               }
223             }
224           }
225         }
226       }
227     }
228     return class2Src;
229   }
230
231   public String getDescription() {
232     return "Groovy builder";
233   }
234
235 }