Skip to content

Commit

Permalink
Diagnose incompatible Java system classpaths
Browse files Browse the repository at this point in the history
When the Java system classpath extracted from the target Java runtime is more recent than the Java runtime used for Java compilation, JavaBuilder now emits a `[BazelJavaConfiguration]` diagnostic with actionable information instead of a bunch of "bad class file" errors on `module-info` files.

Example:
```
error: [BazelJavaConfiguration] The Java 17 runtime used to run javac is not recent enough to compile for the Java 20 runtime in external/remotejdk20_linux. Either register a Java toolchain with a newer java_runtime or specify a lower --java_runtime_version.
```

Work towards #17281

Closes #19547.

PiperOrigin-RevId: 571052686
Change-Id: Ifc1a2303895785482262db7653fe9eb8470361db
  • Loading branch information
fmeum authored and copybara-github committed Oct 5, 2023
1 parent 5bdb208 commit caa6b76
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.common.collect.MoreCollectors.toOptional;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
import static java.util.Locale.ENGLISH;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -72,6 +73,10 @@
*/
public class BlazeJavacMain {

private static final Pattern INCOMPATIBLE_SYSTEM_CLASS_PATH_ERROR =
Pattern.compile(
"(?s)bad class file: /modules/.*class file has wrong version (?<version>[4-9][0-9])\\.");

private static final Pattern UNSUPPORTED_CLASS_VERSION_ERROR =
Pattern.compile(
"^(?<class>[^ ]*) has been compiled by a more recent version of the Java Runtime "
Expand Down Expand Up @@ -183,6 +188,12 @@ public static BlazeJavacResult compile(BlazeJavacArguments arguments) {
errWriter.flush();
ImmutableList<FormattedDiagnostic> diagnostics = diagnosticsBuilder.build();

diagnostics.stream()
.map(d -> maybeGetJavaConfigurationError(arguments, d))
.flatMap(Optional::stream)
.findFirst()
.ifPresent(errOutput::append);

boolean werror =
diagnostics.stream().anyMatch(d -> d.getCode().equals("compiler.err.warnings.and.werror"));
if (status.equals(Status.OK)) {
Expand Down Expand Up @@ -279,6 +290,33 @@ private static boolean shouldReportDiagnostic(boolean werror, FormattedDiagnosti
return false;
}

private static Optional<String> maybeGetJavaConfigurationError(
BlazeJavacArguments arguments, Diagnostic<?> diagnostic) {
if (!diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
return Optional.empty();
}
Matcher matcher;
if (!diagnostic.getCode().equals("compiler.err.cant.access")
|| arguments.system() == null
|| !(matcher = INCOMPATIBLE_SYSTEM_CLASS_PATH_ERROR.matcher(diagnostic.getMessage(ENGLISH)))
.find()) {
return Optional.empty();
}
// The output path is of the form $PRODUCT-out/$CPU-$MODE[-exec-...]/bin/...
boolean isForTool = arguments.classOutput().subpath(1, 2).toString().contains("-exec-");
// Java 8 corresponds to class file major version 52.
int systemClasspathVersion = Integer.parseUnsignedInt(matcher.group("version")) - 44;
return Optional.of(
String.format(
"error: [BazelJavaConfiguration] The Java %d runtime used to run javac is not recent "
+ "enough to compile for the Java %d runtime in %s. Either register a Java "
+ "toolchain with a newer java_runtime or specify a lower %s.\n",
Runtime.version().feature(),
systemClasspathVersion,
arguments.system(),
isForTool ? "--tool_java_runtime_version" : "--java_runtime_version"));
}

/** Processes Plugin-specific arguments and removes them from the args array. */
@VisibleForTesting
static void processPluginArgs(
Expand Down
66 changes: 54 additions & 12 deletions src/test/shell/bazel/bazel_java17_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,16 @@ EOF
expect_log "^World\$"
}

function test_incompatible_processor() {
function test_incompatible_system_classpath() {
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 <<EOF
cat >pkg/BUILD <<'EOF'
load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain")
java_binary(
name = "Main",
srcs = ["Main.java"],
main_class = "com.example.Main",
# Exports an annotation processor.
deps = ["@bazel_tools//tools/java/runfiles"],
)
default_java_toolchain(
name = "java_toolchain",
Expand All @@ -163,27 +161,71 @@ EOF

cat >pkg/Main.java <<'EOF'
package com.example;
import java.net.URI;
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 \
--java_runtime_version=remotejdk_20 \
&>"${TEST_log}" && fail "Expected build to fail"

expect_log "error: \[BazelJavaConfiguration\] The Java 17 runtime used to run javac is not " \
"recent enough to compile for the Java 20 runtime in external/remotejdk20_[a-z0-9]*\. Either " \
"register a Java toolchain with a newer java_runtime or specify a lower " \
"--java_runtime_version\."
}

function test_incompatible_tool_system_classpath() {
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 <<'EOF'
load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain")
java_binary(
name = "Main",
srcs = ["Main.java"],
main_class = "com.example.Main",
)
genrule(
name = "gen",
outs = ["gen.txt"],
tools = [":Main"],
cmd = "$(location :Main) > $@",
)
default_java_toolchain(
name = "java_toolchain",
source_version = "17",
target_version = "17",
java_runtime = "@bazel_tools//tools/jdk:remotejdk_17",
)
EOF

cat >pkg/Main.java <<'EOF'
package com.example;
import java.net.URI;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
EOF

bazel build //pkg:gen \
--extra_toolchains=//pkg:java_toolchain_definition \
--tool_java_language_version=17 \
--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\."
expect_log "error: \[BazelJavaConfiguration\] The Java 17 runtime used to run javac is not " \
"recent enough to compile for the Java 20 runtime in external/remotejdk20_[a-z0-9]*\. Either " \
"register a Java toolchain with a newer java_runtime or specify a lower " \
"--tool_java_runtime_version\."
}

run_suite "Tests Java 17 language features"

0 comments on commit caa6b76

Please sign in to comment.