Skip to content

Commit 218f8e1

Browse files
authored
fix(bzlmod): allow users to specify extra targets to be added to hub repos (bazel-contrib#2369)
Before this change, it was impossible for users to use the targets created with `additive_build_content` whl annotation unless they relied on the implementation detail of the naming of the spoke repositories and had `use_repo` statements in their `MODULE.bazel` files importing the spoke repos. With bazel-contrib#2325 in the works, users will have to change their `use_repo` statements, which is going to be disruptive. In order to offer them an alternative for not relying on the names of the spokes, there has to be a way to expose the extra targets created and this PR implements a method. Incidentally, the same would have happened if we wanted to stabilize the bazel-contrib#260 work and mark `experimental_index_url` as non-experimental anymore. I was hoping we could autodetect them by parsing the build content ourselves in the `pip` extension, but it turned out to be extremely tricky and I figured that it was better to have an API rather than not have it. Whilst at it, also relax the naming requirements for the `whl_modifications` attribute. Fixes bazel-contrib#2187
1 parent ae0aeff commit 218f8e1

File tree

9 files changed

+196
-34
lines changed

9 files changed

+196
-34
lines changed

.bazelci/presubmit.yml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,6 @@ buildifier:
6565
.reusable_build_test_all: &reusable_build_test_all
6666
build_targets: ["..."]
6767
test_targets: ["..."]
68-
.lockfile_mode_error: &lockfile_mode_error
69-
# For testing lockfile support
70-
skip_in_bazel_downstream_pipeline: "Lockfile depends on the bazel version"
71-
build_flags:
72-
- "--lockfile_mode=error"
73-
test_flags:
74-
- "--lockfile_mode=error"
75-
coverage_flags:
76-
- "--lockfile_mode=error"
7768
.coverage_targets_example_bzlmod: &coverage_targets_example_bzlmod
7869
coverage_targets: ["..."]
7970
.coverage_targets_example_bzlmod_build_file_generation: &coverage_targets_example_bzlmod_build_file_generation
@@ -268,17 +259,23 @@ tasks:
268259
integration_test_bzlmod_ubuntu_lockfile:
269260
<<: *reusable_build_test_all
270261
<<: *coverage_targets_example_bzlmod
271-
<<: *lockfile_mode_error
272262
name: "examples/bzlmod: Ubuntu with lockfile"
273263
working_directory: examples/bzlmod
274264
platform: ubuntu2004
265+
shell_commands:
266+
# Update the lockfiles and fail if it is different.
267+
- "../../tools/private/update_bzlmod_lockfiles.sh"
268+
- "git diff --exit-code"
275269
integration_test_bzlmod_macos_lockfile:
276270
<<: *reusable_build_test_all
277271
<<: *coverage_targets_example_bzlmod
278-
<<: *lockfile_mode_error
279272
name: "examples/bzlmod: macOS with lockfile"
280273
working_directory: examples/bzlmod
281274
platform: macos
275+
shell_commands:
276+
# Update the lockfiles and fail if it is different.
277+
- "../../tools/private/update_bzlmod_lockfiles.sh"
278+
- "git diff --exit-code"
282279

283280
integration_test_bzlmod_generate_build_file_generation_ubuntu_min:
284281
<<: *minimum_supported_version

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ A brief description of the categories of changes:
3737
by default. Users wishing to keep this argument and to enforce more hermetic
3838
builds can do so by passing the argument in
3939
[`pip.parse#extra_pip_args`](https://rules-python.readthedocs.io/en/latest/api/rules_python/python/extensions/pip.html#pip.parse.extra_pip_args)
40+
* (pip.parse) {attr}`pip.parse.whl_modifications` now normalizes the given whl names
41+
and now `pyyaml` and `PyYAML` will both work.
4042

4143
{#v0-0-0-fixed}
4244
### Fixed
@@ -58,6 +60,9 @@ A brief description of the categories of changes:
5860
and one extra file `requirements_universal.txt` if you prefer a single file.
5961
The `requirements.txt` file may be removed in the future.
6062
* The rules_python version is now reported in `//python/features.bzl#features.version`
63+
* (pip.parse) {attr}`pip.parse.extra_hub_aliases` can now be used to expose extra
64+
targets created by annotations in whl repositories.
65+
Fixes [#2187](https://github.com/bazelbuild/rules_python/issues/2187).
6166

6267
{#v0-0-0-removed}
6368
### Removed

examples/bzlmod/BUILD.bazel

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,24 @@ py_test_with_transition(
6969
# to run some of the tests.
7070
# See: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/build_test_doc.md
7171
build_test(
72-
name = "all_wheels",
72+
name = "all_wheels_build_test",
7373
targets = all_whl_requirements,
7474
)
7575

7676
build_test(
77-
name = "all_data_requirements",
77+
name = "all_data_requirements_build_test",
7878
targets = all_data_requirements,
7979
)
8080

8181
build_test(
82-
name = "all_requirements",
82+
name = "all_requirements_build_test",
8383
targets = all_requirements,
8484
)
85+
86+
# Check the annotations API
87+
build_test(
88+
name = "extra_annotation_targets_build_test",
89+
targets = [
90+
"@pip//wheel:generated_file",
91+
],
92+
)

examples/bzlmod/MODULE.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ pip.parse(
183183
"cp39_linux_*",
184184
"cp39_*",
185185
],
186+
extra_hub_aliases = {
187+
"wheel": ["generated_file"],
188+
},
186189
hub_name = "pip",
187190
python_version = "3.9",
188191
requirements_lock = "requirements_lock_3_9.txt",

examples/bzlmod/MODULE.bazel.lock

Lines changed: 10 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/private/pypi/extension.bzl

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ def _create_whl_repos(
104104
# containers to aggregate outputs from this function
105105
whl_map = {}
106106
exposed_packages = {}
107+
extra_aliases = {
108+
whl_name: {alias: True for alias in aliases}
109+
for whl_name, aliases in pip_attr.extra_hub_aliases.items()
110+
}
107111
whl_libraries = {}
108112

109113
# if we do not have the python_interpreter set in the attributes
@@ -136,7 +140,7 @@ def _create_whl_repos(
136140
whl_modifications = {}
137141
if pip_attr.whl_modifications != None:
138142
for mod, whl_name in pip_attr.whl_modifications.items():
139-
whl_modifications[whl_name] = mod
143+
whl_modifications[normalize_name(whl_name)] = mod
140144

141145
if pip_attr.experimental_requirement_cycles:
142146
requirement_cycles = {
@@ -214,10 +218,6 @@ def _create_whl_repos(
214218

215219
repository_platform = host_platform(module_ctx)
216220
for whl_name, requirements in requirements_by_platform.items():
217-
# We are not using the "sanitized name" because the user
218-
# would need to guess what name we modified the whl name
219-
# to.
220-
annotation = whl_modifications.get(whl_name)
221221
whl_name = normalize_name(whl_name)
222222

223223
group_name = whl_group_mapping.get(whl_name)
@@ -231,7 +231,7 @@ def _create_whl_repos(
231231
)
232232
maybe_args = dict(
233233
# The following values are safe to omit if they have false like values
234-
annotation = annotation,
234+
annotation = whl_modifications.get(whl_name),
235235
download_only = pip_attr.download_only,
236236
enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs,
237237
environment = pip_attr.environment,
@@ -353,6 +353,7 @@ def _create_whl_repos(
353353
is_reproducible = is_reproducible,
354354
whl_map = whl_map,
355355
exposed_packages = exposed_packages,
356+
extra_aliases = extra_aliases,
356357
whl_libraries = whl_libraries,
357358
)
358359

@@ -437,6 +438,7 @@ You cannot use both the additive_build_content and additive_build_content_file a
437438
hub_whl_map = {}
438439
hub_group_map = {}
439440
exposed_packages = {}
441+
extra_aliases = {}
440442
whl_libraries = {}
441443

442444
is_reproducible = True
@@ -486,6 +488,9 @@ You cannot use both the additive_build_content and additive_build_content_file a
486488
hub_whl_map.setdefault(hub_name, {})
487489
for key, settings in out.whl_map.items():
488490
hub_whl_map[hub_name].setdefault(key, []).extend(settings)
491+
extra_aliases.setdefault(hub_name, {})
492+
for whl_name, aliases in out.extra_aliases.items():
493+
extra_aliases[hub_name].setdefault(whl_name, {}).update(aliases)
489494
exposed_packages.setdefault(hub_name, {}).update(out.exposed_packages)
490495
whl_libraries.update(out.whl_libraries)
491496
is_reproducible = is_reproducible and out.is_reproducible
@@ -514,6 +519,13 @@ You cannot use both the additive_build_content and additive_build_content_file a
514519
for hub_name, group_map in sorted(hub_group_map.items())
515520
},
516521
exposed_packages = {k: sorted(v) for k, v in sorted(exposed_packages.items())},
522+
extra_aliases = {
523+
hub_name: {
524+
whl_name: sorted(aliases)
525+
for whl_name, aliases in extra_whl_aliases.items()
526+
}
527+
for hub_name, extra_whl_aliases in extra_aliases.items()
528+
},
517529
whl_libraries = dict(sorted(whl_libraries.items())),
518530
is_reproducible = is_reproducible,
519531
)
@@ -598,6 +610,7 @@ def _pip_impl(module_ctx):
598610
hub_repository(
599611
name = hub_name,
600612
repo_name = hub_name,
613+
extra_hub_aliases = mods.extra_aliases.get(hub_name, {}),
601614
whl_map = {
602615
key: json.encode(value)
603616
for key, value in whl_map.items()
@@ -684,6 +697,16 @@ The indexes must support Simple API as described here:
684697
https://packaging.python.org/en/latest/specifications/simple-repository-api/
685698
""",
686699
),
700+
"extra_hub_aliases": attr.string_list_dict(
701+
doc = """\
702+
Extra aliases to make for specific wheels in the hub repo. This is useful when
703+
paired with the {attr}`whl_modifications`.
704+
705+
:::{versionadded} 0.38.0
706+
:::
707+
""",
708+
mandatory = False,
709+
),
687710
"hub_name": attr.string(
688711
mandatory = True,
689712
doc = """

python/private/pypi/hub_repository.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def _impl(rctx):
3535
key: [whl_alias(**v) for v in json.decode(values)]
3636
for key, values in rctx.attr.whl_map.items()
3737
},
38+
extra_hub_aliases = rctx.attr.extra_hub_aliases,
3839
requirement_cycles = rctx.attr.groups,
3940
)
4041
for path, contents in aliases.items():
@@ -65,6 +66,10 @@ def _impl(rctx):
6566

6667
hub_repository = repository_rule(
6768
attrs = {
69+
"extra_hub_aliases": attr.string_list_dict(
70+
doc = "Extra aliases to make for specific wheels in the hub repo.",
71+
mandatory = True,
72+
),
6873
"groups": attr.string_list_dict(
6974
mandatory = False,
7075
),

python/private/pypi/render_pkg_aliases.bzl

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _render_whl_library_alias(
117117
**kwargs
118118
)
119119

120-
def _render_common_aliases(*, name, aliases, group_name = None):
120+
def _render_common_aliases(*, name, aliases, extra_aliases = [], group_name = None):
121121
lines = [
122122
"""load("@bazel_skylib//lib:selects.bzl", "selects")""",
123123
"""package(default_visibility = ["//visibility:public"])""",
@@ -153,12 +153,17 @@ def _render_common_aliases(*, name, aliases, group_name = None):
153153
target_name = target_name,
154154
visibility = ["//_groups:__subpackages__"] if name.startswith("_") else None,
155155
)
156-
for target_name, name in {
157-
PY_LIBRARY_PUBLIC_LABEL: PY_LIBRARY_IMPL_LABEL if group_name else PY_LIBRARY_PUBLIC_LABEL,
158-
WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL,
159-
DATA_LABEL: DATA_LABEL,
160-
DIST_INFO_LABEL: DIST_INFO_LABEL,
161-
}.items()
156+
for target_name, name in (
157+
{
158+
PY_LIBRARY_PUBLIC_LABEL: PY_LIBRARY_IMPL_LABEL if group_name else PY_LIBRARY_PUBLIC_LABEL,
159+
WHEEL_FILE_PUBLIC_LABEL: WHEEL_FILE_IMPL_LABEL if group_name else WHEEL_FILE_PUBLIC_LABEL,
160+
DATA_LABEL: DATA_LABEL,
161+
DIST_INFO_LABEL: DIST_INFO_LABEL,
162+
} | {
163+
x: x
164+
for x in extra_aliases
165+
}
166+
).items()
162167
],
163168
)
164169
if group_name:
@@ -177,7 +182,7 @@ def _render_common_aliases(*, name, aliases, group_name = None):
177182

178183
return "\n\n".join(lines)
179184

180-
def render_pkg_aliases(*, aliases, requirement_cycles = None):
185+
def render_pkg_aliases(*, aliases, requirement_cycles = None, extra_hub_aliases = {}):
181186
"""Create alias declarations for each PyPI package.
182187
183188
The aliases should be appended to the pip_repository BUILD.bazel file. These aliases
@@ -188,6 +193,8 @@ def render_pkg_aliases(*, aliases, requirement_cycles = None):
188193
aliases: dict, the keys are normalized distribution names and values are the
189194
whl_alias instances.
190195
requirement_cycles: any package groups to also add.
196+
extra_hub_aliases: The list of extra aliases for each whl to be added
197+
in addition to the default ones.
191198
192199
Returns:
193200
A dict of file paths and their contents.
@@ -215,6 +222,7 @@ def render_pkg_aliases(*, aliases, requirement_cycles = None):
215222
"{}/BUILD.bazel".format(normalize_name(name)): _render_common_aliases(
216223
name = normalize_name(name),
217224
aliases = pkg_aliases,
225+
extra_aliases = extra_hub_aliases.get(name, []),
218226
group_name = whl_group_mapping.get(normalize_name(name)),
219227
).strip()
220228
for name, pkg_aliases in aliases.items()

0 commit comments

Comments
 (0)