xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/py_exec_tools_toolchain.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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"""Rule that defines a toolchain for build tools."""
16
17load("@bazel_skylib//lib:paths.bzl", "paths")
18load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
19load("//python/private:sentinel.bzl", "SentinelInfo")
20load("//python/private:toolchain_types.bzl", "TARGET_TOOLCHAIN_TYPE")
21load(":py_exec_tools_info.bzl", "PyExecToolsInfo")
22
23def _py_exec_tools_toolchain_impl(ctx):
24    extra_kwargs = {}
25    if ctx.attr._visible_for_testing[BuildSettingInfo].value:
26        extra_kwargs["toolchain_label"] = ctx.label
27
28    exec_interpreter = ctx.attr.exec_interpreter
29    if SentinelInfo in ctx.attr.exec_interpreter:
30        exec_interpreter = None
31
32    return [platform_common.ToolchainInfo(
33        exec_tools = PyExecToolsInfo(
34            exec_interpreter = exec_interpreter,
35            precompiler = ctx.attr.precompiler,
36        ),
37        **extra_kwargs
38    )]
39
40py_exec_tools_toolchain = rule(
41    implementation = _py_exec_tools_toolchain_impl,
42    doc = """
43Provides a toolchain for build time tools.
44
45This provides `ToolchainInfo` with the following attributes:
46* `exec_tools`: {type}`PyExecToolsInfo`
47* `toolchain_label`: {type}`Label` _only present when `--visibile_for_testing=True`
48  for internal testing_. The rule's label; this allows identifying what toolchain
49  implmentation was selected for testing purposes.
50""",
51    attrs = {
52        "exec_interpreter": attr.label(
53            default = "//python/private:current_interpreter_executable",
54            cfg = "exec",
55            doc = """
56An interpreter that is directly usable in the exec configuration
57
58If not specified, the interpreter from {obj}`//python:toolchain_type` will
59be used.
60
61To disable, specify the special target {obj}`//python:none`; the raw value `None`
62will use the default.
63
64:::{note}
65This is only useful for `ctx.actions.run` calls that _directly_ invoke the
66interpreter, which is fairly uncommon and low level. It is better to use a
67`cfg="exec"` attribute that points to a `py_binary` rule instead, which will
68handle all the necessary transitions and runtime setup to invoke a program.
69:::
70
71See {obj}`PyExecToolsInfo.exec_interpreter` for further docs.
72""",
73        ),
74        "precompiler": attr.label(
75            allow_files = True,
76            cfg = "exec",
77            doc = "See {obj}`PyExecToolsInfo.precompiler`",
78        ),
79        "_visible_for_testing": attr.label(
80            default = "//python/private:visible_for_testing",
81        ),
82    },
83)
84
85def _current_interpreter_executable_impl(ctx):
86    toolchain = ctx.toolchains[TARGET_TOOLCHAIN_TYPE]
87    runtime = toolchain.py3_runtime
88
89    # NOTE: We name the output filename after the underlying file name
90    # because of things like pyenv: they use $0 to determine what to
91    # re-exec. If it's not a recognized name, then they fail.
92    if runtime.interpreter:
93        executable = ctx.actions.declare_file(runtime.interpreter.basename)
94        ctx.actions.symlink(output = executable, target_file = runtime.interpreter, is_executable = True)
95    else:
96        executable = ctx.actions.declare_symlink(paths.basename(runtime.interpreter_path))
97        ctx.actions.symlink(output = executable, target_path = runtime.interpreter_path)
98    return [
99        toolchain,
100        DefaultInfo(
101            executable = executable,
102            runfiles = ctx.runfiles([executable], transitive_files = runtime.files),
103        ),
104    ]
105
106current_interpreter_executable = rule(
107    implementation = _current_interpreter_executable_impl,
108    toolchains = [TARGET_TOOLCHAIN_TYPE],
109    executable = True,
110)
111