c48e9bb443b7e7b3432fd01c4438e792eef4374f
[idea/community.git] / plugins / groovy / rt / src / org / jetbrains / groovy / compiler / rt / DependentGroovycRunner.java
1 /*
2  * Copyright 2000-2013 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.groovy.compiler.rt;
17
18 import groovy.lang.Binding;
19 import groovy.lang.GroovyClassLoader;
20 import groovy.lang.GroovyShell;
21 import groovyjarjarasm.asm.Opcodes;
22 import org.codehaus.groovy.ast.*;
23 import org.codehaus.groovy.ast.expr.ConstantExpression;
24 import org.codehaus.groovy.ast.expr.Expression;
25 import org.codehaus.groovy.ast.expr.ListExpression;
26 import org.codehaus.groovy.ast.expr.MethodCallExpression;
27 import org.codehaus.groovy.classgen.GeneratorContext;
28 import org.codehaus.groovy.control.*;
29 import org.codehaus.groovy.control.customizers.ImportCustomizer;
30 import org.codehaus.groovy.control.messages.WarningMessage;
31 import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
32 import org.codehaus.groovy.tools.javac.JavaCompiler;
33 import org.codehaus.groovy.tools.javac.JavaCompilerFactory;
34
35 import java.io.*;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.util.*;
39
40 /**
41  * @author peter
42  */
43 @SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
44 public class DependentGroovycRunner {
45   public static final String TEMP_RESOURCE_SUFFIX = "___" + new Random().nextInt() + "_neverHappen";
46   public static final String[] RESOURCES_TO_MASK = {"META-INF/services/org.codehaus.groovy.transform.ASTTransformation", "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule"};
47   private static final String STUB_DIR = "stubDir";
48
49   public static boolean runGroovyc(boolean forStubs, String argsPath, Queue mailbox) {
50     File argsFile = new File(argsPath);
51     final CompilerConfiguration config = new CompilerConfiguration();
52     config.setClasspath("");
53     config.setOutput(new PrintWriter(System.err));
54     config.setWarningLevel(WarningMessage.PARANOIA);
55
56     final List<CompilerMessage> compilerMessages = new ArrayList<CompilerMessage>();
57     final List<CompilationUnitPatcher> patchers = new ArrayList<CompilationUnitPatcher>();
58     final List<File> srcFiles = new ArrayList<File>();
59     final Map<String, File> class2File = new HashMap<String, File>();
60
61     final String[] finalOutputRef = new String[1];
62     fillFromArgsFile(argsFile, config, patchers, compilerMessages, srcFiles, class2File, finalOutputRef);
63     if (srcFiles.isEmpty()) return true;
64
65     String[] finalOutputs = finalOutputRef[0].split(File.pathSeparator);
66
67     if (forStubs) {
68       Map<String, Object> options = new HashMap<String, Object>();
69       options.put(STUB_DIR, config.getTargetDirectory());
70       options.put("keepStubs", Boolean.TRUE);
71       config.setJointCompilationOptions(options);
72       if (mailbox != null) {
73         config.setTargetDirectory(finalOutputs[0]);
74       }
75     }
76
77     try {
78       if (!"false".equals(System.getProperty(GroovyRtConstants.GROOVYC_ASM_RESOLVING_ONLY))) {
79         config.getOptimizationOptions().put("asmResolving", true);
80         config.getOptimizationOptions().put("classLoaderResolving", false);
81       }
82     }
83     catch (NoSuchMethodError ignored) { // old groovyc's don't have optimization options
84     }
85
86     String configScript = System.getProperty(GroovyRtConstants.GROOVYC_CONFIG_SCRIPT);
87     if (configScript != null) {
88       try {
89         applyConfigurationScript(new File(configScript), config);
90       }
91       catch (LinkageError ignored) {
92       }
93     }
94
95     System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: loading sources...");
96     renameResources(finalOutputs, "", TEMP_RESOURCE_SUFFIX);
97
98     final List<GroovyCompilerWrapper.OutputItem> compiledFiles;
99     try {
100       final AstAwareResourceLoader resourceLoader = new AstAwareResourceLoader(class2File);
101       final GroovyCompilerWrapper wrapper = new GroovyCompilerWrapper(compilerMessages, forStubs);
102       final CompilationUnit unit = createCompilationUnit(forStubs, config, buildClassLoaderFor(config, resourceLoader), mailbox, wrapper);
103       unit.addPhaseOperation(new CompilationUnit.SourceUnitOperation() {
104         public void call(SourceUnit source) throws CompilationFailedException {
105           File file = new File(source.getName());
106           for (ClassNode aClass : source.getAST().getClasses()) {
107             resourceLoader.myClass2File.put(aClass.getName(), file);
108           }
109         }
110       }, Phases.CONVERSION);
111
112       addSources(forStubs, srcFiles, unit);
113       runPatchers(patchers, compilerMessages, unit, resourceLoader, srcFiles);
114
115       System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: compiling...");
116       compiledFiles = wrapper.compile(unit, forStubs && mailbox == null ? Phases.CONVERSION : Phases.ALL);
117     }
118     finally {
119       renameResources(finalOutputs, TEMP_RESOURCE_SUFFIX, "");
120       System.out.println(GroovyRtConstants.CLEAR_PRESENTABLE);
121     }
122
123     System.out.println();
124     reportCompiledItems(compiledFiles);
125
126     int errorCount = 0;
127     for (CompilerMessage message : compilerMessages) {
128       if (message.getCategory() == GroovyCompilerMessageCategories.ERROR) {
129         if (errorCount > 100) {
130           continue;
131         }
132         errorCount++;
133       }
134
135       printMessage(message);
136     }
137     return false;
138   }
139
140   // adapted from https://github.com/gradle/gradle/blob/c4fdfb57d336b1a0f1b27354c758c61c0a586942/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
141   private static void applyConfigurationScript(File configScript, CompilerConfiguration configuration) {
142     Binding binding = new Binding();
143     binding.setVariable("configuration", configuration);
144
145     CompilerConfiguration configuratorConfig = new CompilerConfiguration();
146     ImportCustomizer customizer = new ImportCustomizer();
147     customizer.addStaticStars("org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder");
148     configuratorConfig.addCompilationCustomizers(customizer);
149
150     try {
151       new GroovyShell(binding, configuratorConfig).evaluate(configScript);
152     }
153     catch (Exception e) {
154       e.printStackTrace();
155     }
156   }
157   
158   private static void renameResources(String[] finalOutputs, String removeSuffix, String addSuffix) {
159     for (String output : finalOutputs) {
160       for (String res : RESOURCES_TO_MASK) {
161         File file = new File(output, res + removeSuffix);
162         if (file.exists()) {
163           file.renameTo(new File(output, res + addSuffix));
164         }
165       }
166     }
167   }
168
169   private static String fillFromArgsFile(File argsFile, CompilerConfiguration compilerConfiguration, List<CompilationUnitPatcher> patchers, List<CompilerMessage> compilerMessages,
170                                          List<File> srcFiles, Map<String, File> class2File, String[] finalOutputs) {
171     String moduleClasspath = null;
172
173     BufferedReader reader = null;
174     FileInputStream stream;
175
176     try {
177       stream = new FileInputStream(argsFile);
178       reader = new BufferedReader(new InputStreamReader(stream));
179
180       reader.readLine(); // skip classpath
181
182       String line;
183       while ((line = reader.readLine()) != null) {
184         if (!GroovyRtConstants.SRC_FILE.equals(line)) {
185           break;
186         }
187
188         final File file = new File(reader.readLine());
189         srcFiles.add(file);
190       }
191
192       while (line != null) {
193         if (line.equals("class2src")) {
194           while (!GroovyRtConstants.END.equals(line = reader.readLine())) {
195             class2File.put(line, new File(reader.readLine()));
196           }
197         }
198         else if (line.startsWith(GroovyRtConstants.PATCHERS)) {
199           final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
200           String s;
201           while (!GroovyRtConstants.END.equals(s = reader.readLine())) {
202             try {
203               final Class<?> patcherClass = classLoader.loadClass(s);
204               final CompilationUnitPatcher patcher = (CompilationUnitPatcher)patcherClass.newInstance();
205               patchers.add(patcher);
206             }
207             catch (InstantiationException e) {
208               addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
209             }
210             catch (IllegalAccessException e) {
211               addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
212             }
213             catch (ClassNotFoundException e) {
214               addExceptionInfo(compilerMessages, e, "Couldn't instantiate " + s);
215             }
216           }
217         }
218         else if (line.startsWith(GroovyRtConstants.ENCODING)) {
219           compilerConfiguration.setSourceEncoding(reader.readLine());
220         }
221         else if (line.startsWith(GroovyRtConstants.OUTPUTPATH)) {
222           compilerConfiguration.setTargetDirectory(reader.readLine());
223         }
224         else if (line.startsWith(GroovyRtConstants.FINAL_OUTPUTPATH)) {
225           finalOutputs[0] = reader.readLine();
226         }
227
228         line = reader.readLine();
229       }
230
231     }
232     catch (FileNotFoundException e) {
233       e.printStackTrace();
234     }
235     catch (IOException e) {
236       e.printStackTrace();
237     }
238     finally {
239       try {
240         reader.close();
241       }
242       catch (IOException e) {
243         e.printStackTrace();
244       }
245       finally {
246         argsFile.delete();
247       }
248     }
249     return moduleClasspath;
250   }
251
252   private static void addSources(boolean forStubs, List<File> srcFiles, final CompilationUnit unit) {
253     for (final File file : srcFiles) {
254       if (forStubs && file.getName().endsWith(".java")) {
255         continue;
256       }
257
258       unit.addSource(new SourceUnit(file, unit.getConfiguration(), unit.getClassLoader(), unit.getErrorCollector()));
259     }
260   }
261
262   private static void runPatchers(List<CompilationUnitPatcher> patchers, List<CompilerMessage> compilerMessages, CompilationUnit unit, final AstAwareResourceLoader loader, List<File> srcFiles) {
263     if (!patchers.isEmpty()) {
264       for (CompilationUnitPatcher patcher : patchers) {
265         try {
266           patcher.patchCompilationUnit(unit, loader, srcFiles.toArray(new File[srcFiles.size()]));
267         }
268         catch (LinkageError e) {
269           addExceptionInfo(compilerMessages, e, "Couldn't run " + patcher.getClass().getName());
270         }
271       }
272     }
273   }
274
275   private static void reportCompiledItems(List<GroovyCompilerWrapper.OutputItem> compiledFiles) {
276     for (GroovyCompilerWrapper.OutputItem compiledFile : compiledFiles) {
277       /*
278       * output path
279       * source file
280       * output root directory
281       */
282       System.out.print(GroovyRtConstants.COMPILED_START);
283       System.out.print(compiledFile.getOutputPath());
284       System.out.print(GroovyRtConstants.SEPARATOR);
285       System.out.print(compiledFile.getSourceFile());
286       System.out.print(GroovyRtConstants.COMPILED_END);
287       System.out.println();
288     }
289   }
290
291   private static void printMessage(CompilerMessage message) {
292     System.out.print(GroovyRtConstants.MESSAGES_START);
293     System.out.print(message.getCategory());
294     System.out.print(GroovyRtConstants.SEPARATOR);
295     System.out.print(message.getMessage());
296     System.out.print(GroovyRtConstants.SEPARATOR);
297     System.out.print(message.getUrl());
298     System.out.print(GroovyRtConstants.SEPARATOR);
299     System.out.print(message.getLineNum());
300     System.out.print(GroovyRtConstants.SEPARATOR);
301     System.out.print(message.getColumnNum());
302     System.out.print(GroovyRtConstants.SEPARATOR);
303     System.out.print(GroovyRtConstants.MESSAGES_END);
304     System.out.println();
305   }
306
307   private static void addExceptionInfo(List<CompilerMessage> compilerMessages, Throwable e, String message) {
308     final StringWriter writer = new StringWriter();
309     e.printStackTrace(new PrintWriter(writer));
310     compilerMessages.add(new CompilerMessage(GroovyCompilerMessageCategories.WARNING, message + ":\n" + writer, "<exception>", -1, -1));
311   }
312
313   private static CompilationUnit createCompilationUnit(final boolean forStubs,
314                                                        final CompilerConfiguration config,
315                                                        final GroovyClassLoader classLoader,
316                                                        Queue mailbox, GroovyCompilerWrapper wrapper) {
317
318     final GroovyClassLoader transformLoader = new GroovyClassLoader(classLoader);
319
320     try {
321       if (forStubs) {
322         return createStubGenerator(config, classLoader, transformLoader, mailbox, wrapper);
323       }
324     }
325     catch (NoClassDefFoundError ignore) { // older groovy distributions just don't have stub generation capability
326     }
327
328     CompilationUnit unit;
329     try {
330       unit = new CompilationUnit(config, null, classLoader, transformLoader) {
331
332         public void gotoPhase(int phase) throws CompilationFailedException {
333           super.gotoPhase(phase);
334           if (phase <= Phases.ALL) {
335             System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: " + getPhaseDescription());
336           }
337         }
338       };
339     }
340     catch (NoSuchMethodError e) {
341       //groovy 1.5.x
342       unit = new CompilationUnit(config, null, classLoader) {
343
344         public void gotoPhase(int phase) throws CompilationFailedException {
345           super.gotoPhase(phase);
346           if (phase <= Phases.ALL) {
347             System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: " + getPhaseDescription());
348           }
349         }
350       };
351     }
352     return unit;
353   }
354
355   private static CompilationUnit createStubGenerator(final CompilerConfiguration config, final GroovyClassLoader classLoader, final GroovyClassLoader transformLoader, final Queue mailbox, final GroovyCompilerWrapper wrapper) {
356     final JavaAwareCompilationUnit unit = new JavaAwareCompilationUnit(config, classLoader) {
357       private boolean annoRemovedAdded;
358
359       @Override
360       public GroovyClassLoader getTransformLoader() {
361         return transformLoader;
362       }
363
364       @Override
365       public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) {
366         if (!annoRemovedAdded && mailbox == null && phase == Phases.CONVERSION && op.getClass().getName().startsWith("org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit$")) {
367           annoRemovedAdded = true;
368           super.addPhaseOperation(new PrimaryClassNodeOperation() {
369             @Override
370             public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
371               final ClassCodeVisitorSupport annoRemover = new ClassCodeVisitorSupport() {
372                 @Override
373                 protected SourceUnit getSourceUnit() {
374                   return source;
375                 }
376
377                 public void visitClass(ClassNode node) {
378                   if (node.isEnum()) {
379                     node.setModifiers(node.getModifiers() & ~Opcodes.ACC_FINAL);
380                   }
381                   super.visitClass(node);
382                 }
383
384                 @Override
385                 public void visitField(FieldNode fieldNode) {
386                   Expression valueExpr = fieldNode.getInitialValueExpression();
387                   if (valueExpr instanceof ConstantExpression && ClassHelper.STRING_TYPE.equals(valueExpr.getType())) {
388                     fieldNode.setInitialValueExpression(new MethodCallExpression(valueExpr, "toString", new ListExpression()));
389                   }
390                   super.visitField(fieldNode);
391                 }
392
393                 @Override
394                 public void visitAnnotations(AnnotatedNode node) {
395                   List<AnnotationNode> annotations = node.getAnnotations();
396                   if (!annotations.isEmpty()) {
397                     annotations.clear();
398                   }
399                   super.visitAnnotations(node);
400                 }
401               };
402               try {
403                 annoRemover.visitClass(classNode);
404               }
405               catch (LinkageError ignored) {
406               }
407             }
408           }, phase);
409         }
410
411         super.addPhaseOperation(op, phase);
412       }
413
414       public void gotoPhase(int phase) throws CompilationFailedException {
415         if (phase < Phases.SEMANTIC_ANALYSIS) {
416           System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovy stub generator: " + getPhaseDescription());
417         }
418         else if (phase <= Phases.ALL) {
419           System.out.println(GroovyRtConstants.PRESENTABLE_MESSAGE + "Groovyc: " + getPhaseDescription());
420         }
421
422         super.gotoPhase(phase);
423       }
424
425     };
426     unit.setCompilerFactory(new JavaCompilerFactory() {
427       public JavaCompiler createCompiler(final CompilerConfiguration config) {
428         return new JavaCompiler() {
429           public void compile(List<String> files, CompilationUnit cu) {
430             if (mailbox != null) {
431               reportCompiledItems(GroovyCompilerWrapper.getStubOutputItems(unit, (File)config.getJointCompilationOptions().get(STUB_DIR)));
432               System.out.flush();
433               System.err.flush();
434
435               pauseAndWaitForJavac(mailbox);
436               wrapper.onContinuation();
437             }
438           }
439         };
440       }
441     });
442     unit.addSources(new String[]{"SomeClass.java"});
443     return unit;
444   }
445
446   @SuppressWarnings("unchecked")
447   private static void pauseAndWaitForJavac(Queue mailbox) {
448     mailbox.offer(GroovyRtConstants.STUBS_GENERATED);
449     while (true) {
450       try {
451         //noinspection BusyWait
452         Thread.sleep(10);
453         Object response = mailbox.poll();
454         if (GroovyRtConstants.STUBS_GENERATED.equals(response)) {
455           mailbox.offer(response); // another thread hasn't received it => resend
456         } else if (GroovyRtConstants.BUILD_ABORTED.equals(response)) {
457           throw new RuntimeException(GroovyRtConstants.BUILD_ABORTED);
458         } else if (GroovyRtConstants.JAVAC_COMPLETED.equals(response)) {
459           break; // stop waiting and continue compiling
460         } else if (response != null) {
461           throw new RuntimeException("Unknown response: " + response);
462         }
463       }
464       catch (InterruptedException e) {
465         throw new RuntimeException(e);
466       }
467     }
468   }
469
470   static GroovyClassLoader buildClassLoaderFor(final CompilerConfiguration compilerConfiguration, final AstAwareResourceLoader resourceLoader) {
471     final ClassDependencyLoader checkWellFormed = new ClassDependencyLoader() {
472       @Override
473       protected void loadClassDependencies(Class aClass) throws ClassNotFoundException {
474         if (resourceLoader.getSourceFile(aClass.getName()) == null) return;
475         super.loadClassDependencies(aClass);
476       }
477     };
478     
479     GroovyClassLoader classLoader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {
480       public GroovyClassLoader run() {
481         return new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), compilerConfiguration) {
482           public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript)
483             throws ClassNotFoundException, CompilationFailedException {
484             Class aClass;
485             try {
486               aClass = super.loadClass(name, lookupScriptFiles, preferClassOverScript);
487             }
488             catch (NoClassDefFoundError e) {
489               throw new ClassNotFoundException(name);
490             }
491             catch (LinkageError e) {
492               throw new RuntimeException("Problem loading class " + name, e);
493             }
494             return checkWellFormed.loadDependencies(aClass);
495           }
496         };
497       }
498     });
499     classLoader.setResourceLoader(resourceLoader);
500     return classLoader;
501   }
502 }