• 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"""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