Skip to content

Commit

Permalink
[Backport] Do full recompilation on fatal failure for Groovy
Browse files Browse the repository at this point in the history
Backport of #24880
  • Loading branch information
asodja committed May 26, 2023
1 parent 569467b commit 10caff4
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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:
* <pre>
* 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)
* </pre>
*/
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 10caff4

Please sign in to comment.