xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/hermetic_runtime_repo_setup.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"""Setup a python-build-standalone based toolchain."""
15
16load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")
17load("//python:py_runtime.bzl", "py_runtime")
18load("//python:py_runtime_pair.bzl", "py_runtime_pair")
19load("//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
20load(":py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
21load(":semver.bzl", "semver")
22
23def define_hermetic_runtime_toolchain_impl(
24        *,
25        name,
26        extra_files_glob_include,
27        extra_files_glob_exclude,
28        python_version,
29        python_bin,
30        coverage_tool):
31    """Define a toolchain implementation for a python-build-standalone repo.
32
33    It expected this macro is called in the top-level package of an extracted
34    python-build-standalone repository. See
35    python/private/python_repositories.bzl for how it is invoked.
36
37    Args:
38        name: {type}`str` name used for tools to identify the invocation.
39        extra_files_glob_include: {type}`list[str]` additional glob include
40            patterns for the target runtime files (the one included in
41            binaries).
42        extra_files_glob_exclude: {type}`list[str]` additional glob exclude
43            patterns for the target runtime files.
44        python_version: {type}`str` The Python version, in `major.minor.micro`
45            format.
46        python_bin: {type}`str` The path to the Python binary within the
47            repositoroy.
48        coverage_tool: {type}`str` optional target to the coverage tool to
49            use.
50    """
51    _ = name  # @unused
52    version_info = semver(python_version)
53    version_dict = version_info.to_dict()
54    native.filegroup(
55        name = "files",
56        srcs = native.glob(
57            include = [
58                "bin/**",
59                "extensions/**",
60                "include/**",
61                "libs/**",
62                "share/**",
63            ] + extra_files_glob_include,
64            # Platform-agnostic filegroup can't match on all patterns.
65            allow_empty = True,
66            exclude = [
67                "**/* *",  # Bazel does not support spaces in file names.
68                # Unused shared libraries. `python` executable and the `:libpython` target
69                # depend on `libpython{python_version}.so.1.0`.
70                "lib/libpython{major}.{minor}.so".format(**version_dict),
71                # static libraries
72                "lib/**/*.a",
73                # tests for the standard libraries.
74                "lib/python{major}.{minor}/**/test/**".format(**version_dict),
75                "lib/python{major}.{minor}/**/tests/**".format(**version_dict),
76                "**/__pycache__/*.pyc.*",  # During pyc creation, temp files named *.pyc.NNN are created
77            ] + extra_files_glob_exclude,
78        ),
79    )
80    cc_import(
81        name = "interface",
82        interface_library = "libs/python{major}{minor}.lib".format(**version_dict),
83        system_provided = True,
84    )
85
86    native.filegroup(
87        name = "includes",
88        srcs = native.glob(["include/**/*.h"]),
89    )
90    cc_library(
91        name = "python_headers",
92        deps = select({
93            "@bazel_tools//src/conditions:windows": [":interface"],
94            "//conditions:default": None,
95        }),
96        hdrs = [":includes"],
97        includes = [
98            "include",
99            "include/python{major}.{minor}".format(**version_dict),
100            "include/python{major}.{minor}m".format(**version_dict),
101        ],
102    )
103    cc_library(
104        name = "libpython",
105        hdrs = [":includes"],
106        srcs = select({
107            "@platforms//os:linux": [
108                "lib/libpython{major}.{minor}.so".format(**version_dict),
109                "lib/libpython{major}.{minor}.so.1.0".format(**version_dict),
110            ],
111            "@platforms//os:macos": ["lib/libpython{major}.{minor}.dylib".format(**version_dict)],
112            "@platforms//os:windows": ["python3.dll", "libs/python{major}{minor}.lib".format(**version_dict)],
113        }),
114    )
115
116    native.exports_files(["python", python_bin])
117
118    # Used to only download coverage toolchain when the coverage is collected by
119    # bazel.
120    native.config_setting(
121        name = "coverage_enabled",
122        values = {"collect_code_coverage": "true"},
123        visibility = ["//visibility:private"],
124    )
125
126    py_runtime(
127        name = "py3_runtime",
128        files = [":files"],
129        interpreter = python_bin,
130        interpreter_version_info = {
131            "major": str(version_info.major),
132            "micro": str(version_info.patch),
133            "minor": str(version_info.minor),
134        },
135        # Convert empty string to None
136        coverage_tool = coverage_tool or None,
137        python_version = "PY3",
138        implementation_name = "cpython",
139        # See https://peps.python.org/pep-3147/ for pyc tag infix format
140        pyc_tag = "cpython-{major}{minor}".format(**version_dict),
141    )
142
143    py_runtime_pair(
144        name = "python_runtimes",
145        py2_runtime = None,
146        py3_runtime = ":py3_runtime",
147    )
148
149    py_cc_toolchain(
150        name = "py_cc_toolchain",
151        headers = ":python_headers",
152        libs = ":libpython",
153        python_version = python_version,
154    )
155
156    py_exec_tools_toolchain(
157        name = "py_exec_tools_toolchain",
158        # This macro is called in another repo: use Label() to ensure it
159        # resolves in the rules_python context.
160        precompiler = Label("//tools/precompiler:precompiler"),
161    )
162