# 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 )