1# Copyright 2024 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Rules for extracting a platform classpath from Java runtimes.""" 16 17load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 18load("//java/common:java_common.bzl", "java_common") 19load(":utf8_environment.bzl", "Utf8EnvironmentInfo") 20 21visibility("private") 22 23# TODO: This provider and is only necessary since --java_{language,runtime}_version 24# are not available directly to Starlark. 25_JavaVersionsInfo = provider( 26 "Exposes the --java_{language,runtime}_version value as extracted from a transition to a dependant.", 27 fields = { 28 "java_language_version": "The value of --java_language_version", 29 "java_runtime_version": "The value of --java_runtime_version", 30 }, 31) 32 33def _language_version_bootstrap_runtime(ctx): 34 providers = [ 35 _JavaVersionsInfo( 36 java_language_version = ctx.attr.java_language_version[BuildSettingInfo].value, 37 java_runtime_version = ctx.attr.java_runtime_version[BuildSettingInfo].value, 38 ), 39 ] 40 41 bootstrap_runtime = ctx.toolchains["@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type"] 42 if bootstrap_runtime: 43 providers.append(bootstrap_runtime.java_runtime) 44 45 return providers 46 47language_version_bootstrap_runtime = rule( 48 implementation = _language_version_bootstrap_runtime, 49 attrs = { 50 "java_language_version": attr.label( 51 providers = [BuildSettingInfo], 52 ), 53 "java_runtime_version": attr.label( 54 providers = [BuildSettingInfo], 55 ), 56 }, 57 toolchains = [ 58 config_common.toolchain_type("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type", mandatory = False), 59 ], 60) 61 62def _get_bootstrap_runtime_version(*, java_language_version, java_runtime_version): 63 """Returns the runtime version to use for bootstrapping the given language version. 64 65 If the runtime version is not versioned, e.g. "local_jdk", it is used as is. 66 Otherwise, the language version replaces the numeric part of the runtime version, e.g., 67 "remotejdk_17" becomes "remotejdk_8". 68 """ 69 prefix, separator, version = java_runtime_version.rpartition("_") 70 if version and version.isdigit(): 71 new_version = java_language_version 72 else: 73 # The runtime version is not versioned, e.g. "local_jdk". Use it as is. 74 new_version = version 75 76 return prefix + separator + new_version 77 78def _bootclasspath_transition_impl(settings, _): 79 java_language_version = settings["//command_line_option:java_language_version"] 80 java_runtime_version = settings["//command_line_option:java_runtime_version"] 81 82 return { 83 "//command_line_option:java_runtime_version": _get_bootstrap_runtime_version( 84 java_language_version = java_language_version, 85 java_runtime_version = java_runtime_version, 86 ), 87 "//toolchains:java_language_version": java_language_version, 88 "//toolchains:java_runtime_version": java_runtime_version, 89 } 90 91_bootclasspath_transition = transition( 92 implementation = _bootclasspath_transition_impl, 93 inputs = [ 94 "//command_line_option:java_language_version", 95 "//command_line_option:java_runtime_version", 96 ], 97 outputs = [ 98 "//command_line_option:java_runtime_version", 99 "//toolchains:java_language_version", 100 "//toolchains:java_runtime_version", 101 ], 102) 103 104_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type") 105 106# Opt the Java bootstrap actions into path mapping: 107# https://github.com/bazelbuild/bazel/commit/a239ea84832f18ee8706682145e9595e71b39680 108_SUPPORTS_PATH_MAPPING = {"supports-path-mapping": "1"} 109 110def _java_home(java_executable): 111 return java_executable.dirname[:-len("/bin")] 112 113def _bootclasspath_impl(ctx): 114 exec_javabase = ctx.attr.java_runtime_alias[java_common.JavaRuntimeInfo] 115 env = ctx.attr._utf8_environment[Utf8EnvironmentInfo].environment 116 117 class_dir = ctx.actions.declare_directory("%s_classes" % ctx.label.name) 118 119 args = ctx.actions.args() 120 args.add("-source") 121 args.add("8") 122 args.add("-target") 123 args.add("8") 124 args.add("-Xlint:-options") 125 args.add("-J-XX:-UsePerfData") 126 args.add("-d") 127 args.add_all([class_dir], expand_directories = False) 128 args.add(ctx.file.src) 129 130 ctx.actions.run( 131 executable = "%s/bin/javac" % exec_javabase.java_home, 132 mnemonic = "JavaToolchainCompileClasses", 133 inputs = [ctx.file.src] + ctx.files.java_runtime_alias, 134 outputs = [class_dir], 135 arguments = [args], 136 env = env, 137 execution_requirements = _SUPPORTS_PATH_MAPPING, 138 ) 139 140 bootclasspath = ctx.outputs.output_jar 141 142 args = ctx.actions.args() 143 args.add("-XX:+IgnoreUnrecognizedVMOptions") 144 args.add("-XX:-UsePerfData") 145 args.add("--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED") 146 args.add("--add-exports=jdk.compiler/com.sun.tools.javac.platform=ALL-UNNAMED") 147 args.add("--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED") 148 args.add_all("-cp", [class_dir], expand_directories = False) 149 args.add("DumpPlatformClassPath") 150 args.add(bootclasspath) 151 152 if ctx.attr.language_version_bootstrap_runtime: 153 # The attribute is subject to a split transition. 154 language_version_bootstrap_runtime = ctx.attr.language_version_bootstrap_runtime[0] 155 if java_common.JavaRuntimeInfo in language_version_bootstrap_runtime: 156 any_javabase = language_version_bootstrap_runtime[java_common.JavaRuntimeInfo] 157 else: 158 java_versions_info = language_version_bootstrap_runtime[_JavaVersionsInfo] 159 bootstrap_runtime_version = _get_bootstrap_runtime_version( 160 java_language_version = java_versions_info.java_language_version, 161 java_runtime_version = java_versions_info.java_runtime_version, 162 ) 163 is_exec = "-exec" in ctx.bin_dir.path 164 tool_prefix = "tool_" if is_exec else "" 165 fail(""" 166No Java runtime found to extract the bootclasspath from for --{tool_prefix}java_language_version={language_version} and --{tool_prefix}java_runtime_version={runtime_version}. 167You can: 168 169 * register a Java runtime with name "{bootstrap_runtime_version}" to provide the bootclasspath or 170 * set --java_language_version to the Java version of an available runtime. 171 172Rerun with --toolchain_resolution_debug='@bazel_tools//tools/jdk:bootstrap_runtime_toolchain_type' to see more details about toolchain resolution. 173""".format( 174 language_version = java_versions_info.java_language_version, 175 runtime_version = java_versions_info.java_runtime_version, 176 bootstrap_runtime_version = bootstrap_runtime_version, 177 tool_prefix = tool_prefix, 178 )) 179 else: 180 any_javabase = ctx.toolchains[_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE].java_runtime 181 any_javabase_files = any_javabase.files.to_list() 182 183 # If possible, add the Java executable to the command line as a File so that it can be path 184 # mapped. 185 java_executable = [f for f in any_javabase_files if f.path == any_javabase.java_executable_exec_path] 186 if len(java_executable) == 1: 187 args.add_all(java_executable, map_each = _java_home) 188 else: 189 args.add(any_javabase.java_home) 190 191 system_files = ("release", "modules", "jrt-fs.jar") 192 system = [f for f in any_javabase_files if f.basename in system_files] 193 if len(system) != len(system_files): 194 system = None 195 196 inputs = depset([class_dir] + ctx.files.java_runtime_alias, transitive = [any_javabase.files]) 197 ctx.actions.run( 198 executable = str(exec_javabase.java_executable_exec_path), 199 mnemonic = "JavaToolchainCompileBootClasspath", 200 inputs = inputs, 201 outputs = [bootclasspath], 202 arguments = [args], 203 env = env, 204 execution_requirements = _SUPPORTS_PATH_MAPPING, 205 ) 206 return [ 207 DefaultInfo(files = depset([bootclasspath])), 208 java_common.BootClassPathInfo( 209 bootclasspath = [bootclasspath], 210 system = system, 211 ), 212 OutputGroupInfo(jar = [bootclasspath]), 213 ] 214 215_bootclasspath = rule( 216 implementation = _bootclasspath_impl, 217 attrs = { 218 "java_runtime_alias": attr.label( 219 cfg = "exec", 220 providers = [java_common.JavaRuntimeInfo], 221 ), 222 "language_version_bootstrap_runtime": attr.label( 223 cfg = _bootclasspath_transition, 224 ), 225 "output_jar": attr.output(mandatory = True), 226 "src": attr.label( 227 cfg = "exec", 228 allow_single_file = True, 229 ), 230 "_allowlist_function_transition": attr.label( 231 default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 232 ), 233 "_utf8_environment": attr.label( 234 default = ":utf8_environment", 235 cfg = "exec", 236 ), 237 }, 238 toolchains = [_JAVA_BOOTSTRAP_RUNTIME_TOOLCHAIN_TYPE], 239) 240 241def bootclasspath(name, **kwargs): 242 _bootclasspath( 243 name = name, 244 output_jar = name + ".jar", 245 **kwargs 246 ) 247