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