Skip to content

Commit d3aa535

Browse files
committed
feat(pip_parse): support referencing dependencies to packages via hub
With this change we can in theory have multi-platform libraries in the dependency cycle and use the pip hub repo for the dependencies. With this we can also make the contents of `whl_library` not depend on what platform the actual dependencies are. This allows us to support the following topologies: * A platform-specific wheel depends on cross-platform wheel. * A cross-platform wheel depends on cross-platform wheel. * A whl_library can have `select` dependencies based on the interpreter version, e.g. pull in a `tomli` dependency only when the Python interpreter is less than 3.11. Relates to bazel-contrib#1663. Work towards bazel-contrib#735.
1 parent 4be00a6 commit d3aa535

12 files changed

+371
-66
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ A brief description of the categories of changes:
6767
downloader. If you see any issues, report in
6868
[#1357](https://github.com/bazelbuild/rules_python/issues/1357). The URLs for
6969
the whl and sdist files will be written to the lock file.
70+
* (pip_parse): A new flag `use_hub_alias_dependencies` has been added that is going
71+
to become default in the next release. This makes use of `dep_template` flag
72+
in the `whl_library` rule. This also affects the
73+
`experimental_requirement_cycles` feature where the dependencies that are in
74+
a group would be only accessible via the hub repo aliases. If you still
75+
depend on legacy labels instead of the hub repo aliases and you use the
76+
`experimental_requirement_cycles`, now is a good time to migrate.
7077

7178
[0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0
7279
[python_default_visibility]: gazelle/README.md#directive-python_default_visibility

python/pip_install/pip_repository.bzl

+30-2
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,12 @@ def _pip_repository_impl(rctx):
365365
"python_interpreter": _get_python_interpreter_attr(rctx),
366366
"quiet": rctx.attr.quiet,
367367
"repo": rctx.attr.name,
368-
"repo_prefix": "{}_".format(rctx.attr.name),
369368
"timeout": rctx.attr.timeout,
370369
}
370+
if rctx.attr.use_hub_alias_dependencies:
371+
config["dep_template"] = "@{}//{{name}}:{{target}}".format(rctx.attr.name)
372+
else:
373+
config["repo_prefix"] = "{}_".format(rctx.attr.name)
371374

372375
if rctx.attr.python_interpreter_target:
373376
config["python_interpreter_target"] = str(rctx.attr.python_interpreter_target)
@@ -387,6 +390,13 @@ def _pip_repository_impl(rctx):
387390

388391
rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
389392
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
393+
" # %%GROUP_LIBRARY%%": """\
394+
group_repo = "{name}__groups"
395+
group_library(
396+
name = group_repo,
397+
repo_prefix = "{name}_",
398+
groups = all_requirement_groups,
399+
)""".format(name = rctx.attr.name) if not rctx.attr.use_hub_alias_dependencies else "",
390400
"%%ALL_DATA_REQUIREMENTS%%": _format_repr_list([
391401
macro_tmpl.format(p, "data")
392402
for p in bzl_packages
@@ -595,6 +605,8 @@ python_interpreter. An example value: "@python3_x86_64-unknown-linux-gnu//:pytho
595605
"repo_prefix": attr.string(
596606
doc = """
597607
Prefix for the generated packages will be of the form `@<prefix><sanitized-package-name>//...`
608+
609+
DEPRECATED. Only left for people who vendor requirements.bzl.
598610
""",
599611
),
600612
# 600 is documented as default here: https://docs.bazel.build/versions/master/skylark/lib/repository_ctx.html#execute
@@ -637,6 +649,15 @@ attributes.
637649
allow_single_file = True,
638650
doc = "Override the requirements_lock attribute when the host platform is Windows",
639651
),
652+
"use_hub_alias_dependencies": attr.bool(
653+
default = False,
654+
doc = """\
655+
Controls if the hub alias dependencies are used. If set to true, then the
656+
group_library will be included in the hub repo.
657+
658+
True will become default in a subsequent release.
659+
""",
660+
),
640661
"_template": attr.label(
641662
default = ":pip_repository_requirements.bzl.tmpl",
642663
),
@@ -886,7 +907,7 @@ def _whl_library_impl(rctx):
886907
entry_points[entry_point_without_py] = entry_point_script_name
887908

888909
build_file_contents = generate_whl_library_build_bazel(
889-
repo_prefix = rctx.attr.repo_prefix,
910+
dep_template = rctx.attr.dep_template or "@{}_{{name}}//:{{target}}".format(rctx.attr.repo_prefix),
890911
whl_name = whl_path.basename,
891912
dependencies = metadata["deps"],
892913
dependencies_by_platform = metadata["deps_by_platform"],
@@ -941,6 +962,13 @@ whl_library_attrs = dict({
941962
),
942963
allow_files = True,
943964
),
965+
"dep_template": attr.string(
966+
doc = """
967+
The dep template to use for referencing the dependencies. It should have `{name}`
968+
and `{target}` tokens that will be replaced with the normalized distribution name
969+
and the target that we need respectively.
970+
""",
971+
),
944972
"filename": attr.string(
945973
doc = "Download the whl file to this filename. Only used when the `urls` is passed. If not specified, will be auto-detected from the `urls`.",
946974
),

python/pip_install/pip_repository_requirements.bzl.tmpl

+1-6
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ def install_deps(**whl_library_kwargs):
5858
for requirement in group_requirements
5959
}
6060

61-
group_repo = "%%NAME%%__groups"
62-
group_library(
63-
name = group_repo,
64-
repo_prefix = "%%NAME%%_",
65-
groups = all_requirement_groups,
66-
)
61+
# %%GROUP_LIBRARY%%
6762

6863
# Install wheels which may be participants in a group
6964
whl_config = dict(_config)

python/pip_install/private/generate_group_library_build_bazel.bzl

+30-16
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ load(
2222
"WHEEL_FILE_PUBLIC_LABEL",
2323
)
2424
load("//python/private:normalize_name.bzl", "normalize_name")
25+
load("//python/private:text_util.bzl", "render")
2526

2627
_PRELUDE = """\
27-
load("@rules_python//python:defs.bzl", "py_library", "py_binary")
28+
load("@rules_python//python:defs.bzl", "py_library")
2829
"""
2930

3031
_GROUP_TEMPLATE = """\
@@ -62,26 +63,39 @@ def _generate_group_libraries(repo_prefix, group_name, group_members):
6263
which make up the group.
6364
"""
6465

65-
lib_dependencies = [
66-
"@%s%s//:%s" % (repo_prefix, normalize_name(d), PY_LIBRARY_IMPL_LABEL)
67-
for d in group_members
68-
]
69-
whl_file_deps = [
70-
"@%s%s//:%s" % (repo_prefix, normalize_name(d), WHEEL_FILE_IMPL_LABEL)
71-
for d in group_members
72-
]
73-
visibility = [
74-
"@%s%s//:__pkg__" % (repo_prefix, normalize_name(d))
75-
for d in group_members
76-
]
66+
group_members = sorted(group_members)
67+
68+
if repo_prefix:
69+
lib_dependencies = [
70+
"@%s%s//:%s" % (repo_prefix, normalize_name(d), PY_LIBRARY_IMPL_LABEL)
71+
for d in group_members
72+
]
73+
whl_file_deps = [
74+
"@%s%s//:%s" % (repo_prefix, normalize_name(d), WHEEL_FILE_IMPL_LABEL)
75+
for d in group_members
76+
]
77+
visibility = [
78+
"@%s%s//:__pkg__" % (repo_prefix, normalize_name(d))
79+
for d in group_members
80+
]
81+
else:
82+
lib_dependencies = [
83+
"//%s:%s" % (normalize_name(d), PY_LIBRARY_IMPL_LABEL)
84+
for d in group_members
85+
]
86+
whl_file_deps = [
87+
"//%s:%s" % (normalize_name(d), WHEEL_FILE_IMPL_LABEL)
88+
for d in group_members
89+
]
90+
visibility = ["//:__subpackages__"]
7791

7892
return _GROUP_TEMPLATE.format(
7993
name = normalize_name(group_name),
8094
whl_public_label = WHEEL_FILE_PUBLIC_LABEL,
81-
whl_deps = repr(whl_file_deps),
95+
whl_deps = render.indent(render.list(whl_file_deps)).lstrip(),
8296
lib_public_label = PY_LIBRARY_PUBLIC_LABEL,
83-
lib_deps = repr(lib_dependencies),
84-
visibility = repr(visibility),
97+
lib_deps = render.indent(render.list(lib_dependencies)).lstrip(),
98+
visibility = render.indent(render.list(visibility)).lstrip(),
8599
)
86100

87101
def generate_group_library_build_bazel(

python/pip_install/private/generate_whl_library_build_bazel.bzl

+21-10
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ selects.config_setting_group(
213213

214214
def generate_whl_library_build_bazel(
215215
*,
216-
repo_prefix,
216+
dep_template,
217217
whl_name,
218218
dependencies,
219219
dependencies_by_platform,
@@ -226,7 +226,7 @@ def generate_whl_library_build_bazel(
226226
"""Generate a BUILD file for an unzipped Wheel
227227
228228
Args:
229-
repo_prefix: the repo prefix that should be used for dependency lists.
229+
dep_template: the dependency template that should be used for dependency lists.
230230
whl_name: the whl_name that this is generated for.
231231
dependencies: a list of PyPI packages that are dependencies to the py_library.
232232
dependencies_by_platform: a dict[str, list] of PyPI packages that may vary by platform.
@@ -328,38 +328,49 @@ def generate_whl_library_build_bazel(
328328
lib_dependencies = _render_list_and_select(
329329
deps = dependencies,
330330
deps_by_platform = dependencies_by_platform,
331-
tmpl = "@{}{{}}//:{}".format(repo_prefix, PY_LIBRARY_PUBLIC_LABEL),
331+
tmpl = dep_template.format(name = "{}", target = PY_LIBRARY_PUBLIC_LABEL),
332332
)
333333

334334
whl_file_deps = _render_list_and_select(
335335
deps = dependencies,
336336
deps_by_platform = dependencies_by_platform,
337-
tmpl = "@{}{{}}//:{}".format(repo_prefix, WHEEL_FILE_PUBLIC_LABEL),
337+
tmpl = dep_template.format(name = "{}", target = WHEEL_FILE_PUBLIC_LABEL),
338338
)
339339

340340
# If this library is a member of a group, its public label aliases need to
341341
# point to the group implementation rule not the implementation rules. We
342342
# also need to mark the implementation rules as visible to the group
343343
# implementation.
344-
if group_name:
345-
group_repo = repo_prefix + "_groups"
346-
label_tmpl = "\"@{}//:{}_{{}}\"".format(group_repo, normalize_name(group_name))
347-
impl_vis = ["@{}//:__pkg__".format(group_repo)]
344+
if group_name and "//:" in dep_template:
345+
# This is the legacy behaviour where the group library is outside the hub repo
346+
label_tmpl = dep_template.format(
347+
name = "_groups",
348+
target = normalize_name(group_name) + "_{}",
349+
)
350+
impl_vis = [dep_template.format(
351+
name = "_groups",
352+
target = "__pkg__",
353+
)]
348354
additional_content.extend([
349355
"",
350356
render.alias(
351357
name = PY_LIBRARY_PUBLIC_LABEL,
352-
actual = label_tmpl.format(PY_LIBRARY_PUBLIC_LABEL),
358+
actual = repr(label_tmpl.format(PY_LIBRARY_PUBLIC_LABEL)),
353359
),
354360
"",
355361
render.alias(
356362
name = WHEEL_FILE_PUBLIC_LABEL,
357-
actual = label_tmpl.format(WHEEL_FILE_PUBLIC_LABEL),
363+
actual = repr(label_tmpl.format(WHEEL_FILE_PUBLIC_LABEL)),
358364
),
359365
])
360366
py_library_label = PY_LIBRARY_IMPL_LABEL
361367
whl_file_label = WHEEL_FILE_IMPL_LABEL
362368

369+
elif group_name:
370+
py_library_label = PY_LIBRARY_PUBLIC_LABEL
371+
whl_file_label = WHEEL_FILE_PUBLIC_LABEL
372+
impl_vis = [dep_template.format(name = "", target = "__subpackages__")]
373+
363374
else:
364375
py_library_label = PY_LIBRARY_PUBLIC_LABEL
365376
whl_file_label = WHEEL_FILE_PUBLIC_LABEL

python/private/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ bzl_library(
226226
":normalize_name_bzl",
227227
":text_util_bzl",
228228
":version_label_bzl",
229+
"//python/pip_install/private:generate_group_library_build_bazel_bzl",
229230
],
230231
)
231232

python/private/bzlmod/pip.bzl

+11-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ load("@bazel_features//:features.bzl", "bazel_features")
1818
load("@pythons_hub//:interpreters.bzl", "DEFAULT_PYTHON_VERSION", "INTERPRETER_LABELS")
1919
load(
2020
"//python/pip_install:pip_repository.bzl",
21-
"group_library",
2221
"locked_requirements_label",
2322
"pip_repository_attrs",
2423
"use_isolated",
@@ -101,7 +100,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
101100
whl_mods = whl_mods,
102101
)
103102

104-
def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, simpleapi_cache):
103+
def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, group_map, simpleapi_cache):
105104
python_interpreter_target = pip_attr.python_interpreter_target
106105

107106
# if we do not have the python_interpreter set in the attributes
@@ -129,6 +128,7 @@ def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, simpleapi_ca
129128
hub_name,
130129
version_label(pip_attr.python_version),
131130
)
131+
major_minor = _major_minor_version(pip_attr.python_version)
132132

133133
requirements_lock = locked_requirements_label(module_ctx, pip_attr)
134134

@@ -171,12 +171,11 @@ def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, simpleapi_ca
171171
for whl_name in group_whls
172172
}
173173

174-
group_repo = "%s__groups" % (pip_name,)
175-
group_library(
176-
name = group_repo,
177-
repo_prefix = pip_name + "_",
178-
groups = pip_attr.experimental_requirement_cycles,
179-
)
174+
# TODO @aignas 2024-04-05: how do we support different requirement
175+
# cycles for different abis/oses? For now we will need the users to
176+
# assume the same groups across all versions/platforms until we start
177+
# using an alternative cycle resolution strategy.
178+
group_map[hub_name] = pip_attr.experimental_requirement_cycles
180179
else:
181180
whl_group_mapping = {}
182181
requirement_cycles = {}
@@ -201,8 +200,6 @@ def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, simpleapi_ca
201200
cache = simpleapi_cache,
202201
)
203202

204-
major_minor = _major_minor_version(pip_attr.python_version)
205-
206203
# Create a new wheel library for each of the different whls
207204
for whl_name, requirement_line in requirements:
208205
# We are not using the "sanitized name" because the user
@@ -219,7 +216,7 @@ def _create_whl_repos(module_ctx, pip_attr, whl_map, whl_overrides, simpleapi_ca
219216
repo_name = "{}_{}".format(pip_name, whl_name)
220217
whl_library_args = dict(
221218
repo = pip_name,
222-
repo_prefix = pip_name + "_",
219+
dep_template = "@{}//{{name}}:{{target}}".format(hub_name),
223220
requirement = requirement_line,
224221
)
225222
maybe_args = dict(
@@ -421,6 +418,7 @@ def _pip_impl(module_ctx):
421418
# dict[hub, dict[whl, dict[version, str pip]]]
422419
# Where hub, whl, and pip are the repo names
423420
hub_whl_map = {}
421+
hub_group_map = {}
424422

425423
simpleapi_cache = {}
426424

@@ -459,7 +457,7 @@ def _pip_impl(module_ctx):
459457
else:
460458
pip_hub_map[pip_attr.hub_name].python_versions.append(pip_attr.python_version)
461459

462-
_create_whl_repos(module_ctx, pip_attr, hub_whl_map, whl_overrides, simpleapi_cache)
460+
_create_whl_repos(module_ctx, pip_attr, hub_whl_map, whl_overrides, hub_group_map, simpleapi_cache)
463461

464462
for hub_name, whl_map in hub_whl_map.items():
465463
pip_repository(
@@ -470,6 +468,7 @@ def _pip_impl(module_ctx):
470468
for key, value in whl_map.items()
471469
},
472470
default_version = _major_minor_version(DEFAULT_PYTHON_VERSION),
471+
groups = hub_group_map.get(hub_name),
473472
)
474473

475474
def _pip_parse_ext_attrs():

python/private/bzlmod/pip_repository.bzl

+4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _pip_repository_impl(rctx):
3232
for key, values in rctx.attr.whl_map.items()
3333
},
3434
default_version = rctx.attr.default_version,
35+
requirement_cycles = rctx.attr.groups,
3536
)
3637
for path, contents in aliases.items():
3738
rctx.file(path, contents)
@@ -68,6 +69,9 @@ This is the default python version in the format of X.Y. This should match
6869
what is setup by the 'python' extension using the 'is_default = True'
6970
setting.""",
7071
),
72+
"groups": attr.string_list_dict(
73+
mandatory = False,
74+
),
7175
"repo_name": attr.string(
7276
mandatory = True,
7377
doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name.",

0 commit comments

Comments
 (0)