Add `incompatible_language_version_bootclasspath` flag
Copybara Import from https://github.com/bazelbuild/rules_java/pull/182
BEGIN_PUBLIC
Add `incompatible_language_version_bootclasspath` flag (#182)
With `--@rules_java//java:incompatible_language_version_bootclasspath`, the bootclasspath used for Java compilation is now determined based on the numeric version specified in `--java_language_version` and the type specified in `--java_runtime_version` rather than just using the target runtime.
For example, with `--java_language_version=8` and
`--java_runtime_version=remotejdk_21`, the bootclasspath would be extracted from `remotejdk_8`.
For unversioned runtime versions such as `local_jdk`, the behavior doesn't change.
If a matching runtime is not available, analysis fails with a customized error message explaining the various options to the user.
Work towards https://github.com/bazelbuild/bazel/discussions/21769
Closes #182
END_PUBLIC
COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/rules_java/pull/182 from fmeum:java-language-version de275069b6c98919264f2bfce791dde56eaefcde
PiperOrigin-RevId: 703396804
Change-Id: Ieb1def97a5eee59763336eb61deaf584f341a1d4
diff --git a/test/analysis/bootclasspath_tests.bzl b/test/analysis/bootclasspath_tests.bzl
index c862d83..5eb436a 100644
--- a/test/analysis/bootclasspath_tests.bzl
+++ b/test/analysis/bootclasspath_tests.bzl
@@ -2,6 +2,7 @@
load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
load("@rules_testing//lib:truth.bzl", "subjects")
+load("//java/common:java_common.bzl", "java_common")
def _test_utf_8_environment(name):
analysis_test(
@@ -16,10 +17,61 @@
env_subject.keys().contains("LC_CTYPE")
env_subject.get("LC_CTYPE", factory = subjects.str).contains("UTF-8")
+def _test_incompatible_language_version_bootclasspath_disabled(name):
+ analysis_test(
+ name = name,
+ impl = _test_incompatible_language_version_bootclasspath_disabled_impl,
+ target = Label("//toolchains:platformclasspath"),
+ config_settings = {
+ "//command_line_option:java_language_version": "11",
+ "//command_line_option:java_runtime_version": "remotejdk_17",
+ str(Label("//toolchains:incompatible_language_version_bootclasspath")): False,
+ },
+ )
+
+def _test_incompatible_language_version_bootclasspath_disabled_impl(env, target):
+ system_path = target[java_common.BootClassPathInfo]._system_path
+ env.expect.that_str(system_path).contains("remotejdk17_")
+
+def _test_incompatible_language_version_bootclasspath_enabled_versioned(name):
+ analysis_test(
+ name = name,
+ impl = _test_incompatible_language_version_bootclasspath_enabled_versioned_impl,
+ target = Label("//toolchains:platformclasspath"),
+ config_settings = {
+ "//command_line_option:java_language_version": "11",
+ "//command_line_option:java_runtime_version": "remotejdk_17",
+ str(Label("//toolchains:incompatible_language_version_bootclasspath")): True,
+ },
+ )
+
+def _test_incompatible_language_version_bootclasspath_enabled_versioned_impl(env, target):
+ system_path = target[java_common.BootClassPathInfo]._system_path
+ env.expect.that_str(system_path).contains("remotejdk11_")
+
+def _test_incompatible_language_version_bootclasspath_enabled_unversioned(name):
+ analysis_test(
+ name = name,
+ impl = _test_incompatible_language_version_bootclasspath_enabled_unversioned_impl,
+ target = Label("//toolchains:platformclasspath"),
+ config_settings = {
+ "//command_line_option:java_language_version": "11",
+ "//command_line_option:java_runtime_version": "local_jdk",
+ str(Label("//toolchains:incompatible_language_version_bootclasspath")): True,
+ },
+ )
+
+def _test_incompatible_language_version_bootclasspath_enabled_unversioned_impl(env, target):
+ system_path = target[java_common.BootClassPathInfo]._system_path
+ env.expect.that_str(system_path).contains("local_jdk")
+
def bootclasspath_tests(name):
test_suite(
name = name,
tests = [
_test_utf_8_environment,
+ _test_incompatible_language_version_bootclasspath_disabled,
+ _test_incompatible_language_version_bootclasspath_enabled_versioned,
+ _test_incompatible_language_version_bootclasspath_enabled_unversioned,
],
)
diff --git a/toolchains/BUILD b/toolchains/BUILD
index e7f4d84..99c4456 100644
--- a/toolchains/BUILD
+++ b/toolchains/BUILD
@@ -1,10 +1,15 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_setting")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load(
+ ":bootclasspath.bzl",
+ "bootclasspath",
+ "language_version_bootstrap_runtime",
+)
+load(
":default_java_toolchain.bzl",
"DEFAULT_TOOLCHAIN_CONFIGURATION",
"PREBUILT_TOOLCHAIN_CONFIGURATION",
- "bootclasspath",
"default_java_toolchain",
"java_runtime_files",
)
@@ -31,6 +36,15 @@
srcs = glob(["*.bzl"]),
)
+# If enabled, the bootclasspath for Java compilation will be extracted from a Java runtime matching
+# the version specified with `--java_language_version` rather than the runtime specified with
+# `--java_runtime_version`.
+bool_flag(
+ name = "incompatible_language_version_bootclasspath",
+ build_setting_default = False,
+ visibility = ["//visibility:private"],
+)
+
# A single binary distribution of a JDK (e.g., OpenJDK 17 for Windows arm64) provides three
# different types of toolchains from the perspective of Bazel:
@@ -283,15 +297,46 @@
}),
)
+string_setting(
+ name = "java_language_version",
+ build_setting_default = "",
+ visibility = ["//visibility:private"],
+)
+
+string_setting(
+ name = "java_runtime_version",
+ build_setting_default = "",
+ visibility = ["//visibility:private"],
+)
+
+language_version_bootstrap_runtime(
+ name = "language_version_bootstrap_runtime",
+ java_language_version = ":java_language_version",
+ java_runtime_version = ":java_runtime_version",
+ visibility = ["//visibility:private"],
+)
+
utf8_environment(
name = "utf8_environment",
visibility = ["//visibility:private"],
)
+config_setting(
+ name = "incompatible_language_version_bootclasspath_enabled",
+ flag_values = {
+ ":incompatible_language_version_bootclasspath": "True",
+ },
+ visibility = ["//visibility:private"],
+)
+
bootclasspath(
name = "platformclasspath",
src = "DumpPlatformClassPath.java",
java_runtime_alias = ":current_java_runtime",
+ language_version_bootstrap_runtime = select({
+ ":incompatible_language_version_bootclasspath_enabled": ":language_version_bootstrap_runtime",
+ "//conditions:default": None,
+ }),
)
default_java_toolchain(
diff --git a/toolchains/bootclasspath.bzl b/toolchains/bootclasspath.bzl
new file mode 100644
index 0000000..b6f57e0
--- /dev/null
+++ b/toolchains/bootclasspath.bzl
@@ -0,0 +1,246 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Rules for extracting a platform classpath from Java runtimes."""
+
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load("//java/common:java_common.bzl", "java_common")
+load(":utf8_environment.bzl", "Utf8EnvironmentInfo")
+
+visibility("private")
+
+# TODO: This provider and is only necessary since --java_{language,runtime}_version
+# are not available directly to Starlark.
+_JavaVersionsInfo = provider(
+ "Exposes the --java_{language,runtime}_version value as extracted from a transition to a dependant.",
+ fields = {
+ "java_language_version": "The value of --java_language_version",
+ "java_runtime_version": "The value of --java_runtime_version",
+ },
+)
+
+def _language_version_bootstrap_runtime(ctx):
+ providers = [
+ _JavaVersionsInfo(
+ java_language_version = ctx.attr.java_language_version[BuildSettingInfo].value,
+ java_runtime_version = ctx.attr.java_runtime_version[BuildSettingInfo].value,
+ ),
+ ]
+
+ bootstrap_runtime = ctx.toolchains["@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type"]
+ if bootstrap_runtime:
+ providers.append(bootstrap_runtime.java_runtime)
+
+ return providers
+
+language_version_bootstrap_runtime = rule(
+ implementation = _language_version_bootstrap_runtime,
+ attrs = {
+ "java_language_version": attr.label(
+ providers = [BuildSettingInfo],
+ ),
+ "java_runtime_version": attr.label(
+ providers = [BuildSettingInfo],
+ ),
+ },
+ toolchains = [
+ config_common.toolchain_type("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type", mandatory = False),
+ ],
+)
+
+def _get_bootstrap_runtime_version(*, java_language_version, java_runtime_version):
+ """Returns the runtime version to use for bootstrapping the given language version.
+
+ If the runtime version is not versioned, e.g. "local_jdk", it is used as is.
+ Otherwise, the language version replaces the numeric part of the runtime version, e.g.,
+ "remotejdk_17" becomes "remotejdk_8".
+ """
+ prefix, separator, version = java_runtime_version.rpartition("_")
+ if version and version.isdigit():
+ new_version = java_language_version
+ else:
+ # The runtime version is not versioned, e.g. "local_jdk". Use it as is.
+ new_version = version
+
+ return prefix + separator + new_version
+
+def _bootclasspath_transition_impl(settings, _):
+ java_language_version = settings["//command_line_option:java_language_version"]
+ java_runtime_version = settings["//command_line_option:java_runtime_version"]
+
+ return {
+ "//command_line_option:java_runtime_version": _get_bootstrap_runtime_version(
+ java_language_version = java_language_version,
+ java_runtime_version = java_runtime_version,
+ ),
+ "//toolchains:java_language_version": java_language_version,
+ "//toolchains:java_runtime_version": java_runtime_version,
+ }
+
+_bootclasspath_transition = transition(
+ implementation = _bootclasspath_transition_impl,
+ inputs = [
+ "//command_line_option:java_language_version",
+ "//command_line_option:java_runtime_version",
+ ],
+ outputs = [
+ "//command_line_option:java_runtime_version",
+ "//toolchains:java_language_version",
+ "//toolchains:java_runtime_version",
+ ],
+)
+
+_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type")
+
+# Opt the Java bootstrap actions into path mapping:
+# https://github.com/bazelbuild/bazel/commit/a239ea84832f18ee8706682145e9595e71b39680
+_SUPPORTS_PATH_MAPPING = {"supports-path-mapping": "1"}
+
+def _java_home(java_executable):
+ return java_executable.dirname[:-len("/bin")]
+
+def _bootclasspath_impl(ctx):
+ exec_javabase = ctx.attr.java_runtime_alias[java_common.JavaRuntimeInfo]
+ env = ctx.attr._utf8_environment[Utf8EnvironmentInfo].environment
+
+ class_dir = ctx.actions.declare_directory("%s_classes" % ctx.label.name)
+
+ args = ctx.actions.args()
+ args.add("-source")
+ args.add("8")
+ args.add("-target")
+ args.add("8")
+ args.add("-Xlint:-options")
+ args.add("-J-XX:-UsePerfData")
+ args.add("-d")
+ args.add_all([class_dir], expand_directories = False)
+ args.add(ctx.file.src)
+
+ ctx.actions.run(
+ executable = "%s/bin/javac" % exec_javabase.java_home,
+ mnemonic = "JavaToolchainCompileClasses",
+ inputs = [ctx.file.src] + ctx.files.java_runtime_alias,
+ outputs = [class_dir],
+ arguments = [args],
+ env = env,
+ execution_requirements = _SUPPORTS_PATH_MAPPING,
+ )
+
+ bootclasspath = ctx.outputs.output_jar
+
+ args = ctx.actions.args()
+ args.add("-XX:+IgnoreUnrecognizedVMOptions")
+ args.add("-XX:-UsePerfData")
+ args.add("--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED")
+ args.add("--add-exports=jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED")
+ args.add("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")
+ args.add_all("-cp", [class_dir], expand_directories = False)
+ args.add("DumpPlatformClassPath")
+ args.add(bootclasspath)
+
+ if ctx.attr.language_version_bootstrap_runtime:
+ # The attribute is subject to a split transition.
+ language_version_bootstrap_runtime = ctx.attr.language_version_bootstrap_runtime[0]
+ if java_common.JavaRuntimeInfo in language_version_bootstrap_runtime:
+ any_javabase = language_version_bootstrap_runtime[java_common.JavaRuntimeInfo]
+ else:
+ java_versions_info = language_version_bootstrap_runtime[_JavaVersionsInfo]
+ bootstrap_runtime_version = _get_bootstrap_runtime_version(
+ java_language_version = java_versions_info.java_language_version,
+ java_runtime_version = java_versions_info.java_runtime_version,
+ )
+ is_exec = "-exec" in ctx.bin_dir.path
+ tool_prefix = "tool_" if is_exec else ""
+ fail("""
+No Java runtime found to extract the bootclasspath from for --{tool_prefix}java_language_version={language_version} and --{tool_prefix}java_runtime_version={runtime_version}.
+You can:
+
+ * register a Java runtime with name "{bootstrap_runtime_version}" to provide the bootclasspath or
+ * set --java_language_version to the Java version of an available runtime.
+
+Rerun with --toolchain_resolution_debug='@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type' to see more details about toolchain resolution.
+""".format(
+ language_version = java_versions_info.java_language_version,
+ runtime_version = java_versions_info.java_runtime_version,
+ bootstrap_runtime_version = bootstrap_runtime_version,
+ tool_prefix = tool_prefix,
+ ))
+ else:
+ any_javabase = ctx.toolchains[_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE].java_runtime
+ any_javabase_files = any_javabase.files.to_list()
+
+ # If possible, add the Java executable to the command line as a File so that it can be path
+ # mapped.
+ java_executable = [f for f in any_javabase_files if f.path == any_javabase.java_executable_exec_path]
+ if len(java_executable) == 1:
+ args.add_all(java_executable, map_each = _java_home)
+ else:
+ args.add(any_javabase.java_home)
+
+ system_files = ("release", "modules", "jrt-fs.jar")
+ system = [f for f in any_javabase_files if f.basename in system_files]
+ if len(system) != len(system_files):
+ system = None
+
+ inputs = depset([class_dir] + ctx.files.java_runtime_alias, transitive = [any_javabase.files])
+ ctx.actions.run(
+ executable = str(exec_javabase.java_executable_exec_path),
+ mnemonic = "JavaToolchainCompileBootClasspath",
+ inputs = inputs,
+ outputs = [bootclasspath],
+ arguments = [args],
+ env = env,
+ execution_requirements = _SUPPORTS_PATH_MAPPING,
+ )
+ return [
+ DefaultInfo(files = depset([bootclasspath])),
+ java_common.BootClassPathInfo(
+ bootclasspath = [bootclasspath],
+ system = system,
+ ),
+ OutputGroupInfo(jar = [bootclasspath]),
+ ]
+
+_bootclasspath = rule(
+ implementation = _bootclasspath_impl,
+ attrs = {
+ "java_runtime_alias": attr.label(
+ cfg = "exec",
+ providers = [java_common.JavaRuntimeInfo],
+ ),
+ "language_version_bootstrap_runtime": attr.label(
+ cfg = _bootclasspath_transition,
+ ),
+ "output_jar": attr.output(mandatory = True),
+ "src": attr.label(
+ cfg = "exec",
+ allow_single_file = True,
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ "_utf8_environment": attr.label(
+ default = ":utf8_environment",
+ cfg = "exec",
+ ),
+ },
+ toolchains = [_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE],
+)
+
+def bootclasspath(name, **kwargs):
+ _bootclasspath(
+ name = name,
+ output_jar = name + ".jar",
+ **kwargs
+ )
diff --git a/toolchains/default_java_toolchain.bzl b/toolchains/default_java_toolchain.bzl
index 5aaa06b..4ec8961 100644
--- a/toolchains/default_java_toolchain.bzl
+++ b/toolchains/default_java_toolchain.bzl
@@ -14,9 +14,8 @@
"""Rules for defining default_java_toolchain"""
-load("//java/common:java_common.bzl", "java_common")
load("//java/toolchains:java_toolchain.bzl", "java_toolchain")
-load(":utf8_environment.bzl", "Utf8EnvironmentInfo")
+load(":bootclasspath.bzl", _bootclasspath = "bootclasspath")
# JVM options, without patching java.compiler and jdk.compiler modules.
BASE_JDK9_JVM_OPTS = [
@@ -213,112 +212,4 @@
tags = ["manual"],
)
-_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type")
-
-# Opt the Java bootstrap actions into path mapping:
-# https://github.com/bazelbuild/bazel/commit/a239ea84832f18ee8706682145e9595e71b39680
-_SUPPORTS_PATH_MAPPING = {"supports-path-mapping": "1"}
-
-def _java_home(java_executable):
- return java_executable.dirname[:-len("/bin")]
-
-def _bootclasspath_impl(ctx):
- exec_javabase = ctx.attr.java_runtime_alias[java_common.JavaRuntimeInfo]
- env = ctx.attr._utf8_environment[Utf8EnvironmentInfo].environment
-
- class_dir = ctx.actions.declare_directory("%s_classes" % ctx.label.name)
-
- args = ctx.actions.args()
- args.add("-source")
- args.add("8")
- args.add("-target")
- args.add("8")
- args.add("-Xlint:-options")
- args.add("-J-XX:-UsePerfData")
- args.add("-d")
- args.add_all([class_dir], expand_directories = False)
- args.add(ctx.file.src)
-
- ctx.actions.run(
- executable = "%s/bin/javac" % exec_javabase.java_home,
- mnemonic = "JavaToolchainCompileClasses",
- inputs = [ctx.file.src] + ctx.files.java_runtime_alias,
- outputs = [class_dir],
- arguments = [args],
- env = env,
- execution_requirements = _SUPPORTS_PATH_MAPPING,
- )
-
- bootclasspath = ctx.outputs.output_jar
-
- args = ctx.actions.args()
- args.add("-XX:+IgnoreUnrecognizedVMOptions")
- args.add("-XX:-UsePerfData")
- args.add("--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED")
- args.add("--add-exports=jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED")
- args.add("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")
- args.add_all("-cp", [class_dir], expand_directories = False)
- args.add("DumpPlatformClassPath")
- args.add(bootclasspath)
-
- any_javabase = ctx.toolchains[_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE].java_runtime
- any_javabase_files = any_javabase.files.to_list()
-
- # If possible, add the Java executable to the command line as a File so that it can be path
- # mapped.
- java_executable = [f for f in any_javabase_files if f.path == any_javabase.java_executable_exec_path]
- if len(java_executable) == 1:
- args.add_all(java_executable, map_each = _java_home)
- else:
- args.add(any_javabase.java_home)
-
- system_files = ("release", "modules", "jrt-fs.jar")
- system = [f for f in any_javabase_files if f.basename in system_files]
- if len(system) != len(system_files):
- system = None
-
- inputs = depset([class_dir] + ctx.files.java_runtime_alias, transitive = [any_javabase.files])
- ctx.actions.run(
- executable = str(exec_javabase.java_executable_exec_path),
- mnemonic = "JavaToolchainCompileBootClasspath",
- inputs = inputs,
- outputs = [bootclasspath],
- arguments = [args],
- env = env,
- execution_requirements = _SUPPORTS_PATH_MAPPING,
- )
- return [
- DefaultInfo(files = depset([bootclasspath])),
- java_common.BootClassPathInfo(
- bootclasspath = [bootclasspath],
- system = system,
- ),
- OutputGroupInfo(jar = [bootclasspath]),
- ]
-
-_bootclasspath = rule(
- implementation = _bootclasspath_impl,
- attrs = {
- "java_runtime_alias": attr.label(
- cfg = "exec",
- providers = [java_common.JavaRuntimeInfo],
- ),
- "output_jar": attr.output(mandatory = True),
- "src": attr.label(
- cfg = "exec",
- allow_single_file = True,
- ),
- "_utf8_environment": attr.label(
- default = ":utf8_environment",
- cfg = "exec",
- ),
- },
- toolchains = [_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE],
-)
-
-def bootclasspath(name, **kwargs):
- _bootclasspath(
- name = name,
- output_jar = name + ".jar",
- **kwargs
- )
+bootclasspath = _bootclasspath