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"""Setup a python-build-standalone based toolchain.""" 15 16load("@rules_cc//cc:cc_import.bzl", "cc_import") 17load("@rules_cc//cc:cc_library.bzl", "cc_library") 18load("//python:py_runtime.bzl", "py_runtime") 19load("//python:py_runtime_pair.bzl", "py_runtime_pair") 20load("//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain") 21load(":glob_excludes.bzl", "glob_excludes") 22load(":py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain") 23load(":semver.bzl", "semver") 24 25_IS_FREETHREADED = Label("//python/config_settings:is_py_freethreaded") 26 27def define_hermetic_runtime_toolchain_impl( 28 *, 29 name, 30 extra_files_glob_include, 31 extra_files_glob_exclude, 32 python_version, 33 python_bin, 34 coverage_tool): 35 """Define a toolchain implementation for a python-build-standalone repo. 36 37 It expected this macro is called in the top-level package of an extracted 38 python-build-standalone repository. See 39 python/private/python_repositories.bzl for how it is invoked. 40 41 Args: 42 name: {type}`str` name used for tools to identify the invocation. 43 extra_files_glob_include: {type}`list[str]` additional glob include 44 patterns for the target runtime files (the one included in 45 binaries). 46 extra_files_glob_exclude: {type}`list[str]` additional glob exclude 47 patterns for the target runtime files. 48 python_version: {type}`str` The Python version, in `major.minor.micro` 49 format. 50 python_bin: {type}`str` The path to the Python binary within the 51 repository. 52 coverage_tool: {type}`str` optional target to the coverage tool to 53 use. 54 """ 55 _ = name # @unused 56 version_info = semver(python_version) 57 version_dict = version_info.to_dict() 58 native.filegroup( 59 name = "files", 60 srcs = native.glob( 61 include = [ 62 "bin/**", 63 "extensions/**", 64 "include/**", 65 "libs/**", 66 "share/**", 67 ] + extra_files_glob_include, 68 # Platform-agnostic filegroup can't match on all patterns. 69 allow_empty = True, 70 exclude = [ 71 # Unused shared libraries. `python` executable and the `:libpython` target 72 # depend on `libpython{python_version}.so.1.0`. 73 "lib/libpython{major}.{minor}*.so".format(**version_dict), 74 # static libraries 75 "lib/**/*.a", 76 # tests for the standard libraries. 77 "lib/python{major}.{minor}*/**/test/**".format(**version_dict), 78 "lib/python{major}.{minor}*/**/tests/**".format(**version_dict), 79 # During pyc creation, temp files named *.pyc.NNN are created 80 "**/__pycache__/*.pyc.*", 81 ] + glob_excludes.version_dependent_exclusions() + extra_files_glob_exclude, 82 ), 83 ) 84 cc_import( 85 name = "interface", 86 interface_library = select({ 87 _IS_FREETHREADED: "libs/python{major}{minor}t.lib".format(**version_dict), 88 "//conditions:default": "libs/python{major}{minor}.lib".format(**version_dict), 89 }), 90 system_provided = True, 91 ) 92 cc_import( 93 name = "abi3_interface", 94 interface_library = select({ 95 _IS_FREETHREADED: "libs/python3t.lib", 96 "//conditions:default": "libs/python3.lib", 97 }), 98 system_provided = True, 99 ) 100 101 native.filegroup( 102 name = "includes", 103 srcs = native.glob(["include/**/*.h"]), 104 ) 105 cc_library( 106 name = "python_headers", 107 deps = select({ 108 "@bazel_tools//src/conditions:windows": [":interface", ":abi3_interface"], 109 "//conditions:default": None, 110 }), 111 hdrs = [":includes"], 112 includes = [ 113 "include", 114 ] + select({ 115 _IS_FREETHREADED: [ 116 "include/python{major}.{minor}t".format(**version_dict), 117 ], 118 "//conditions:default": [ 119 "include/python{major}.{minor}".format(**version_dict), 120 "include/python{major}.{minor}m".format(**version_dict), 121 ], 122 }), 123 ) 124 native.config_setting( 125 name = "is_freethreaded_linux", 126 flag_values = { 127 Label("//python/config_settings:py_freethreaded"): "yes", 128 }, 129 constraint_values = [ 130 "@platforms//os:linux", 131 ], 132 visibility = ["//visibility:private"], 133 ) 134 native.config_setting( 135 name = "is_freethreaded_osx", 136 flag_values = { 137 Label("//python/config_settings:py_freethreaded"): "yes", 138 }, 139 constraint_values = [ 140 "@platforms//os:osx", 141 ], 142 visibility = ["//visibility:private"], 143 ) 144 native.config_setting( 145 name = "is_freethreaded_windows", 146 flag_values = { 147 Label("//python/config_settings:py_freethreaded"): "yes", 148 }, 149 constraint_values = [ 150 "@platforms//os:windows", 151 ], 152 visibility = ["//visibility:private"], 153 ) 154 155 cc_library( 156 name = "libpython", 157 hdrs = [":includes"], 158 srcs = select({ 159 ":is_freethreaded_linux": [ 160 "lib/libpython{major}.{minor}t.so".format(**version_dict), 161 "lib/libpython{major}.{minor}t.so.1.0".format(**version_dict), 162 ], 163 ":is_freethreaded_osx": [ 164 "lib/libpython{major}.{minor}t.dylib".format(**version_dict), 165 ], 166 ":is_freethreaded_windows": [ 167 "python3t.dll", 168 "python{major}{minor}t.dll".format(**version_dict), 169 "libs/python{major}{minor}t.lib".format(**version_dict), 170 "libs/python3t.lib", 171 ], 172 "@platforms//os:linux": [ 173 "lib/libpython{major}.{minor}.so".format(**version_dict), 174 "lib/libpython{major}.{minor}.so.1.0".format(**version_dict), 175 ], 176 "@platforms//os:macos": ["lib/libpython{major}.{minor}.dylib".format(**version_dict)], 177 "@platforms//os:windows": [ 178 "python3.dll", 179 "python{major}{minor}.dll".format(**version_dict), 180 "libs/python{major}{minor}.lib".format(**version_dict), 181 "libs/python3.lib", 182 ], 183 }), 184 ) 185 186 native.exports_files(["python", python_bin]) 187 188 # Used to only download coverage toolchain when the coverage is collected by 189 # bazel. 190 native.config_setting( 191 name = "coverage_enabled", 192 values = {"collect_code_coverage": "true"}, 193 visibility = ["//visibility:private"], 194 ) 195 196 py_runtime( 197 name = "py3_runtime", 198 files = [":files"], 199 interpreter = python_bin, 200 interpreter_version_info = { 201 "major": str(version_info.major), 202 "micro": str(version_info.patch), 203 "minor": str(version_info.minor), 204 }, 205 coverage_tool = select({ 206 # Convert empty string to None 207 ":coverage_enabled": coverage_tool or None, 208 "//conditions:default": None, 209 }), 210 python_version = "PY3", 211 implementation_name = "cpython", 212 # See https://peps.python.org/pep-3147/ for pyc tag infix format 213 pyc_tag = select({ 214 _IS_FREETHREADED: "cpython-{major}{minor}t".format(**version_dict), 215 "//conditions:default": "cpython-{major}{minor}".format(**version_dict), 216 }), 217 ) 218 219 py_runtime_pair( 220 name = "python_runtimes", 221 py2_runtime = None, 222 py3_runtime = ":py3_runtime", 223 ) 224 225 py_cc_toolchain( 226 name = "py_cc_toolchain", 227 headers = ":python_headers", 228 libs = ":libpython", 229 python_version = python_version, 230 ) 231 232 py_exec_tools_toolchain( 233 name = "py_exec_tools_toolchain", 234 # This macro is called in another repo: use Label() to ensure it 235 # resolves in the rules_python context. 236 precompiler = Label("//tools/precompiler:precompiler"), 237 ) 238