Groovy-Eclipse compiler support back-end (IDEA-52379)
authorpeter <peter@jetbrains.com>
Thu, 18 Sep 2014 14:10:38 +0000 (16:10 +0200)
committerpeter <peter@jetbrains.com>
Thu, 18 Sep 2014 14:22:28 +0000 (16:22 +0200)
12 files changed:
.idea/libraries/Groovy_Eclipse_Batch.xml [new file with mode: 0644]
java/testFramework/src/com/intellij/testFramework/CompilerTester.java
jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java
license/eclipse_license.txt [new file with mode: 0644]
plugins/groovy/jps-plugin/groovy-jps-plugin.iml
plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/EclipseOutputParser.java [new file with mode: 0644]
plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseBuilder.java [new file with mode: 0644]
plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseMain.java [new file with mode: 0644]
plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovyBuilder.java
plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GroovyBuilderService.java
plugins/groovy/lib/groovy-eclipse-batch-2.3.4-01.jar [new file with mode: 0644]
plugins/groovy/test/org/jetbrains/plugins/groovy/compiler/GroovyCompilerTest.groovy

diff --git a/.idea/libraries/Groovy_Eclipse_Batch.xml b/.idea/libraries/Groovy_Eclipse_Batch.xml
new file mode 100644 (file)
index 0000000..ebdc677
--- /dev/null
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="Groovy-Eclipse-Batch">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/community/plugins/groovy/lib/groovy-eclipse-batch-2.3.4-01.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
index 27f36509f60e55ce0397956c1003641dd00ee7b9..4815d3db80801d862feb3f0afda791c57323ecd7 100644 (file)
@@ -250,7 +250,10 @@ public class CompilerTester {
           CompilerMessage[] messages = compileContext.getMessages(category);
           for (CompilerMessage message : messages) {
             final String text = message.getMessage();
-            if (category != CompilerMessageCategory.INFORMATION || !(text.contains("Compilation completed successfully") || text.startsWith("Using javac"))) {
+            if (category != CompilerMessageCategory.INFORMATION ||
+                !(text.contains("Compilation completed successfully") ||
+                  text.startsWith("Using javac") ||
+                  text.startsWith("Using Groovy-Eclipse"))) {
               myMessages.add(message);
             }
           }
index 94b00d9838a1260574d313609477f2d49b43963e..409adcdfe05df0bbf079c30095adeebbeb54d8b5 100644 (file)
@@ -351,34 +351,13 @@ public class JavaBuilder extends ModuleLevelBuilder {
     final Set<JpsModule> modules = chunk.getModules();
     ProcessorConfigProfile profile = null;
     if (modules.size() == 1) {
-      final JpsModule module = modules.iterator().next();
-      profile = compilerConfig.getAnnotationProcessingProfile(module);
+      profile = compilerConfig.getAnnotationProcessingProfile(modules.iterator().next());
     }
     else {
-      // perform cycle-related validations
-      Pair<String, LanguageLevel> pair = null;
-      for (JpsModule module : modules) {
-        final LanguageLevel moduleLevel = javaExt.getLanguageLevel(module);
-        if (pair == null) {
-          pair = Pair.create(module.getName(), moduleLevel); // first value
-        }
-        else {
-          if (!Comparing.equal(pair.getSecond(), moduleLevel)) {
-            final String message = "Modules " + pair.getFirst()+ " and " +module.getName() + " must have the same language level because of cyclic dependencies between them";
-            diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
-            return true;
-          }
-        }
-      }
-
-      // check that all chunk modules are excluded from annotation processing
-      for (JpsModule module : modules) {
-        final ProcessorConfigProfile prof = compilerConfig.getAnnotationProcessingProfile(module);
-        if (prof.isEnabled()) {
-          final String message = "Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [" + chunk.getName() + "] are excluded from annotation processing";
-          diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
-          return true;
-        }
+      String message = validateCycle(chunk, javaExt, compilerConfig, modules);
+      if (message != null) {
+        diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, message));
+        return true;
       }
     }
 
@@ -422,7 +401,40 @@ public class JavaBuilder extends ModuleLevelBuilder {
       counter.await();
     }
   }
-                                                                            
+
+  @Nullable
+  public static String validateCycle(ModuleChunk chunk,
+                                     JpsJavaExtensionService javaExt,
+                                     JpsJavaCompilerConfiguration compilerConfig, Set<JpsModule> modules) {
+    Pair<String, LanguageLevel> pair = null;
+    for (JpsModule module : modules) {
+      final LanguageLevel moduleLevel = javaExt.getLanguageLevel(module);
+      if (pair == null) {
+        pair = Pair.create(module.getName(), moduleLevel); // first value
+      }
+      else {
+        if (!Comparing.equal(pair.getSecond(), moduleLevel)) {
+          return "Modules " +
+                 pair.getFirst() +
+                 " and " +
+                 module.getName() +
+                 " must have the same language level because of cyclic dependencies between them";
+        }
+      }
+    }
+
+    // check that all chunk modules are excluded from annotation processing
+    for (JpsModule module : modules) {
+      final ProcessorConfigProfile prof = compilerConfig.getAnnotationProcessingProfile(module);
+      if (prof.isEnabled()) {
+        return "Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [" +
+               chunk.getName() +
+               "] are excluded from annotation processing";
+      }
+    }
+    return null;
+  }
+
   private static boolean shouldForkCompilerProcess(CompileContext context, ModuleChunk chunk, JavaCompilingTool tool) {
     final int compilerSdkVersion = getCompilerSdkVersion(context);
     if (compilerSdkVersion < 9) {
diff --git a/license/eclipse_license.txt b/license/eclipse_license.txt
new file mode 100644 (file)
index 0000000..3d967ae
--- /dev/null
@@ -0,0 +1,70 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
\ No newline at end of file
index f101b29b53c613a302113a5a72a2f86176c37dd6..d90d6f6afe37e978158083b40a2f8a373f8e1984 100644 (file)
@@ -12,6 +12,7 @@
     <orderEntry type="module" module-name="groovy-rt-constants" />
     <orderEntry type="module" module-name="jps-model-serialization" />
     <orderEntry type="module" module-name="jps-model-impl" />
+    <orderEntry type="library" scope="PROVIDED" name="Groovy-Eclipse-Batch" level="project" />
   </component>
 </module>
 
diff --git a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/EclipseOutputParser.java b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/EclipseOutputParser.java
new file mode 100644 (file)
index 0000000..043dbb9
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.jps.incremental.groovy;
+
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.incremental.messages.BuildMessage;
+import org.jetbrains.jps.incremental.messages.CompilerMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Adapted from org.codehaus.groovy.eclipse.compiler.GroovyEclipseCompiler, part of maven groovy-eclipse-compiler plugin.
+ *
+ * The source is distributed under Eclipse Public License (http://www.eclipse.org/legal/epl-v10.html, eclipse_license.txt).
+ *
+ * @author peter
+ */
+class EclipseOutputParser {
+  private final String myBuilderName;
+  private final ModuleChunk myChunk;
+
+  public EclipseOutputParser(String builderName, ModuleChunk chunk) {
+    myBuilderName = builderName;
+    myChunk = chunk;
+  }
+
+  private static final String PROB_SEPARATOR = "----------\n";
+
+  List<CompilerMessage> parseMessages(String input) throws IOException {
+    if (input.contains("The type groovy.lang.GroovyObject cannot be resolved. It is indirectly referenced from required .class files")) {
+      return Arrays.asList(new CompilerMessage(myBuilderName, BuildMessage.Kind.ERROR,
+                                               "Cannot compile Groovy files: no Groovy library is defined for module '" + myChunk.representativeTarget().getModule().getName() + "'"));
+    }
+
+    List<CompilerMessage> parsedMessages = new ArrayList<CompilerMessage>();
+
+    String[] msgs = input.split(PROB_SEPARATOR);
+    for (String msg : msgs) {
+      if (msg.length() > 1) {
+        // add the error bean
+        CompilerMessage message = parseMessage(msg);
+        if (message != null) {
+          parsedMessages.add(message);
+        }
+        else {
+          // assume that there are one or more non-normal messages here
+          // All messages start with <num>. ERROR or <num>. WARNING
+          String[] extraMsgs = msg.split("\n");
+          StringBuilder sb = new StringBuilder();
+          for (String extraMsg : extraMsgs) {
+            if (extraMsg.indexOf(". WARNING") > 0 || extraMsg.indexOf(". ERROR") > 0) {
+              handleCurrentMessage(parsedMessages, sb);
+              sb = new StringBuilder("\n").append(extraMsg).append("\n");
+            }
+            else {
+              if (!PROB_SEPARATOR.equals(extraMsg)) {
+                sb.append(extraMsg).append("\n");
+              }
+            }
+          }
+          handleCurrentMessage(parsedMessages, sb);
+        }
+      }
+    }
+    return parsedMessages;
+  }
+
+  private void handleCurrentMessage(final List<CompilerMessage> parsedMessages, final StringBuilder sb) {
+    if (sb.length() > 0) {
+      ContainerUtil.addIfNotNull(parsedMessages, parseMessage(sb.toString()));
+    }
+  }
+
+  @Nullable
+  private CompilerMessage parseMessage(String msgText) {
+    // message should look like this:
+    //        1. WARNING in /Users/andrew/git-repos/foo/src/main/java/packAction.java (at line 47)
+    //            public abstract class AbstractScmTagAction extends TaskAction implements BuildBadgeAction {
+    //                                  ^^^^^^^^^^^^^^^^^^^^
+
+    // But there will also be messages contributed from annotation processors that will look non-normal
+    int dotIndex = msgText.indexOf('.');
+    BuildMessage.Kind kind;
+    boolean isNormal = false;
+    if (dotIndex > 0) {
+      if (msgText.substring(dotIndex, dotIndex + ". WARNING".length()).equals(". WARNING")) {
+        kind = BuildMessage.Kind.WARNING;
+        isNormal = true;
+        dotIndex += ". WARNING in ".length();
+      } else if (msgText.substring(dotIndex, dotIndex + ". ERROR".length()).equals(". ERROR")) {
+        kind = BuildMessage.Kind.ERROR;
+        isNormal = true;
+        dotIndex += ". ERROR in ".length();
+      } else {
+        kind = BuildMessage.Kind.INFO;
+      }
+    } else {
+      kind = BuildMessage.Kind.INFO;
+    }
+
+    int firstNewline = msgText.indexOf('\n');
+    String firstLine = firstNewline > 0 ? msgText.substring(0, firstNewline) : msgText;
+    String rest = firstNewline > 0 ? msgText.substring(firstNewline+1).trim() : "";
+
+    if (isNormal) {
+      try {
+        int parenIndex = firstLine.indexOf(" (");
+        String file = firstLine.substring(dotIndex, parenIndex);
+        int line = Integer.parseInt(firstLine.substring(parenIndex + " (at line ".length(), firstLine.indexOf(')')));
+        int lastLineIndex = rest.lastIndexOf("\n");
+        return new CompilerMessage(myBuilderName, kind, rest.substring(lastLineIndex + 1), file, -1, -1, -1, line, -1);
+      }
+      catch (RuntimeException ignore) {
+      }
+    }
+
+    if (msgText.trim().matches("(\\d)+ problem(s)? \\((\\d)+ (error|warning)(s)?\\)")) {
+      return null;
+    }
+
+    return new CompilerMessage(myBuilderName, kind, msgText);
+  }
+
+}
diff --git a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseBuilder.java b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseBuilder.java
new file mode 100644 (file)
index 0000000..8dd5a78
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.jps.incremental.groovy;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.ProjectPaths;
+import org.jetbrains.jps.builders.DirtyFilesHolder;
+import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor;
+import org.jetbrains.jps.incremental.*;
+import org.jetbrains.jps.incremental.java.JavaBuilder;
+import org.jetbrains.jps.incremental.messages.BuildMessage;
+import org.jetbrains.jps.incremental.messages.CompilerMessage;
+import org.jetbrains.jps.incremental.messages.ProgressMessage;
+import org.jetbrains.jps.model.java.JpsJavaExtensionService;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
+import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
+import org.jetbrains.jps.model.module.JpsModule;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * @author peter
+ */
+public class GreclipseBuilder extends ModuleLevelBuilder {
+  private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.groovy.GreclipseBuilder");
+  private static final Key<Boolean> COMPILER_VERSION_INFO = Key.create("_greclipse_compiler_info_");
+  private final ClassLoader myGreclipseLoader;
+
+  protected GreclipseBuilder(@NotNull ClassLoader greclipseLoader) {
+    super(BuilderCategory.TRANSLATOR);
+    myGreclipseLoader = greclipseLoader;
+  }
+
+  @Override
+  public List<String> getCompilableFileExtensions() {
+    return Arrays.asList("groovy", "java");
+  }
+
+  @Override
+  public void buildStarted(CompileContext context) {
+    JavaBuilder.IS_ENABLED.set(context, Boolean.FALSE);
+  }
+
+  @Override
+  public ExitCode build(final CompileContext context,
+                        ModuleChunk chunk,
+                        DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
+                        OutputConsumer outputConsumer) throws ProjectBuildException, IOException {
+    try {
+      final List<File> toCompile = GroovyBuilder.collectChangedFiles(context, dirtyFilesHolder, false, true);
+      if (toCompile.isEmpty()) {
+        return ExitCode.NOTHING_DONE;
+      }
+
+      Map<ModuleBuildTarget, String> outputDirs = GroovyBuilder.getCanonicalModuleOutputs(context, chunk, this);
+      if (outputDirs == null) {
+        return ExitCode.ABORT;
+      }
+
+      final JpsJavaExtensionService javaExt = JpsJavaExtensionService.getInstance();
+      final JpsJavaCompilerConfiguration compilerConfig = javaExt.getCompilerConfiguration(context.getProjectDescriptor().getProject());
+      assert compilerConfig != null;
+
+      final Set<JpsModule> modules = chunk.getModules();
+      ProcessorConfigProfile profile = null;
+      if (modules.size() == 1) {
+        profile = compilerConfig.getAnnotationProcessingProfile(modules.iterator().next());
+      }
+      else {
+        String message = JavaBuilder.validateCycle(chunk, javaExt, compilerConfig, modules);
+        if (message != null) {
+          context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, message));
+          return ExitCode.ABORT;
+        }
+      }
+
+
+      String mainOutputDir = outputDirs.get(chunk.representativeTarget());
+      final List<String> args = createCommandLine(context, chunk, toCompile, mainOutputDir, profile);
+
+      if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
+        LOG.debug("Compiling with args: " + args);
+      }
+
+      Boolean notified = COMPILER_VERSION_INFO.get(context);
+      if (notified != Boolean.TRUE) {
+        context.processMessage(new CompilerMessage("", BuildMessage.Kind.INFO, "Using Groovy-Eclipse to compile Java & Groovy sources"));
+        COMPILER_VERSION_INFO.set(context, Boolean.TRUE);
+      }
+
+      context.processMessage(new ProgressMessage("Compiling java & groovy [" + chunk.getPresentableShortName() + "]"));
+
+      StringWriter out = new StringWriter();
+      StringWriter err = new StringWriter();
+      HashMap<String, List<String>> outputMap = ContainerUtil.newHashMap();
+
+      boolean success = performCompilation(args, out, err, outputMap, context, chunk);
+      
+      List<GroovycOSProcessHandler.OutputItem> items = ContainerUtil.newArrayList();
+      for (String src : outputMap.keySet()) {
+        //noinspection ConstantConditions
+        for (String classFile : outputMap.get(src)) {
+          items.add(new GroovycOSProcessHandler.OutputItem(FileUtil.toSystemIndependentName(mainOutputDir + classFile),
+                                                           FileUtil.toSystemIndependentName(src)));
+        }
+      }
+      Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> successfullyCompiled =
+        GroovyBuilder.processCompiledFiles(context, chunk, outputDirs, mainOutputDir, items);
+
+      EclipseOutputParser parser = new EclipseOutputParser(getPresentableName(), chunk);
+      List<CompilerMessage> messages = ContainerUtil.concat(parser.parseMessages(out.toString()), parser.parseMessages(err.toString()));
+      boolean hasError = false;
+      for (CompilerMessage message : messages) {
+        if (message.getKind() == BuildMessage.Kind.ERROR) {
+          hasError = true;
+        }
+        context.processMessage(message);
+      }
+
+      if (!success && !hasError) {
+        context.processMessage(new CompilerMessage(getPresentableName(), BuildMessage.Kind.ERROR, "Compilation failed"));
+      }
+
+      if (GroovyBuilder.updateDependencies(context, chunk, dirtyFilesHolder, toCompile, successfullyCompiled, outputConsumer, this)) {
+        return ExitCode.ADDITIONAL_PASS_REQUIRED;
+      }
+      return ExitCode.OK;
+
+    }
+    catch (Exception e) {
+      throw new ProjectBuildException(e);
+    }
+  }
+
+  private boolean performCompilation(List<String> args, StringWriter out, StringWriter err, Map<String, List<String>> outputs, CompileContext context, ModuleChunk chunk) {
+    try {
+      Class<?> mainClass = Class.forName(GreclipseMain.class.getName(), true, myGreclipseLoader);
+      Constructor<?> constructor = mainClass.getConstructor(PrintWriter.class, PrintWriter.class, Map.class, Map.class);
+      Method compileMethod = mainClass.getMethod("compile", String[].class);
+
+      HashMap<String, Object> customDefaultOptions = ContainerUtil.newHashMap();
+      // without this greclipse won't load AST transformations
+      customDefaultOptions.put("org.eclipse.jdt.core.compiler.groovy.groovyClassLoaderPath", getClasspathString(chunk));
+
+      // used by greclipse to cache transform loaders
+      // names should be different for production & tests
+      customDefaultOptions.put("org.eclipse.jdt.core.compiler.groovy.groovyProjectName", chunk.getPresentableShortName());
+
+      Object main = constructor.newInstance(new PrintWriter(out), new PrintWriter(err), customDefaultOptions, outputs);
+      return (Boolean)compileMethod.invoke(main, new Object[]{ArrayUtil.toStringArray(args)});
+    }
+    catch (Exception e) {
+      context.processMessage(new CompilerMessage(getPresentableName(), e));
+      return false;
+    }
+  }
+
+  private static List<String> createCommandLine(CompileContext context,
+                                                ModuleChunk chunk,
+                                                List<File> srcFiles,
+                                                String mainOutputDir, ProcessorConfigProfile profile) {
+    final List<String> args = new ArrayList<String>();
+
+    args.add("-cp");
+    args.add(getClasspathString(chunk));
+
+    JavaBuilder.addCompilationOptions(args, context, chunk, profile);
+
+    args.add("-d");
+    args.add(mainOutputDir);
+
+    for (File file : srcFiles) {
+      args.add(file.getPath());
+    }
+
+    return args;
+  }
+
+  private static String getClasspathString(ModuleChunk chunk) {
+    final Set<String> cp = new LinkedHashSet<String>();
+    for (File file : ProjectPaths.getCompilationClasspathFiles(chunk, chunk.containsTests(), false, false)) {
+      if (file.exists()) {
+        cp.add(FileUtil.toCanonicalPath(file.getPath()));
+      }
+    }
+    return StringUtil.join(cp, File.pathSeparator);
+  }
+
+  @NotNull
+  @Override
+  public String getPresentableName() {
+    return "Groovy-Eclipse";
+  }
+}
diff --git a/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseMain.java b/plugins/groovy/jps-plugin/src/org/jetbrains/jps/incremental/groovy/GreclipseMain.java
new file mode 100644 (file)
index 0000000..c6147ff
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.jps.incremental.groovy;
+
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.batch.Main;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author peter
+ */
+public class GreclipseMain extends Main {
+  private final Map<String, List<String>> myOutputs;
+
+  public GreclipseMain(PrintWriter outWriter, PrintWriter errWriter, Map customDefaultOptions, Map<String, List<String>> outputs) {
+    super(new PrintWriter(outWriter), new PrintWriter(errWriter), false, customDefaultOptions, null);
+    myOutputs = outputs;
+  }
+
+  @Override
+  public void outputClassFiles(CompilationResult result) {
+    super.outputClassFiles(result);
+
+    if (result == null || result.hasErrors() && !proceedOnError) {
+      return;
+    }
+
+    List<String> classFiles = new ArrayList<String>();
+    for (ClassFile file : result.getClassFiles()) {
+      classFiles.add(new String(file.fileName()) + ".class");
+    }
+    myOutputs.put(new String(result.getFileName()), classFiles);
+  }
+
+}
index 03e3b544b5ed8a0b4bae085f52a2be9058f09d66..4540c3afd0b18e3b95ee43abd0303c7e53911819 100644 (file)
@@ -100,7 +100,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
     try {
       JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject());
 
-      final List<File> toCompile = collectChangedFiles(context, dirtyFilesHolder, settings);
+      final List<File> toCompile = collectChangedFiles(context, dirtyFilesHolder, myForStubs, false);
       if (toCompile.isEmpty()) {
         return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.NOTHING_DONE;
       }
@@ -108,7 +108,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
         LOG.info("forStubs=" + myForStubs);
       }
 
