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.
```
  • Loading branch information
fmeum committed Sep 20, 2023
1 parent 9e90a65 commit e65811e
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 0 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 @@ -54,6 +55,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;
Expand All @@ -70,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])\\.");

/**
* Sets up a BlazeJavaCompiler with the given plugins within the given context.
*
Expand Down Expand Up @@ -161,6 +168,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 @@ -257,6 +270,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
87 changes: 87 additions & 0 deletions src/test/shell/bazel/bazel_java17_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,92 @@ EOF
expect_log "^World\$"
}

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'
load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain")
java_binary(
name = "Main",
srcs = ["Main.java"],
main_class = "com.example.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:Main \
--extra_toolchains=//pkg:java_toolchain_definition \
--java_language_version=17 \
--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 "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 e65811e

Please sign in to comment.