From ba55debb17b1d874b84291f7aadf7e2f7ba5ec75 Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 7 May 2023 11:36:47 +0200 Subject: [PATCH] Verify bootclasspath version is at most compilation runtime version The bootclasspath is loaded by the Java toolchain's runtime used for compilation and thus needs to be compatible with it, otherwise this triggers errors such as: ``` error: cannot access module-info bad class file: /modules/jdk.net/module-info.class class file has wrong version 64.0, should be 61.0 Please remove or make sure it appears in the correct subdirectory of the classpath. error: cannot access module-info bad class file: /modules/jdk.accessibility/module-info.class class file has wrong version 64.0, should be 61.0 Please remove or make sure it appears in the correct subdirectory of the classpath. error: cannot access module-info bad class file: /modules/jdk.internal.vm.compiler.management/module-info.class class file has wrong version 64.0, should be 61.0 Please remove or make sure it appears in the correct subdirectory of the classpath. ``` --- .../lib/rules/java/BootClassPathInfo.java | 24 ++++++++ .../build/lib/rules/java/JavaToolchain.java | 7 +++ src/test/shell/bazel/bazel_with_jdk_test.sh | 61 +++++++++++++++++++ tools/jdk/default_java_toolchain.bzl | 6 +- 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java index ad5f49b1a63e1b..741bd063c5ff8a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java @@ -35,6 +35,7 @@ import net.starlark.java.eval.NoneType; import net.starlark.java.eval.Sequence; import net.starlark.java.eval.Starlark; +import net.starlark.java.eval.StarlarkInt; import net.starlark.java.eval.StarlarkThread; import net.starlark.java.eval.StarlarkValue; import net.starlark.java.syntax.Location; @@ -74,6 +75,13 @@ private Provider() { "The inputs to javac's --system flag, either a directory or a listing of files," + " which must contain at least 'release', 'lib/modules', and" + " 'lib/jrt-fs.jar'"), + @Param( + name = "version", + positional = false, + named = true, + defaultValue = "0", + doc = "The major version of the runtime providing this bootclasspath." + ), }, selfCall = true, useStarlarkThread = true) @@ -81,15 +89,21 @@ public BootClassPathInfo bootClassPathInfo( Sequence bootClassPathList, Sequence auxiliaryList, Object systemOrNone, + StarlarkInt version, StarlarkThread thread) throws EvalException { NestedSet systemInputs = getSystemInputs(systemOrNone); Optional systemPath = getSystemPath(systemInputs); + int versionChecked = version.toInt("version"); + if (versionChecked < 0) { + throw Starlark.errorf("version must be non-negative, got %d", versionChecked); + } return new BootClassPathInfo( getBootClassPath(bootClassPathList), getAuxiliary(auxiliaryList), systemInputs, systemPath, + versionChecked, thread.getCallerLocation()); } @@ -150,18 +164,21 @@ private static Optional getSystemPath(NestedSet systemIn private final NestedSet auxiliary; private final NestedSet systemInputs; private final Optional systemPath; + private final int version; private BootClassPathInfo( NestedSet bootclasspath, NestedSet auxiliary, NestedSet systemInputs, Optional systemPath, + int version, Location creationLocation) { super(creationLocation); this.bootclasspath = bootclasspath; this.auxiliary = auxiliary; this.systemInputs = systemInputs; this.systemPath = systemPath; + this.version = version; } @Override @@ -175,6 +192,7 @@ public static BootClassPathInfo create(NestedSet bootclasspath) { NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), Optional.empty(), + 0, null); } @@ -184,6 +202,7 @@ public static BootClassPathInfo empty() { NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER), Optional.empty(), + 0, null); } @@ -210,6 +229,11 @@ public NestedSet systemInputs() { return systemInputs; } + /** The major version of the runtime providing this bootclasspath. */ + public int version() { + return version; + } + public boolean isEmpty() { return bootclasspath.isEmpty(); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java index 5e6b31a72de646..544d742eb0f781 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java @@ -160,6 +160,13 @@ public ConfiguredTarget create(RuleContext ruleContext) JavaRuntimeInfo javaRuntime = JavaRuntimeInfo.from(ruleContext, "java_runtime"); + if (javaRuntime.version() != 0 && javaRuntime.version() < bootclasspath.version()) { + ruleContext.attributeError("java_runtime", String.format( + "The version of the Java compilation toolchain's java_runtime (%d) must be at least as " + + "high as the version of the Java runtime that provides the bootclasspath (%d)", + javaRuntime.version(), bootclasspath.version())); + } + JavaToolchainProvider provider = JavaToolchainProvider.create( ruleContext.getLabel(), diff --git a/src/test/shell/bazel/bazel_with_jdk_test.sh b/src/test/shell/bazel/bazel_with_jdk_test.sh index e6629c36e716a8..9fddcf0c88f6de 100755 --- a/src/test/shell/bazel/bazel_with_jdk_test.sh +++ b/src/test/shell/bazel/bazel_with_jdk_test.sh @@ -289,4 +289,65 @@ function test_bazel_compiles_with_localjdk() { expect_not_log "exec external/remotejdk11_linux/bin/java" } +function test_java_runtime_greater_than_java_toolchain_runtime_version() { + mkdir -p pkg + cat >pkg/BUILD <<'EOF' +java_binary( + name = "Main", + srcs = ["Main.java"], + main_class = "com.example.Main", +) +EOF + + cat >pkg/Main.java <<'EOF' +package com.example; +public class Main { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} +EOF + + bazel build //pkg:Main \ + --java_language_version=8 \ + --java_runtime_version=remotejdk_20 \ + &>"${TEST_log}" && fail "Expected build to fail" + + expect_log "The version of the Java compilation toolchain's java_runtime (17) must be at least as high as the version of the Java runtime that provides the bootclasspath (20)." +} + +function test_tool_java_runtime_greater_than_java_toolchain_runtime_version() { + mkdir -p pkg + cat >pkg/BUILD <<'EOF' +java_binary( + name = "Main", + srcs = ["Main.java"], + main_class = "com.example.Main", +) + +genrule( + name = "gen", + outs = ["gen.txt"], + tools = [":Main"], + cmd = "$(location :Main) > $@", +) +EOF + + cat >pkg/Main.java <<'EOF' +package com.example; +public class Main { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} +EOF + + bazel build //pkg:gen \ + --tool_java_language_version=8 \ + --tool_java_runtime_version=remotejdk_20 \ + &>"${TEST_log}" && fail "Expected build to fail" + + expect_log "The version of the Java compilation toolchain's java_runtime (17) must be at least as high as the version of the Java runtime that provides the bootclasspath (20)." +} + run_suite "Tests detection of local JDK and that Bazel executes with a bundled JDK." diff --git a/tools/jdk/default_java_toolchain.bzl b/tools/jdk/default_java_toolchain.bzl index a83e14e7251c30..90b459e33f4dfd 100644 --- a/tools/jdk/default_java_toolchain.bzl +++ b/tools/jdk/default_java_toolchain.bzl @@ -241,9 +241,12 @@ def _bootclasspath_impl(ctx): system = [f for f in ctx.files.target_javabase if f.basename in system_files] if len(system) != len(system_files): system = None + version = 0 if ctx.attr.target_javabase: + runtime_info = ctx.attr.target_javabase[java_common.JavaRuntimeInfo] inputs.extend(ctx.files.target_javabase) - args.add(ctx.attr.target_javabase[java_common.JavaRuntimeInfo].java_home) + args.add(runtime_info.java_home) + version = runtime_info.version ctx.actions.run( executable = str(host_javabase.java_executable_exec_path), @@ -257,6 +260,7 @@ def _bootclasspath_impl(ctx): java_common.BootClassPathInfo( bootclasspath = [bootclasspath], system = system, + version = version, ), OutputGroupInfo(jar = [bootclasspath]), ]