-      Map<ModuleBuildTarget, String> finalOutputs = getCanonicalModuleOutputs(context, chunk);
+      Map<ModuleBuildTarget, String> finalOutputs = getCanonicalModuleOutputs(context, chunk, this);
       if (finalOutputs == null) {
         return ExitCode.ABORT;
       }
@@ -146,7 +146,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
       final GroovycOSProcessHandler handler = runGroovyc(context, chunk, tempFile, settings, classpath, optimizeClassLoading);
 
       Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>>
-        compiled = processCompiledFiles(context, chunk, generationOutputs, compilerOutput, handler);
+        compiled = processCompiledFiles(context, chunk, generationOutputs, compilerOutput, handler.getSuccessfullyCompiled());
 
       if (checkChunkRebuildNeeded(context, handler)) {
         return ExitCode.CHUNK_REBUILD_REQUIRED;
@@ -161,7 +161,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
         context.processMessage(message);
       }
 
-      if (!myForStubs && updateDependencies(context, chunk, dirtyFilesHolder, toCompile, compiled, outputConsumer)) {
+      if (!myForStubs && updateDependencies(context, chunk, dirtyFilesHolder, toCompile, compiled, outputConsumer, this)) {
         return ExitCode.ADDITIONAL_PASS_REQUIRED;
       }
       return hasFilesToCompileForNextRound(context) ? ExitCode.ADDITIONAL_PASS_REQUIRED : ExitCode.OK;
@@ -288,15 +288,16 @@ public class GroovyBuilder extends ModuleLevelBuilder {
     }
   }
 
