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.annotations.Nullable;
12 import org.jetbrains.asm4.ClassReader;
13 import org.jetbrains.ether.dependencyView.Callbacks;
14 import org.jetbrains.ether.dependencyView.Mappings;
15 import org.jetbrains.groovy.compiler.rt.GroovyCompilerWrapper;
16 import org.jetbrains.jps.ModuleChunk;
17 import org.jetbrains.jps.cmdline.ClasspathBootstrap;
18 import org.jetbrains.jps.incremental.*;
19 import org.jetbrains.jps.incremental.fs.RootDescriptor;
20 import org.jetbrains.jps.incremental.java.ClassPostProcessor;
21 import org.jetbrains.jps.incremental.java.JavaBuilder;
22 import org.jetbrains.jps.incremental.messages.BuildMessage;
23 import org.jetbrains.jps.incremental.messages.CompilerMessage;
24 import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
25 import org.jetbrains.jps.incremental.messages.ProgressMessage;
26 import org.jetbrains.jps.incremental.storage.SourceToOutputMapping;
27 import org.jetbrains.jps.javac.OutputFileObject;
28 import org.jetbrains.jps.model.java.JpsJavaClasspathKind;
29 import org.jetbrains.jps.model.java.JpsJavaSdkType;
30 import org.jetbrains.jps.model.library.JpsSdkProperties;
31 import org.jetbrains.jps.model.library.JpsTypedLibrary;
32 import org.jetbrains.jps.model.module.JpsModule;
35 import java.io.IOException;
37 import java.util.concurrent.ConcurrentHashMap;
40 * @author Eugene Zhuravlev
43 public class GroovyBuilder extends ModuleLevelBuilder {
44 private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.groovy.GroovyBuilder");
45 public static final String BUILDER_NAME = "groovy";
46 private static final Key<Boolean> CHUNK_REBUILD_ORDERED = Key.create("CHUNK_REBUILD_ORDERED");
47 private static final Key<Map<String, String>> STUB_TO_SRC = Key.create("STUB_TO_SRC");
48 private final boolean myForStubs;
49 private final String myBuilderName;
51 public GroovyBuilder(boolean forStubs) {
52 super(forStubs ? BuilderCategory.SOURCE_GENERATOR : BuilderCategory.OVERWRITING_TRANSLATOR);
53 myForStubs = forStubs;
54 myBuilderName = BUILDER_NAME + (forStubs ? "-stubs" : "-classes");
58 JavaBuilder.registerClassPostProcessor(new RecompileStubSources());
61 public String getName() {
65 public ModuleLevelBuilder.ExitCode build(final CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
67 final List<File> toCompile = collectChangedFiles(context, chunk);
68 if (toCompile.isEmpty()) {
69 return ExitCode.NOTHING_DONE;
72 Map<JpsModule, String> finalOutputs = getCanonicalModuleOutputs(context, chunk);
73 if (finalOutputs == null) {
74 return ExitCode.ABORT;
76 Map<JpsModule, String> generationOutputs = getGenerationOutputs(chunk, finalOutputs);
78 final Set<String> toCompilePaths = new LinkedHashSet<String>();
79 for (File file : toCompile) {
80 toCompilePaths.add(FileUtil.toSystemIndependentName(file.getPath()));
83 Map<String, String> class2Src = buildClassToSourceMap(chunk, context, toCompilePaths, finalOutputs);
85 final String encoding = context.getProjectDescriptor().getEncodingConfiguration().getPreferredModuleChunkEncoding(chunk);
86 List<String> patchers = Collections.emptyList(); //todo patchers
87 String compilerOutput = generationOutputs.get(chunk.representativeModule());
88 final File tempFile = GroovycOSProcessHandler.fillFileWithGroovycParameters(
89 compilerOutput, toCompilePaths, FileUtil.toSystemDependentName(finalOutputs.get(chunk.representativeModule())), class2Src, encoding, patchers
93 final List<String> cmd = ExternalProcessUtil.buildJavaCommandLine(
94 getJavaExecutable(chunk),
95 "org.jetbrains.groovy.compiler.rt.GroovycRunner",
96 Collections.<String>emptyList(), new ArrayList<String>(generateClasspath(context, chunk)),
97 Arrays.asList("-Xmx384m",
98 "-Dfile.encoding=" + CharsetToolkit.getDefaultSystemCharset().name()/*,
99 "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5239"*/),
100 Arrays.<String>asList(myForStubs ? "stubs" : "groovyc",
104 final Process process = Runtime.getRuntime().exec(ArrayUtil.toStringArray(cmd));
105 GroovycOSProcessHandler handler = GroovycOSProcessHandler.runGroovyc(process, new Consumer<String>() {
106 public void consume(String s) {
107 context.processMessage(new ProgressMessage(s));
111 if (!context.isProjectRebuild() && handler.shouldRetry()) {
112 if (CHUNK_REBUILD_ORDERED.get(context) != null) {
113 CHUNK_REBUILD_ORDERED.set(context, null);
115 CHUNK_REBUILD_ORDERED.set(context, Boolean.TRUE);
116 LOG.info("Order chunk rebuild");
117 return ExitCode.CHUNK_REBUILD_REQUIRED;
122 final ModuleRootsIndex rootsIndex = context.getProjectDescriptor().rootsIndex;
123 for (JpsModule module : generationOutputs.keySet()) {
124 File root = new File(generationOutputs.get(module));
125 rootsIndex.associateRoot(context, root, module, context.isCompilingTests());
129 for (CompilerMessage message : handler.getCompilerMessages()) {
130 context.processMessage(message);
134 List<GroovycOSProcessHandler.OutputItem> compiled = new ArrayList<GroovycOSProcessHandler.OutputItem>();
135 for (GroovycOSProcessHandler.OutputItem item : handler.getSuccessfullyCompiled()) {
136 compiled.add(ensureCorrectOutput(context, chunk, item, generationOutputs, compilerOutput));
140 Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
141 if (stubToSrc == null) {
142 STUB_TO_SRC.set(context, stubToSrc = new ConcurrentHashMap<String, String>());
144 for (GroovycOSProcessHandler.OutputItem item : handler.getSuccessfullyCompiled()) {
145 stubToSrc.put(FileUtil.toSystemIndependentName(item.outputPath), item.sourcePath);
149 if (!myForStubs && updateDependencies(context, chunk, toCompile, generationOutputs, compiled)) {
150 return ExitCode.ADDITIONAL_PASS_REQUIRED;
154 catch (Exception e) {
155 throw new ProjectBuildException(e);
160 public void cleanupResources(CompileContext context, ModuleChunk chunk) {
161 super.cleanupResources(context, chunk);
162 STUB_TO_SRC.set(context, null);
165 private Map<JpsModule, String> getGenerationOutputs(ModuleChunk chunk, Map<JpsModule, String> finalOutputs) throws IOException {
166 Map<JpsModule, String> generationOutputs = new HashMap<JpsModule, String>();
167 for (JpsModule module : chunk.getModules()) {
168 generationOutputs.put(module, myForStubs ? FileUtil.createTempDirectory("groovyStubs", "__" + module.getName()).getPath() : finalOutputs.get(module));
170 return generationOutputs;
173 @Nullable private static Map<JpsModule, String> getCanonicalModuleOutputs(CompileContext context, ModuleChunk chunk) {
174 Map<JpsModule, String> finalOutputs = new HashMap<JpsModule, String>();
175 for (JpsModule module : chunk.getModules()) {
176 File moduleOutputDir = context.getProjectPaths().getModuleOutputDir(module, context.isCompilingTests());
177 if (moduleOutputDir == null) {
178 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Output directory not specified for module " + module.getName()));
181 String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
182 assert moduleOutputPath != null;
183 finalOutputs.put(module, moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/");
188 private static GroovycOSProcessHandler.OutputItem ensureCorrectOutput(CompileContext context,
190 GroovycOSProcessHandler.OutputItem item, Map<JpsModule, String> generationOutputs, String compilerOutput) throws IOException {
191 if (chunk.getModules().size() > 1) {
192 final ModuleRootsIndex rootsIndex = context.getProjectDescriptor().rootsIndex;
193 RootDescriptor descriptor = rootsIndex.getModuleAndRoot(context, new File(item.sourcePath));
194 if (descriptor != null) {
195 JpsModule srcModule = rootsIndex.getModuleByName(descriptor.module);
196 if (srcModule != null && srcModule != chunk.representativeModule()) {
197 File output = new File(item.outputPath);
199 //todo honor package prefixes
200 File correctRoot = new File(generationOutputs.get(srcModule));
201 File correctOutput = new File(correctRoot, FileUtil.getRelativePath(new File(compilerOutput), output));
203 FileUtil.rename(output, correctOutput);
204 return new GroovycOSProcessHandler.OutputItem(correctOutput.getPath(), item.sourcePath);
211 private static String getJavaExecutable(ModuleChunk chunk) {
212 JpsTypedLibrary<JpsSdkProperties> sdk = chunk.getModules().iterator().next().getSdk(JpsJavaSdkType.INSTANCE);
214 return JpsJavaSdkType.getJavaExecutable(sdk.getProperties());
216 return SystemProperties.getJavaHome() + "/bin/java";
220 public boolean shouldHonorFileEncodingForCompilation(File file) {
221 return isGroovyFile(file.getAbsolutePath());
224 private static List<File> collectChangedFiles(CompileContext context, ModuleChunk chunk) throws IOException {
225 final ResourcePatterns patterns = ResourcePatterns.KEY.get(context);
226 assert patterns != null;
227 final List<File> toCompile = new ArrayList<File>();
228 FSOperations.processFilesToRecompile(context, chunk, new FileProcessor() {
229 public boolean apply(JpsModule module, File file, String sourceRoot) throws IOException {
230 final String path = file.getPath();
231 if (isGroovyFile(path) && !patterns.isResourceFile(file, sourceRoot)) { //todo file type check
240 private boolean updateDependencies(CompileContext context,
242 List<File> toCompile,
243 Map<JpsModule, String> generationOutputs,
244 List<GroovycOSProcessHandler.OutputItem> successfullyCompiled) throws IOException {
245 final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
246 final List<File> successfullyCompiledFiles = new ArrayList<File>();
247 if (!successfullyCompiled.isEmpty()) {
249 final Callbacks.Backend callback = delta.getCallback();
250 final FileGeneratedEvent generatedEvent = new FileGeneratedEvent();
252 for (GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
253 final String sourcePath = FileUtil.toSystemIndependentName(item.sourcePath);
254 final String outputPath = FileUtil.toSystemIndependentName(item.outputPath);
255 final RootDescriptor moduleAndRoot = context.getProjectDescriptor().rootsIndex.getModuleAndRoot(context, new File(sourcePath));
256 if (moduleAndRoot != null) {
257 final String moduleName = moduleAndRoot.module;
258 context.getProjectDescriptor().dataManager.getSourceToOutputMap(moduleName, moduleAndRoot.isTestRoot).appendData(sourcePath, outputPath);
259 String moduleOutputPath = generationOutputs.get(context.getProjectDescriptor().rootsIndex.getModuleByName(moduleName));
260 generatedEvent.add(moduleOutputPath, FileUtil.getRelativePath(moduleOutputPath, outputPath, '/'));
262 callback.associate(outputPath, sourcePath, new ClassReader(FileUtil.loadFileBytes(new File(outputPath))));
263 successfullyCompiledFiles.add(new File(sourcePath));
266 context.processMessage(generatedEvent);
270 return updateMappings(context, delta, chunk, toCompile, successfullyCompiledFiles);
273 private static List<String> generateClasspath(CompileContext context, ModuleChunk chunk) {
274 final Set<String> cp = new LinkedHashSet<String>();
276 // IMPORTANT! must be the first in classpath
277 cp.add(ClasspathBootstrap.getResourcePath(GroovyCompilerWrapper.class).getPath());
279 for (File file : context.getProjectPaths().getClasspathFiles(chunk, JpsJavaClasspathKind.compile(context.isCompilingTests()), false)) {
280 cp.add(FileUtil.toCanonicalPath(file.getPath()));
282 for (File file : context.getProjectPaths().getClasspathFiles(chunk, JpsJavaClasspathKind.runtime(context.isCompilingTests()), false)) {
283 cp.add(FileUtil.toCanonicalPath(file.getPath()));
285 return new ArrayList<String>(cp);
288 private static boolean isGroovyFile(String path) {
289 return path.endsWith(".groovy") || path.endsWith(".gpp");
292 private static Map<String, String> buildClassToSourceMap(ModuleChunk chunk, CompileContext context, Set<String> toCompilePaths, Map<JpsModule, String> finalOutputs) throws IOException {
293 final Map<String, String> class2Src = new HashMap<String, String>();
294 for (JpsModule module : chunk.getModules()) {
295 String moduleOutputPath = finalOutputs.get(module);
296 final SourceToOutputMapping srcToOut = context.getProjectDescriptor().dataManager.getSourceToOutputMap(module.getName(), context.isCompilingTests());
297 for (String src : srcToOut.getKeys()) {
298 if (!toCompilePaths.contains(src) && isGroovyFile(src) &&
299 !context.getProjectDescriptor().project.getCompilerConfiguration().getExcludes().isExcluded(new File(src))) {
300 final Collection<String> outs = srcToOut.getState(src);
302 for (String out : outs) {
303 if (out.endsWith(".class") && out.startsWith(moduleOutputPath)) {
304 final String className = out.substring(moduleOutputPath.length(), out.length() - ".class".length()).replace('/', '.');
305 class2Src.put(className, src);
316 public String toString() {
317 return "GroovyBuilder{" +
318 "myForStubs=" + myForStubs +
322 public String getDescription() {
323 return "Groovy builder";
326 private static class RecompileStubSources implements ClassPostProcessor {
328 public void process(CompileContext context, OutputFileObject out) {
329 Map<String, String> stubToSrc = STUB_TO_SRC.get(context);
330 if (stubToSrc == null) {
334 File src = out.getSourceFile();
338 String groovy = stubToSrc.get(FileUtil.toSystemIndependentName(src.getPath()));
339 if (groovy == null) {
344 FSOperations.markDirty(context, new File(groovy));
346 catch (IOException e) {