diff --git a/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java b/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java index 52c837d7ea05..26bba16a9956 100644 --- a/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java +++ b/subprojects/language-groovy/src/main/java/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java @@ -29,9 +29,11 @@ import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.control.customizers.CompilationCustomizer; import org.codehaus.groovy.control.customizers.ImportCustomizer; +import org.codehaus.groovy.control.messages.ExceptionMessage; import org.codehaus.groovy.control.messages.SimpleMessage; import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit; import org.codehaus.groovy.tools.javac.JavaCompiler; @@ -283,6 +285,12 @@ private void copyJavaCompilerResult(ApiCompilerResult javaCompilerResult) { unit.compile(); return result; } catch (org.codehaus.groovy.control.CompilationFailedException e) { + if (isFatalException(e)) { + // This indicates a compiler bug and not a user error, + // so we cannot recover from such error: we need to force full recompilation. + throw new CompilationFatalException(e); + } + System.err.println(e.getMessage()); // Explicit flush, System.err is an auto-flushing PrintWriter unless it is replaced. System.err.flush(); @@ -297,6 +305,27 @@ private void copyJavaCompilerResult(ApiCompilerResult javaCompilerResult) { } } + /** + * Returns true if the exception is fatal, unrecoverable for the incremental compilation. Example of such error: + *
+     * error: startup failed:
+     * General error during instruction selection: java.lang.NoClassDefFoundError: Unable to load class ClassName due to missing dependency DependencyName
+     *   java.lang.RuntimeException: java.lang.NoClassDefFoundError: Unable to load class ClassName due to missing dependency DependencyName
+     *      at org.codehaus.groovy.control.CompilationUnit$IPrimaryClassNodeOperation.doPhaseOperation(CompilationUnit.java:977)
+     *      at org.codehaus.groovy.control.CompilationUnit.processPhaseOperations(CompilationUnit.java:672)
+     *      at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:636)
+     *      at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:611)
+     * 
+ */ + private static boolean isFatalException(org.codehaus.groovy.control.CompilationFailedException e) { + if (e instanceof MultipleCompilationErrorsException) { + // Groovy compiler wraps any uncontrolled exception (e.g. IOException, NoClassDefFoundError and similar) in a `ExceptionMessage` + return ((MultipleCompilationErrorsException) e).getErrorCollector().getErrors().stream() + .anyMatch(message -> message instanceof ExceptionMessage); + } + return false; + } + private static boolean shouldProcessAnnotations(GroovyJavaJointCompileSpec spec) { return spec.getGroovyCompileOptions().isJavaAnnotationProcessing() && spec.annotationProcessingConfigured(); } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractJavaGroovyIncrementalCompilationSupport.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractJavaGroovyIncrementalCompilationSupport.groovy index fd3cad79cf67..0c27ab20577d 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractJavaGroovyIncrementalCompilationSupport.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractJavaGroovyIncrementalCompilationSupport.groovy @@ -53,7 +53,7 @@ abstract class AbstractJavaGroovyIncrementalCompilationSupport extends AbstractI def packageGroup = (body =~ "\\s*package ([\\w.]+).*") String packageName = packageGroup.size() > 0 ? packageGroup[0][1] : "" String packageFolder = packageName.replaceAll("[.]", "/") - def className = (body =~ /(?s).*?(?:class|interface|enum) ([\w$]+) .*/)[0][1] + def className = (body =~ /(?s).*?(?:class|interface|enum|trait) ([\w$]+) .*/)[0][1] assert className: "unable to find class name" def f if (packageFolder.isEmpty()) { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/BaseIncrementalCompilationAfterFailureIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/BaseIncrementalCompilationAfterFailureIntegrationTest.groovy index 453cb805b4fa..43c563e322dd 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/BaseIncrementalCompilationAfterFailureIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/BaseIncrementalCompilationAfterFailureIntegrationTest.groovy @@ -543,4 +543,42 @@ class GroovyIncrementalCompilationAfterFailureIntegrationTest extends BaseIncrem "Java" | "java" | "" "Groovy" | "groovy" | COMPILE_STATIC_ANNOTATION } + + @Issue("https://github.com/gradle/gradle/issues/22814") + def 'does full recompilation on fatal failure'() { + given: + def a = source("class A extends ABase implements WithTrait { def m() { println('a') } }") + source "class ABase { def mBase() { println(A) } }" + source """ + import groovy.transform.SelfType + @SelfType(ABase) + trait WithTrait { + final AllPluginsValidation allPlugins = new AllPluginsValidation(this) + + static class AllPluginsValidation { + final ABase base + AllPluginsValidation(ABase base) { + this.base = base + } + } + } + """ + run "compileGroovy" + outputs.recompiledClasses('ABase', 'A', 'WithTrait', 'WithTrait$Trait$FieldHelper', 'WithTrait$AllPluginsValidation', 'WithTrait$Trait$Helper') + + when: + a.text = "class A extends ABase implements WithTrait { def m() { println('b') } }" + + then: + executer.withStackTraceChecksDisabled() + def execution = runAndFail "compileGroovy" + execution.assertHasCause("Unrecoverable compilation error") + + when: + run"compileGroovy", "--info" + + then: + outputs.recompiledClasses('ABase', 'A', 'WithTrait', 'WithTrait$Trait$FieldHelper', 'WithTrait$AllPluginsValidation', 'WithTrait$Trait$Helper') + outputContains("Full recompilation is required") + } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/CompilationFatalException.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/CompilationFatalException.java new file mode 100644 index 000000000000..0476eeda4223 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/CompilationFatalException.java @@ -0,0 +1,27 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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.gradle.api.internal.tasks.compile; + +/** + * Indicates a fatal error during compilation. Gradle will not try to recover output files from a previous compilation. + */ +public class CompilationFatalException extends RuntimeException { + + public CompilationFatalException(Throwable cause) { + super("Unrecoverable compilation error: " + cause.getMessage(), cause); + } +}