diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java index 0ddc57a2ce526a..6a24ddb291d024 100644 --- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/BlazeJavacMain.java @@ -54,6 +54,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; @@ -70,6 +72,11 @@ */ public class BlazeJavacMain { + private static final Pattern UNSUPPORTED_CLASS_VERSION_ERROR = + Pattern.compile( + "^(?[^ ]*) has been compiled by a more recent version of the Java Runtime " + + "\\(class file version (?[4-9][0-9])\\."); + /** * Sets up a BlazeJavaCompiler with the given plugins within the given context. * @@ -139,8 +146,23 @@ public static BlazeJavacResult compile(BlazeJavacArguments arguments) { throw e.getCause(); } } catch (Exception t) { - if (t.getCause() instanceof CancelRequestException) { - return BlazeJavacResult.cancelled(t.getCause().getMessage()); + Throwable cause = t.getCause(); + if (cause instanceof CancelRequestException) { + return BlazeJavacResult.cancelled(cause.getMessage()); + } + Matcher matcher; + if (cause instanceof UnsupportedClassVersionError + && (matcher = UNSUPPORTED_CLASS_VERSION_ERROR.matcher(cause.getMessage())).find()) { + // Java 8 corresponds to class file major version 52. + int processorVersion = Integer.parseUnsignedInt(matcher.group("version")) - 44; + errWriter.printf( + "The Java %d runtime used to run javac is not recent enough to run the processor %s, " + + "which has been compiled targeting Java %d. Either register a Java toolchain " + + "with a newer java_runtime or, if this processor has been built with Bazel, " + + "specify a lower --tool_java_language_version.%n", + Runtime.version().feature(), + matcher.group("class").replace('/', '.'), + processorVersion); } t.printStackTrace(errWriter); status = Status.CRASH; diff --git a/src/test/shell/bazel/bazel_java17_test.sh b/src/test/shell/bazel/bazel_java17_test.sh index 2e9b889f1b8365..093546686c0ba7 100755 --- a/src/test/shell/bazel/bazel_java17_test.sh +++ b/src/test/shell/bazel/bazel_java17_test.sh @@ -140,5 +140,50 @@ EOF expect_log "^World\$" } +function test_incompatible_processor() { + mkdir -p pkg + # This test defines a custom Java toolchain as it relies on the availability of a runtime that is + # strictly newer than the one specified as the toolchain's java_runtime. + cat >pkg/BUILD <pkg/Main.java <<'EOF' +package com.example; +public class Main { + public static void main(String[] args) { + System.out.println("Hello, world!"); + } +} +EOF + + # Specify a --tool_java_language_version higher than any tested runtime. + bazel build //pkg:Main \ + --extra_toolchains=//pkg:java_toolchain_definition \ + --java_language_version=17 \ + --java_runtime_version=remotejdk_17 \ + --tool_java_language_version=20 \ + --tool_java_runtime_version=remotejdk_20 \ + &>"${TEST_log}" && fail "Expected build to fail" + + expect_log "The Java 17 runtime used to run javac is not recent enough to run the processor " \ + "com\.google\.devtools\.build\.runfiles\.AutoBazelRepositoryProcessor, which has been " \ + "compiled targeting Java 20\. Either register a Java toolchain with a newer java_runtime " \ + "or, if this processor has been built with Bazel, specify a lower " \ + "--tool_java_language_version\." +} run_suite "Tests Java 17 language features"