-  private static Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> processCompiledFiles(CompileContext context,
-                                                                               ModuleChunk chunk,
-                                                                               Map<ModuleBuildTarget, String> generationOutputs,
-                                                                               String compilerOutput, GroovycOSProcessHandler handler)
+  public static Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> processCompiledFiles(CompileContext context,
+                                                                                                             ModuleChunk chunk,
+                                                                                                             Map<ModuleBuildTarget, String> generationOutputs,
+                                                                                                             String compilerOutput,
+                                                                                                             List<GroovycOSProcessHandler.OutputItem> successfullyCompiled)
     throws IOException {
     ProjectDescriptor pd = context.getProjectDescriptor();
 
     final Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> compiled = new THashMap<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>>();
-    for (final GroovycOSProcessHandler.OutputItem item : handler.getSuccessfullyCompiled()) {
+    for (final GroovycOSProcessHandler.OutputItem item : successfullyCompiled) {
       if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
         LOG.info("compiled=" + item);
       }
@@ -314,7 +315,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
       }
       else {
         if (Utils.IS_TEST_MODE || LOG.isDebugEnabled()) {
-          LOG.info("No java source root descriptor for th e item found =" + item);
+          LOG.info("No java source root descriptor for the item found =" + item);
         }
       }
     }
@@ -347,14 +348,16 @@ public class GroovyBuilder extends ModuleLevelBuilder {
   }
 
   @Nullable
