• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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