From 2e00179c3c3eebd933c80a043c4fe3fbac3916ff Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Sun, 7 May 2023 11:36:47 +0200 Subject: [PATCH] Verify that Java language level isn't higher than runtime version The value of `--(tool_)java_language_version` results in a matching `-target` argument passed to javac and thus in the generation of class files that only run on runtimes of the same or higher versions. This commit introduces a check that fails if the language version is strictly higher than the version of the current runtime. In the exec configuration, this covers two situations at once: * Compilation of a Java tool, where the runtime version is determined by `--tool_java_runtime_version`. * Compilation with a `java_plugin`, where the runtime version is determined by the `java_runtime_version_alias` target specified as the Java toolchain's `java_runtime`. The latter situation led to especially confusing error messages when the `--tool_java_language_level` was higher than the version of the runtime used for Java compilation. --- .../lib/rules/java/JavaConfiguration.java | 57 ++++++++++++ src/test/shell/bazel/bazel_with_jdk_test.sh | 89 +++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java index 2f27997ecc1f25..9cbae38d042d34 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java @@ -18,11 +18,13 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Ascii; import com.google.common.base.Optional; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.analysis.PlatformOptions; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.StrictDepsMode; +import com.google.devtools.build.lib.analysis.config.CoreOptions; import com.google.devtools.build.lib.analysis.config.Fragment; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; import com.google.devtools.build.lib.analysis.config.RequiresOptions; @@ -207,6 +209,9 @@ public JavaConfiguration(BuildOptions buildOptions) throws InvalidConfigurationE + " --tool_java_runtime_version). This may result in incorrect toolchain selection " + "(see https://github.com/bazelbuild/bazel/issues/7849)."); } + + checkLanguageAndRuntimeVersion(javaOptions.javaLanguageVersion, javaOptions.javaRuntimeVersion, + buildOptions.get(CoreOptions.class).isExec); } private static void checkLegacyToolchainFlagIsUnset(String flag, Label label) @@ -218,6 +223,58 @@ private static void checkLegacyToolchainFlagIsUnset(String flag, Label label) } } + private static void checkLanguageAndRuntimeVersion(@Nullable String rawLanguageVersion, + @Nullable String rawRuntimeVersion, boolean isExec) throws InvalidConfigurationException { + int languageVersion; + if (Strings.isNullOrEmpty(rawLanguageVersion)) { + return; + } + try { + languageVersion = Integer.parseUnsignedInt(rawLanguageVersion); + } catch (NumberFormatException e) { + // This is unexpected, but for the sake of compatibility with unusual toolchains, we should + // not fail the build. + return; + } + + int runtimeVersion; + if (Strings.isNullOrEmpty(rawRuntimeVersion)) { + return; + } + int lastUnderscore = rawRuntimeVersion.lastIndexOf('_'); + String rawRuntimeVersionLastPart; + if (lastUnderscore == -1) { + rawRuntimeVersionLastPart = rawRuntimeVersion; + } else { + rawRuntimeVersionLastPart = rawRuntimeVersion.substring(lastUnderscore + 1); + } + try { + runtimeVersion = Integer.parseUnsignedInt(rawRuntimeVersionLastPart); + } catch (NumberFormatException ignored) { + // Could be e.g. the "jdk" part of "local_jdk" without a version number, which we can't + // check at this point. + return; + } + + if (languageVersion > runtimeVersion) { + if (isExec) { + throw new InvalidConfigurationException( + String.format( + "--tool_java_language_version=%s is incompatible with current Java runtime '%s':" + + " Both --tool_java_runtime_version and the 'java_runtime' attribute of the" + + " Java toolchain have to be set to versions at least as high as the language" + + " version.", + rawLanguageVersion, rawRuntimeVersion)); + } else { + throw new InvalidConfigurationException( + String.format( + "--java_language_version=%s is incompatible with --java_runtime_version=%s: The" + + " runtime version has to be at least as high as the language version.", + rawLanguageVersion, rawRuntimeVersion)); + } + } + } + @Override // TODO(bazel-team): this is the command-line passed options, we should remove from Starlark // probably. diff --git a/src/test/shell/bazel/bazel_with_jdk_test.sh b/src/test/shell/bazel/bazel_with_jdk_test.sh index e6629c36e716a8..ed8e443c754c43 100755 --- a/src/test/shell/bazel/bazel_with_jdk_test.sh +++ b/src/test/shell/bazel/bazel_with_jdk_test.sh @@ -289,4 +289,93 @@ function test_bazel_compiles_with_localjdk() { expect_not_log "exec external/remotejdk11_linux/bin/java" } +function test_java_language_greater_than_runtime_version() { + mkdir -p pkg + cat >pkg/BUILD <pkg/Main.java <"${TEST_log}" && fail "Expected build to fail" + + expect_log "--java_language_version=17 is incompatible with --java_runtime_version=remotejdk_11: The runtime version has to be at least as high as the language version." +} + +function test_tool_java_language_greater_than_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=17 \ + --tool_java_runtime_version=remotejdk_11 \ + &>"${TEST_log}" && fail "Expected build to fail" + + expect_log "--tool_java_language_version=17 is incompatible with current Java runtime 'remotejdk_11': Both --tool_java_runtime_version and the 'java_runtime' attribute of the Java toolchain have to be set to versions at least as high as the language version." +} + +function test_tool_java_language_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", + deps = ["@bazel_tools//tools/java/runfiles"], +) +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 \ + --tool_java_language_version=20 \ + --tool_java_runtime_version=remotejdk_20 \ + &>"${TEST_log}" && fail "Expected build to fail" + + expect_log "--tool_java_language_version=20 is incompatible with current Java runtime 'remotejdk_17': Both --tool_java_runtime_version and the 'java_runtime' attribute of the Java toolchain have to be set to versions at least as high as the language version." +} + run_suite "Tests detection of local JDK and that Bazel executes with a bundled JDK."