-  private Map<ModuleBuildTarget, String> getCanonicalModuleOutputs(CompileContext context, ModuleChunk chunk) {
+  public static Map<ModuleBuildTarget, String> getCanonicalModuleOutputs(CompileContext context, ModuleChunk chunk, Builder builder) {
     Map<ModuleBuildTarget, String> finalOutputs = new HashMap<ModuleBuildTarget, String>();
     for (ModuleBuildTarget target : chunk.getTargets()) {
       File moduleOutputDir = target.getOutputDir();
       if (moduleOutputDir == null) {
-        context.processMessage(new CompilerMessage(myBuilderName, BuildMessage.Kind.ERROR, "Output directory not specified for module " + target.getModule().getName()));
+        context.processMessage(new CompilerMessage(builder.getPresentableName(), BuildMessage.Kind.ERROR, "Output directory not specified for module " + target.getModule().getName()));
         return null;
       }
+      //noinspection ResultOfMethodCallIgnored
+      moduleOutputDir.mkdirs();
       String moduleOutputPath = FileUtil.toCanonicalPath(moduleOutputDir.getPath());
       assert moduleOutputPath != null;
       finalOutputs.put(target, moduleOutputPath.endsWith("/") ? moduleOutputPath : moduleOutputPath + "/");
@@ -395,18 +398,25 @@ public class GroovyBuilder extends ModuleLevelBuilder {
     return chunk.getModules().iterator().next().getSdk(JpsJavaSdkType.INSTANCE);
   }
 
-  private List<File> collectChangedFiles(CompileContext context,
-                                                DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, final JpsGroovySettings settings) throws IOException {
+  static List<File> collectChangedFiles(CompileContext context,
+                                        DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
+                                        final boolean forStubs, final boolean forEclipse)
+    throws IOException {
 
-    final JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getCompilerConfiguration(context.getProjectDescriptor().getProject());
+    final JpsJavaCompilerConfiguration configuration =
+      JpsJavaExtensionService.getInstance().getCompilerConfiguration(context.getProjectDescriptor().getProject());
     assert configuration != null;
 
+    final JpsGroovySettings settings = JpsGroovySettings.getSettings(context.getProjectDescriptor().getProject());
+
     final List<File> toCompile = new ArrayList<File>();
     dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() {
       public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor sourceRoot) throws IOException {
         final String path = file.getPath();
-        if (isGroovyFile(path) && !configuration.isResourceFile(file, sourceRoot.root)) { //todo file type check
-          if (myForStubs && settings.isExcludedFromStubGeneration(file)) {
+        //todo file type check
+        if ((isGroovyFile(path) || forEclipse && path.endsWith(".java")) &&
+            !configuration.isResourceFile(file, sourceRoot.root)) {
+          if (forStubs && settings.isExcludedFromStubGeneration(file)) {
             return true;
           }
 
@@ -418,12 +428,12 @@ public class GroovyBuilder extends ModuleLevelBuilder {
     return toCompile;
   }
 
-  private boolean updateDependencies(CompileContext context,
+  public static boolean updateDependencies(CompileContext context,
                                             ModuleChunk chunk,
                                             DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder,
                                             List<File> toCompile,
                                             Map<ModuleBuildTarget, Collection<GroovycOSProcessHandler.OutputItem>> successfullyCompiled,
-                                            OutputConsumer outputConsumer) throws IOException {
+                                            OutputConsumer outputConsumer, Builder builder) throws IOException {
     final Mappings delta = context.getProjectDescriptor().dataManager.getMappings().createDelta();
     final List<File> successfullyCompiledFiles = new ArrayList<File>();
     if (!successfullyCompiled.isEmpty()) {
@@ -451,7 +461,7 @@ public class GroovyBuilder extends ModuleLevelBuilder {
             final String message = "Class dependency information may be incomplete! Error parsing generated class " + item.outputPath;
             LOG.info(message, e);
             context.processMessage(new CompilerMessage(
-              myBuilderName, BuildMessage.Kind.WARNING, message + "\n" + CompilerMessage.getTextFromThrowable(e), sourcePath)
+              builder.getPresentableName(), BuildMessage.Kind.WARNING, message + "\n" + CompilerMessage.getTextFromThrowable(e), sourcePath)
             );
           }
           successfullyCompiledFiles.add(srcFile);
index 06e91dc54ce06b3210fcc94e6c31e7027f76e4d3..dc4d5198c9018b0a49cc8f42f35a85ead560cf6b 100644 (file)
  */
 package org.jetbrains.jps.incremental.groovy;
 
+import com.intellij.openapi.application.PathManager;
+import com.intellij.util.ObjectUtils;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.jetbrains.jps.incremental.BuilderService;
 import org.jetbrains.jps.incremental.ModuleLevelBuilder;
 
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
 import java.util.Arrays;
 import java.util.List;
 
@@ -26,11 +32,38 @@ import java.util.List;
  * @author peter
  */
 public class GroovyBuilderService extends BuilderService {
+  /**
+   * All Groovy-Eclipse stuff is contained in a separate classLoader to avoid clashes with ecj.jar being in the classpath of the builder process
+   */
+  @Nullable
+  private static final ClassLoader ourGreclipseLoader = createGreclipseLoader();
+
+  @Nullable
+  private static ClassLoader createGreclipseLoader() {
+    String jar = System.getProperty("groovy.eclipse.batch.jar");
+    if (jar == null) return null;
+
+    try {
+      URL[] urls = {
+        new File(jar).toURI().toURL(),
+        new File(ObjectUtils.assertNotNull(PathManager.getJarPathForClass(GreclipseMain.class))).toURI().toURL()
+      };
+      ClassLoader loader = new URLClassLoader(urls, null);
+      Class.forName("org.eclipse.jdt.internal.compiler.batch.Main", false, loader);
+      return loader;
+    }
+    catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   @NotNull
   @Override
   public List<? extends ModuleLevelBuilder> createModuleLevelBuilders() {
-    return Arrays.asList(new GroovyBuilder(true),
-                         new GroovyBuilder(false));
+    if (ourGreclipseLoader != null) {
+      return Arrays.asList(new GreclipseBuilder(ourGreclipseLoader));
+    }
+    return Arrays.asList(new GroovyBuilder(true), new GroovyBuilder(false));
   }
 
 }
diff --git a/plugins/groovy/lib/groovy-eclipse-batch-2.3.4-01.jar b/plugins/groovy/lib/groovy-eclipse-batch-2.3.4-01.jar
new file mode 100644 (file)
index 0000000..03a9cb5
Binary files /dev/null and b/plugins/groovy/lib/groovy-eclipse-batch-2.3.4-01.jar differ
index d6363fb560db86c77c250b4b291b1b318edd55ee..6062196be0ad7498360fe369d790b8c3a3eacc08 100644 (file)
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.groovy.compiler
 
 import com.intellij.compiler.CompilerConfiguration
 import com.intellij.compiler.CompilerConfigurationImpl
+import com.intellij.compiler.CompilerWorkspaceConfiguration
 import com.intellij.execution.executors.DefaultRunExecutor
 import com.intellij.execution.impl.DefaultJavaProgramRunner
 import com.intellij.execution.process.ProcessAdapter
@@ -27,6 +28,7 @@ import com.intellij.execution.process.ProcessOutputTypes
 import com.intellij.execution.runners.ProgramRunner
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.application.PathManager
+import com.intellij.openapi.application.PluginPathManager
 import com.intellij.openapi.compiler.CompilerMessage
 import com.intellij.openapi.compiler.CompilerMessageCategory
 import com.intellij.openapi.compiler.options.ExcludeEntryDescription
@@ -35,6 +37,7 @@ import com.intellij.openapi.module.Module
 import com.intellij.openapi.roots.ModuleRootModificationUtil
 import com.intellij.openapi.util.Key
 import com.intellij.openapi.util.Ref
+import com.intellij.openapi.util.io.FileUtil
 import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.psi.PsiFile
 import com.intellij.testFramework.PsiTestUtil
@@ -45,7 +48,7 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyFile
 /**
  * @author peter
  */
-public class GroovyCompilerTest extends GroovyCompilerTestCase {
+public abstract class GroovyCompilerTest extends GroovyCompilerTestCase {
   @Override protected void setUp() {
     super.setUp();
     addGroovyLibrary(myModule);
@@ -303,24 +306,26 @@ public class GroovyCompilerTest extends GroovyCompilerTestCase {
   }
 
   private void addTransform() throws IOException {
-    myFixture.addFileToProject("Transf.groovy", """
-import org.codehaus.groovy.ast.*
-import org.codehaus.groovy.control.*
-import org.codehaus.groovy.transform.*
+    myFixture.addFileToProject("Transf.java", """
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.control.*;
+import org.codehaus.groovy.transform.*;
 @GroovyASTTransformation(phase = CompilePhase.CONVERSION)
 public class Transf implements ASTTransformation {
-  void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
-    ModuleNode module = nodes[0]
-    for (clazz in module.classes) {
-      if (clazz.name.contains('Bar')) {
-        module.addStaticStarImport('Foo', ClassHelper.makeWithoutCaching(Foo.class))
+  public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
+    ModuleNode module = (ModuleNode)nodes[0];
+    for (ClassNode clazz : module.getClasses()) {
+
+      if (clazz.getName().contains("Bar")) {
+        module.addStaticStarImport("Foo", ClassHelper.makeWithoutCaching(Foo.class));
       }
     }
+      //throw new RuntimeException("In class " + nodes[0]);
   }
 }""");
 
-    myFixture.addFileToProject("Foo.groovy", "class Foo {\n" +
-                                             "static def autoImported() { 239 }\n" +
+    myFixture.addFileToProject("Foo.java", "public class Foo {\n" +
+                                             "public static int autoImported() { return 239; }\n" +
                                              "}");
 
     CompilerConfiguration.getInstance(getProject()).addResourceFilePattern("*.ASTTransformation");
@@ -503,7 +508,7 @@ class Usage {
   }
 
   public void testGroovyAnnotations() {
-    myFixture.addClass 'public @interface Anno { Class[] value(); }'
+    myFixture.addClass 'public @interface Anno { Class<?>[] value(); }'
     myFixture.addFileToProject 'Foo.groovy', '@Anno([String]) class Foo {}'
     myFixture.addFileToProject 'Bar.java', 'class Bar extends Foo {}'
 
@@ -512,7 +517,7 @@ class Usage {
 
   public void testGenericStubs() {
     myFixture.addFileToProject 'Foo.groovy', 'class Foo { List<String> list }'
-    myFixture.addFileToProject 'Bar.java', 'class Bar {{ for (String s : new Foo().getList()) {} }}'
+    myFixture.addFileToProject 'Bar.java', 'class Bar {{ for (String s : new Foo().getList()) { s.hashCode(); } }}'
     assertEmpty make()
   }
 
@@ -697,8 +702,7 @@ public class Main {
     assertEmpty make()
   }
 
-  //todo jeka: when recompiling module, delete all class files including those with excluded source
-  public void "_test reporting module compile errors caused by missing files excluded from compilation"() {
+  public void "test reporting module compile errors caused by missing files excluded from compilation"() {
     def foo = myFixture.addFileToProject('Foo.groovy', 'class Foo {}')
     myFixture.addFileToProject('Bar.groovy', 'class Bar extends Foo {}')
 
@@ -723,22 +727,6 @@ public class Main {
     assert findClassFile("Client")
   }
 
-  public void "test navigate from stub to source"() {
-    GroovyFile groovyFile = (GroovyFile) myFixture.addFileToProject("a.groovy", "class Groovy3 { InvalidType type }")
-    myFixture.addClass("class Java4 extends Groovy3 {}").containingFile
-
-    def msg = make().find { it.message.contains('InvalidType') }
-    assert msg?.virtualFile
-    ApplicationManager.application.runWriteAction { msg.virtualFile.delete(this) }
-
-    def messages = make()
-    assert messages
-    def error = messages.find { it.message.contains('InvalidType') }
-    assert error?.virtualFile
-    assert groovyFile.classes[0] == GroovyCompilerLoader.findClassByStub(project, error.virtualFile)
-
-  }
-
   public void "test ignore groovy internal non-existent interface helper inner class"() {
     myFixture.addFileToProject 'Foo.groovy', '''
 interface Foo {}
@@ -865,4 +853,43 @@ class AppTest {
     assertTrue(exceptionFound.get());
   }
 
+  static class GroovycTest extends GroovyCompilerTest {
+    public void "test navigate from stub to source"() {
+      GroovyFile groovyFile = (GroovyFile) myFixture.addFileToProject("a.groovy", "class Groovy3 { InvalidType type }")
+      myFixture.addClass("class Java4 extends Groovy3 {}").containingFile
+
+      def msg = make().find { it.message.contains('InvalidType') }
+      assert msg?.virtualFile
+      ApplicationManager.application.runWriteAction { msg.virtualFile.delete(this) }
+
+      def messages = make()
+      assert messages
+      def error = messages.find { it.message.contains('InvalidType') }
+      assert error?.virtualFile
+      assert groovyFile.classes[0] == GroovyCompilerLoader.findClassByStub(project, error.virtualFile)
+    }
+  }
+
+  static class EclipseTest extends GroovyCompilerTest {
+    @Override
+    protected void setUp() {
+      super.setUp()
+
+      def conf = CompilerWorkspaceConfiguration.getInstance(project)
+      assert conf.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS == CompilerWorkspaceConfiguration.DEFAULT_COMPILE_PROCESS_VM_OPTIONS
+
+      def jarName = "groovy-eclipse-batch-2.3.4-01.jar"
+      def jarPath = FileUtil.toCanonicalPath(PluginPathManager.getPluginHomePath("groovy") + "/lib/" + jarName)
+      conf.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS = "-Dgroovy.eclipse.batch.jar=" + jarPath
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+      def conf = CompilerWorkspaceConfiguration.getInstance(project)
+      conf.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS = CompilerWorkspaceConfiguration.DEFAULT_COMPILE_PROCESS_VM_OPTIONS
+
+      super.tearDown()
+    }
+  }
+
 }