This repository was archived by the owner on Jan 19, 2024. It is now read-only.
forked from bazel-contrib/rules_python
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathextensions.bzl
More file actions
221 lines (199 loc) · 9.38 KB
/
Copy pathextensions.bzl
File metadata and controls
221 lines (199 loc) · 9.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"Module extensions for use with bzlmod"
load("@rules_python//python:repositories.bzl", "python_register_toolchains")
load("@rules_python//python/pip_install:pip_repository.bzl", "locked_requirements_label", "pip_repository_attrs", "pip_repository_bzlmod", "use_isolated", "whl_library")
load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
load("@rules_python//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
load("@rules_python//python/private:coverage_deps.bzl", "install_coverage_deps")
load("@rules_python//python/private:toolchains_repo.bzl", "get_host_os_arch", "get_host_platform")
def _python_impl(module_ctx):
for mod in module_ctx.modules:
for toolchain_attr in mod.tags.toolchain:
python_register_toolchains(
name = toolchain_attr.name,
python_version = toolchain_attr.python_version,
bzlmod = True,
# Toolchain registration in bzlmod is done in MODULE file
register_toolchains = False,
register_coverage_tool = toolchain_attr.configure_coverage_tool,
ignore_root_user_error = toolchain_attr.ignore_root_user_error,
)
host_hub_name = toolchain_attr.name + "_host_interpreter"
_host_hub(
name = host_hub_name,
user_repo_prefix = toolchain_attr.name,
)
python = module_extension(
implementation = _python_impl,
tag_classes = {
"toolchain": tag_class(
attrs = {
"configure_coverage_tool": attr.bool(
mandatory = False,
doc = "Whether or not to configure the default coverage tool for the toolchains.",
),
"ignore_root_user_error": attr.bool(
default = False,
doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
mandatory = False,
),
"name": attr.string(mandatory = True),
"python_version": attr.string(mandatory = True),
},
),
},
)
# buildifier: disable=unused-variable
def _internal_deps_impl(module_ctx):
pip_install_dependencies()
install_coverage_deps()
internal_deps = module_extension(
implementation = _internal_deps_impl,
tag_classes = {
"install": tag_class(attrs = dict()),
},
)
def _pip_impl(module_ctx):
for mod in module_ctx.modules:
for attr in mod.tags.parse:
requrements_lock = locked_requirements_label(module_ctx, attr)
# Parse the requirements file directly in starlark to get the information
# needed for the whl_libary declarations below. This is needed to contain
# the pip_repository logic to a single module extension.
requirements_lock_content = module_ctx.read(requrements_lock)
parse_result = parse_requirements(requirements_lock_content)
requirements = parse_result.requirements
extra_pip_args = attr.extra_pip_args + parse_result.options
# Create the repository where users load the `requirement` macro. Under bzlmod
# this does not create the install_deps() macro.
pip_repository_bzlmod(
name = attr.name,
requirements_lock = attr.requirements_lock,
incompatible_generate_aliases = attr.incompatible_generate_aliases,
)
for name, requirement_line in requirements:
whl_library(
name = "%s_%s" % (attr.name, _sanitize_name(name)),
requirement = requirement_line,
repo = attr.name,
repo_prefix = attr.name + "_",
annotation = attr.annotations.get(name),
python_interpreter = attr.python_interpreter,
python_interpreter_target = attr.python_interpreter_target,
quiet = attr.quiet,
timeout = attr.timeout,
isolated = use_isolated(module_ctx, attr),
extra_pip_args = extra_pip_args,
download_only = attr.download_only,
pip_data_exclude = attr.pip_data_exclude,
enable_implicit_namespace_pkgs = attr.enable_implicit_namespace_pkgs,
environment = attr.environment,
)
# Keep in sync with python/pip_install/tools/bazel.py
def _sanitize_name(name):
return name.replace("-", "_").replace(".", "_").lower()
def _pip_parse_ext_attrs():
attrs = dict({
"name": attr.string(mandatory = True),
}, **pip_repository_attrs)
# Like the pip_repository rule, we end up setting this manually so
# don't allow users to override it.
attrs.pop("repo_prefix")
return attrs
pip = module_extension(
implementation = _pip_impl,
tag_classes = {
"parse": tag_class(attrs = _pip_parse_ext_attrs()),
},
)
# This function allows us to build the label name of a label
# that is not passed into the current context.
# The module_label is the key element that is passed in.
# This value provides the root location of the labels
# See https://bazel.build/external/extension#repository_names_and_visibility
def _repo_mapped_label(module_label, extension_name, apparent):
"""Construct a canonical repo label accounting for repo mapping.
Args:
module_label: Label object of the module hosting the extension; see
"_module" implicit attribute.
extension_name: str, name of the extension that created the repo in `apparent`.
apparent: str, a repo-qualified target string, but without the "@". e.g.
"python38_x86_linux//:python". The repo name should use the apparent
name used by the extension named by `ext_name` (i.e. the value of the
`name` arg the extension passes to repository rules)
"""
return Label("@@{module}~{extension_name}~{apparent}".format(
module = module_label.workspace_name,
extension_name = extension_name,
apparent = apparent,
))
# We are doing some bazel stuff here that could use an explanation.
# The basis of this function is that we need to create a symlink to
# the python binary that exists in a different repo that we know is
# setup by rules_python.
#
# We are building a Label like
# @@rules_python~override~python~python3_x86_64-unknown-linux-gnu//:python
# and then the function creates a symlink named python to that Label.
# The tricky part is the "~override~" part can't be known in advance
# and will change depending on how and what version of rules_python
# is used. To figure that part out, an implicit attribute is used to
# resolve the module's current name (see "_module" attribute)
#
# We are building the Label name dynamically, and can do this even
# though the Label is not passed into this function. If we choose
# not do this a user would have to write another 16 lines
# of configuration code, but we are able to save them that work
# because we know how rules_python works internally. We are using
# functions from private:toolchains_repo.bzl which is where the repo
# is being built. The repo name differs between host OS and platforms
# and the functions from toolchains_repo gives us this functions that
# information.
def _host_hub_impl(repo_ctx):
# Intentionally empty; this is only intended to be used by repository
# rules, which don't process build file contents.
repo_ctx.file("BUILD.bazel", "")
# The two get_ functions we use are also utilized when building
# the repositories for the different interpreters.
(os, arch) = get_host_os_arch(repo_ctx)
host_platform = "{}_{}//:python".format(
repo_ctx.attr.user_repo_prefix,
get_host_platform(os, arch),
)
# the attribute is set to attr.label(default = "//:_"), which
# provides us the resolved, canonical, prefix for the module's repos.
# The extension_name "python" is determined by the
# name bound to the module_extension() call.
# We then have the OS and platform specific name of the python
# interpreter.
label = _repo_mapped_label(repo_ctx.attr._module, "python", host_platform)
# create the symlink in order to set the interpreter for pip.
repo_ctx.symlink(label, "python")
# We use this rule to set the pip interpreter target when using different operating
# systems with the same project
_host_hub = repository_rule(
implementation = _host_hub_impl,
local = True,
attrs = {
"user_repo_prefix": attr.string(
mandatory = True,
doc = """\
The prefix to create the repository name. Usually the name you used when you created the
Python toolchain.
""",
),
"_module": attr.label(default = "//:_"),
},
)