1 package org.jetbrains.jps.incremental.groovy;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.util.Key;
6 import com.intellij.openapi.util.io.FileUtil;
7 import com.intellij.util.ArrayUtil;
8 import com.intellij.util.Consumer;
9 import com.intellij.util.SystemProperties;
10 import groovy.util.CharsetToolkit;
11 import org.jetbrains.asm4.ClassReader;
12 import org.jetbrains.ether.dependencyView.Callbacks;
13 import org.jetbrains.ether.dependencyView.Mappings;
14 import org.jetbrains.groovy.compiler.rt.GroovyCompilerWrapper;
15 import org.jetbrains.jps.ModuleChunk;
16 import org.jetbrains.jps.cmdline.ClasspathBootstrap;
17 import org.jetbrains.jps.incremental.*;
18 import org.jetbrains.jps.incremental.fs.RootDescriptor;
19 import org.jetbrains.jps.incremental.java.ClassPostProcessor;
20 import org.jetbrains.jps.incremental.java.JavaBuilder;
21 import org.jetbrains.jps.incremental.messages.CompilerMessage;
22 import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
23 import org.jetbrains.jps.incremental.messages.ProgressMessage;
24 import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
25 import org.jetbrains.jps.javac.OutputFileObject;
26 import org.jetbrains.jps.model.java.JpsJavaClasspathKind;
27 import org.jetbrains.jps.model.java.JpsJavaSdkType;
28 import org.jetbrains.jps.model.library.JpsSdkProperties;
29 import org.jetbrains.jps.model.library.JpsTypedLibrary;
30 import org.jetbrains.jps.model.module.JpsModule;
33 import java.io.IOException;
35 import java.util.concurrent.ConcurrentHashMap;
38 * @author Eugene Zhuravlev
41 public class GroovyBuilder extends ModuleLevelBuilder {
42 private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.groovy.GroovyBuilder");
43 public static final String BUILDER_NAME = "groovy";
44 private static final Key<Boolean> CHUNK_REBUILD_ORDERED = Key.create("CHUNK_REBUILD_ORDERED");
45 private static final Key<Map<String, String>> STUB_TO_SRC = Key.create("STUB_TO_SRC");
46 private final boolean myForStubs;
47 private final String myBuilderName;
49 public GroovyBuilder(boolean forStubs) {
50 super(forStubs ? BuilderCategory.SOURCE_GENERATOR : BuilderCategory.OVERWRITING_TRANSLATOR);
51 myForStubs = forStubs;
52 myBuilderName = BUILDER_NAME + (forStubs ? "-stubs" : "-classes");
56 JavaBuilder.registerClassPostProcessor(new RecompileStubSources());
59 public String getName() {
63 public ModuleLevelBuilder.ExitCode build(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
65 final List<File> toCompile = collectChangedFiles(context, chunk);
66 if (toCompile.isEmpty()) {
67 return ExitCode.NOTHING_DONE;
70 Map<JpsModule, String> finalOutputs = getCanonicalModuleOutputs(context, chunk);
71 Map<JpsModule, String> generationOutputs = getGenerationOutputs(chunk, finalOutputs);
73 final Set<String> toCompilePaths = new LinkedHashSet<String>();
74 for (File file : toCompile) {
75 toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
78 Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs);
80 final String encoding = context.getProjectDescriptor().getEncodingConfiguration().getPreferredModuleChunkEncoding(chunk);
81 List<String> patchers = Collections.emptyList(); //todo patchers
82 String compilerOutput = generationOutputs.get(chunk.representativeModule());
83 final File tempFile = GroovycOSProcessHandler.fillFileWithGroovycParameters(
84 compilerOutput, toCompilePaths, FileUtil.toSystemDependentName(finalOutputs.get(chunk.representativeModule())), class2Src, encoding, patchers
88 final List<String> cmd = ExternalProcessUtil.buildJavaCommandLine(
89 getJavaExecutable(chunk),
90 "org.jetbrains.groovy.compiler.rt.GroovycRunner",
91 Collections.<String>emptyList(), new ArrayList<String>(generateClasspath(context, chunk)),
92 Arrays.asList("-Xmx384m",
93 "-Dfile.encoding=" + CharsetToolkit.getDefaultSystemCharset().name()/*,
94 "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239"*/),
95 Arrays.<String>asList(myForStubs ? "stubs" : "groovyc",
99 final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmd));
100 GroovycOSProcessHandler handler = GroovycOSProcessHandler.runGroovyc(process, new Consumer<String>() {
101 public void consume(String s) {
102 context.processMessage(new ProgressMessage(s));
106 if (!context.isProjectRebuild() && handler.shouldRetry()) {
107 if (CHUNK_REBUILD_ORDERED.get(context) != null) {
108 CHUNK_REBUILD_ORDERED.set(context, null);
110 CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE);
111 LOG.info("Order chunk rebuild");
112 return ExitCode.CHUNK_REBUILD_REQUIRED;
117 final ModuleRootsIndex rootsIndex = context.getProjectDescriptor().rootsIndex;
118 for (JpsModule module : generationOutputs.keySet()) {
119 File root = new File(generationOutputs.get(module));
120 rootsIndex.associateRoot(context, root, module, context.isCompilingTests());
124 for (CompilerMessage message : handler.getCompilerMessages()) {
125 context.processMessage(message);
129 List<GroovycOSProcessHandler.OutputItem> compiled = new ArrayList<GroovycOSProcessHandler.OutputItem>();
130 for (GroovycOSProcessHandler.OutputItem item : handler.getSuccessfullyCompiled()) {
131 compiled.add(ensureCorrectOutput(context, chunk, item, generationOutputs, compilerOutput));
135 Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
136 if (stubToSrc == null) {
137 STUB_TO_SRC.set(context, stubToSrc = new ConcurrentHashMap<String, String>());
139 for (GroovycOSProcessHandler.OutputItem item : handler.getSuccessfullyCompiled()) {
140 stubToSrc.put(FileUtil.toSystemIndependentName(item.outputPath), item.sourcePath);
144 if (!myForStubs && updateDependencies(context, chunk, toCompile, generationOutputs, compiled)) {
145 return ExitCode.ADDITIONAL_PASS_REQUIRED;
149 catch (Exception e) {
150 throw new ProjectBuildException(e);
155 public void cleanupResources(CompileContext context, ModuleChunk chunk) {
156 super.cleanupResources(context, chunk);
157 STUB_TO_SRC.set(context, null);
160 private Map<JpsModule, String> getGenerationOutputs(ModuleChunk chunk, Map<JpsModule, String> finalOutputs) throws IOException {
161 Map<JpsModule, String> generationOutputs = new HashMap<JpsModule, String>();
162 for (JpsModule module : chunk.getModules()) {
163 generationOutputs.put(module, myForStubs ? FileUtil.createTempDirectory("groovyStubs", "__" + module.getName()).getPath() : finalOutputs.get(module));
165 return generationOutputs;
168 private static Map<JpsModule, String> getCanonicalModuleOutputs(CompileContext context, ModuleChunk chunk) {
169 Map<JpsModule, String> finalOutputs = new HashMap<JpsModule, String>();
170 for (JpsModule module : chunk.getModules()) {
171 File moduleOutputDir = context.getProjectPaths().getModuleOutputDir(module, context.isCompilingTests());
172 assert moduleOutputDir != null;
173 String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
174 assert moduleOutputPath != null;
175 finalOutputs.put(module, moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/");
180 private static GroovycOSProcessHandler.OutputItem ensureCorrectOutput(CompileContext context,
182 GroovycOSProcessHandler.OutputItem item, Map<JpsModule, String> generationOutputs, String compilerOutput) throws IOException {
183 if (chunk.getModules().size() > 1) {
184 final ModuleRootsIndex rootsIndex = context.getProjectDescriptor().rootsIndex;
185 RootDescriptor descriptor = rootsIndex.getModuleAndRoot(context, new File(item.sourcePath));
186 if (descriptor != null) {
187 JpsModule srcModule = rootsIndex.getModuleByName(descriptor.module);
188 if (srcModule != null && srcModule != chunk.representativeModule()) {
189 File output = new File(item.outputPath);
191 //todo honor package prefixes
192 File correctRoot = new File(generationOutputs.get(srcModule));
193 File correctOutput = new File(correctRoot, FileUtil.getRelativePath(new File(compilerOutput), output));
195 FileUtil.rename(output, correctOutput);
196 return new GroovycOSProcessHandler.OutputItem(correctOutput.getPath(), item.sourcePath);
203 private static String getJavaExecutable(ModuleChunk chunk) {
204 JpsTypedLibrary<JpsSdkProperties> sdk = chunk.getModules().iterator().next().getSdk(JpsJavaSdkType.INSTANCE);
206 return JpsJavaSdkType.getJavaExecutable(sdk.getProperties());
208 return SystemProperties.getJavaHome() + "/bin/java";
212 public boolean shouldHonorFileEncodingForCompilation(File file) {
213 return isGroovyFile(file.getAbsolutePath());
216 private static List<File> collectChangedFiles(CompileContext context, ModuleChunk chunk) throws IOException {
217 final ResourcePatterns patterns = ResourcePatterns.KEY.get(context);
218 assert patterns != null;
219 final List<File> toCompile = new ArrayList<File>();
220 FSOperations.processFilesToRecompile(context, chunk, new FileProcessor() {
221 public boolean apply(JpsModule module, File file, String sourceRoot) throws IOException {
222 final String path = file.getPath();
223 if (isGroovyFile(path) && !patterns.isResourceFile(file, sourceRoot)) { //todo file type check
232 private boolean updateDependencies(CompileContext context,
234 List<File> toCompile,
235 Map<JpsModule, String> generationOutputs,
236 List<GroovycOSProcessHandler.OutputItem> successfullyCompiled) throws IOException {
237 final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
238 final List<File> successfullyCompiledFiles = new ArrayList<File>();
239 if (!successfullyCompiled.isEmpty()) {
241 final Callbacks.Backend callback = delta.getCallback();
242 final FileGeneratedEvent generatedEvent = new FileGeneratedEvent();
244 for (GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
245 final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
246 final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
247 final RootDescriptor moduleAndRoot = context.getProjectDescriptor().rootsIndex.getModuleAndRoot(context, new File(sourcePath));
248 if (moduleAndRoot != null) {
249 final String moduleName = moduleAndRoot.module;
250 context.getProjectDescriptor().dataManager.getSourceToOutputMap(moduleName, moduleAndRoot.isTestRoot).appendData(sourcePath, outputPath);
251 String moduleOutputPath = generationOutputs.get(context.getProjectDescriptor().rootsIndex.getModuleByName(moduleName));
252 generatedEvent.add(moduleOutputPath, FileUtil.getRelativePath(moduleOutputPath, outputPath, '/'));
254 callback.associate(outputPath, sourcePath, new ClassReader(FileUtil.loadFileBytes(new File(outputPath))));
255 successfullyCompiledFiles.add(new File(sourcePath));
258 context.processMessage(generatedEvent);
262 return updateMappings(context, delta, chunk, toCompile, successfullyCompiledFiles);
265 private static List<String> generateClasspath(CompileContext context, ModuleChunk chunk) {
266 final Set<String> cp = new LinkedHashSet<String>();
268 // IMPORTANT! must be the first in classpath
269 cp.add(ClasspathBootstrap.getResourcePath(GroovyCompilerWrapper.class).getPath());
271 for (File file : context.getProjectPaths().getClasspathFiles(chunk, JpsJavaClasspathKind.compile(context.isCompilingTests()), false)) {
272 cp.add(FileUtil.toCanonicalPath(file.getPath()));
274 for (File file : context.getProjectPaths().getClasspathFiles(chunk, JpsJavaClasspathKind.runtime(context.isCompilingTests()), false)) {
275 cp.add(FileUtil.toCanonicalPath(file.getPath()));
277 return new ArrayList<String>(cp);
280 private static boolean isGroovyFile(String path) {
281 return path.endsWith(".groovy") || path.endsWith(".gpp");
284 private static Map<String, String> buildClassToSourceMap(ModuleChunk chunk, CompileContext context, Set<String> toCompilePaths, Map<JpsModule, String> finalOutputs) throws IOException {
285 final Map<String, String> class2Src = new HashMap<String, String>();
286 for (JpsModule module : chunk.getModules()) {
287 String moduleOutputPath = finalOutputs.get(module);
288 final SourceToOutputMapping srcToOut = context.getProjectDescriptor().dataManager.getSourceToOutputMap(module.getName(), context.isCompilingTests());
289 for (String src : srcToOut.getKeys()) {
290 if (!toCompilePaths.contains(src) && isGroovyFile(src) &&
291 !context.getProjectDescriptor().project.getCompilerConfiguration().getExcludes().isExcluded(new File(src))) {
292 final Collection<String> outs = srcToOut.getState(src);
294 for (String out : outs) {
295 if (out.endsWith(".class") && out.startsWith(moduleOutputPath)) {
296 final String className = out.substring(moduleOutputPath.length(), out.length() - ".class".length()).replace('/', '.');
297 class2Src.put(className, src);
308 public String toString() {
309 return "GroovyBuilder{" +
310 "myForStubs=" + myForStubs +
314 public String getDescription() {
315 return "Groovy builder";
318 private static class RecompileStubSources implements ClassPostProcessor {
320 public void process(CompileContext context, OutputFileObject out) {
321 Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
322 if (stubToSrc == null) {
326 File src = out.getSourceFile();
330 String groovy = stubToSrc.get(FileUtil.toSystemIndependentName(src.getPath()));
331 if (groovy == null) {
336 FSOperations.markDirty(context, new File(groovy));
338 catch (IOException e) {