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