Skip to content

Commit c0e18ed

Browse files
authored
feat(bzlmod): support patching 'whl' distributions (#1393)
Before that the users had to rely on patching the actual wheel files and uploading them as different versions to internal artifact stores if they needed to modify the wheel dependencies. This is very common when breaking dependency cycles in `pytorch` or `apache-airflow` packages. With this feature we can support patching external PyPI dependencies via pip.override tag class to fix package dependencies and/or a broken `RECORD` metadata file. Overall design: * Split the `whl_installer` CLI into two parts - downloading and extracting. Merged in #1487. * Add a starlark function which extracts the downloaded wheel applies patches and repackages a wheel (so that the extraction part works as before). * Add a `override` tag_class to the `pip` extension and allow users to pass patches to be applied to specific wheel files. * Only the root module is allowed to apply patches. This is to avoid far away modules modifying the code of other modules and conflicts between modules and their patches. Patches have to be in `unified-diff` format. Related #1076, #1166, #1120
1 parent 327b4e3 commit c0e18ed

File tree

19 files changed

+593
-10
lines changed

19 files changed

+593
-10
lines changed

.bazelrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
# This lets us glob() up all the files inside the examples to make them inputs to tests
44
# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
55
# To update these lines, run tools/bazel_integration_test/update_deleted_packages.sh
6-
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/proto,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
7-
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/proto,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
6+
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/proto,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
7+
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/patches,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,examples/py_proto_library/example.com/proto,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
88

99
test --test_output=errors
1010

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ Breaking changes:
4646
* (py_wheel) Produce deterministic wheel files and make `RECORD` file entries
4747
follow the order of files written to the `.whl` archive.
4848

49+
### Added
50+
51+
* (bzlmod) Added `.whl` patching support via `patches` and `patch_strip`
52+
arguments to the new `pip.override` tag class.
53+
4954
## [0.26.0] - 2023-10-06
5055

5156
### Changed

examples/bzlmod/MODULE.bazel

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,19 @@ pip.parse(
113113
"@whl_mods_hub//:wheel.json": "wheel",
114114
},
115115
)
116+
117+
# You can add patches that will be applied on the whl contents.
118+
#
119+
# The patches have to be in the unified-diff format.
120+
pip.override(
121+
file = "requests-2.25.1-py2.py3-none-any.whl",
122+
patch_strip = 1,
123+
patches = [
124+
"@//patches:empty.patch",
125+
"@//patches:requests_metadata.patch",
126+
"@//patches:requests_record.patch",
127+
],
128+
)
116129
use_repo(pip, "pip")
117130

118131
bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exports_files(
2+
srcs = glob(["*.patch"]),
3+
visibility = ["//visibility:public"],
4+
)

examples/bzlmod/patches/empty.patch

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
diff --unified --recursive a/requests-2.25.1.dist-info/METADATA b/requests-2.25.1.dist-info/METADATA
2+
--- a/requests-2.25.1.dist-info/METADATA 2020-12-16 19:37:50.000000000 +0900
3+
+++ b/requests-2.25.1.dist-info/METADATA 2023-09-30 20:31:50.079863410 +0900
4+
@@ -1,7 +1,7 @@
5+
Metadata-Version: 2.1
6+
Name: requests
7+
Version: 2.25.1
8+
-Summary: Python HTTP for Humans.
9+
+Summary: Python HTTP for Humans. Patched.
10+
Home-page: https://requests.readthedocs.io
11+
Author: Kenneth Reitz
12+
Author-email: [email protected]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--- a/requests-2.25.1.dist-info/RECORD
2+
+++ b/requests-2.25.1.dist-info/RECORD
3+
@@ -17,7 +17,7 @@
4+
requests/structures.py,sha256=msAtr9mq1JxHd-JRyiILfdFlpbJwvvFuP3rfUQT_QxE,3005
5+
requests/utils.py,sha256=_K9AgkN6efPe-a-zgZurXzds5PBC0CzDkyjAE2oCQFQ,30529
6+
requests-2.25.1.dist-info/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142
7+
-requests-2.25.1.dist-info/METADATA,sha256=RuNh38uN0IMsRT3OwaTNB_WyGx6RMwwQoMwujXfkUVM,4168
8+
+requests-2.25.1.dist-info/METADATA,sha256=fRSAA0u0Bi0heD4zYq91wdNUTJlbzhK6_iDOcRRNDx4,4177
9+
requests-2.25.1.dist-info/WHEEL,sha256=Z-nyYpwrcSqxfdux5Mbn_DQ525iP7J2DG3JgGvOYyTQ,110
10+
requests-2.25.1.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9
11+
requests-2.25.1.dist-info/RECORD,,

examples/bzlmod/whl_mods/appended_build_content.BUILD

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ write_file(
55
out = "generated_file.txt",
66
content = ["Hello world from requests"],
77
)
8+
9+
filegroup(
10+
name = "whl_orig",
11+
srcs = glob(
12+
["*.whl"],
13+
allow_empty = False,
14+
exclude = ["*-patched-*.whl"],
15+
),
16+
)

python/pip_install/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ bzl_library(
3030
"//python/pip_install/private:srcs_bzl",
3131
"//python/private:bzlmod_enabled_bzl",
3232
"//python/private:normalize_name_bzl",
33+
"//python/private:patch_whl_bzl",
3334
"//python/private:render_pkg_aliases_bzl",
3435
"//python/private:toolchains_repo_bzl",
3536
"//python/private:which_bzl",
@@ -97,6 +98,8 @@ filegroup(
9798
srcs = [
9899
"//python/pip_install/tools/dependency_resolver:py_srcs",
99100
"//python/pip_install/tools/wheel_installer:py_srcs",
101+
"//python/private:repack_whl.py",
102+
"//tools:wheelmaker.py",
100103
],
101104
visibility = ["//python/pip_install/private:__pkg__"],
102105
)

python/pip_install/pip_repository.bzl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ load("//python/pip_install/private:generate_whl_library_build_bazel.bzl", "gener
2222
load("//python/pip_install/private:srcs.bzl", "PIP_INSTALL_PY_SRCS")
2323
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
2424
load("//python/private:normalize_name.bzl", "normalize_name")
25+
load("//python/private:patch_whl.bzl", "patch_whl")
2526
load("//python/private:render_pkg_aliases.bzl", "render_pkg_aliases")
2627
load("//python/private:toolchains_repo.bzl", "get_host_os_arch")
2728
load("//python/private:which.bzl", "which_with_fail")
@@ -44,6 +45,7 @@ def _construct_pypath(rctx):
4445
4546
Args:
4647
rctx: Handle to the repository_context.
48+
4749
Returns: String of the PYTHONPATH.
4850
"""
4951

@@ -542,6 +544,22 @@ def _whl_library_impl(rctx):
542544
if not rctx.delete("whl_file.json"):
543545
fail("failed to delete the whl_file.json file")
544546

547+
if rctx.attr.whl_patches:
548+
patches = {}
549+
for patch_file, json_args in patches.items():
550+
patch_dst = struct(**json.decode(json_args))
551+
if whl_path.basename in patch_dst.whls:
552+
patches[patch_file] = patch_dst.patch_strip
553+
554+
whl_path = patch_whl(
555+
rctx,
556+
python_interpreter = python_interpreter,
557+
whl_path = whl_path,
558+
patches = patches,
559+
quiet = rctx.attr.quiet,
560+
timeout = rctx.attr.timeout,
561+
)
562+
545563
result = rctx.execute(
546564
args + ["--whl-file", whl_path],
547565
environment = environment,
@@ -635,6 +653,13 @@ whl_library_attrs = {
635653
mandatory = True,
636654
doc = "Python requirement string describing the package to make available",
637655
),
656+
"whl_patches": attr.label_keyed_string_dict(
657+
doc = """"a label-keyed-string dict that has
658+
json.encode(struct([whl_file], patch_strip]) as values. This
659+
is to maintain flexibility and correct bzlmod extension interface
660+
until we have a better way to define whl_library and move whl
661+
patching to a separate place. INTERNAL USE ONLY.""",
662+
),
638663
"_python_path_entries": attr.label_list(
639664
# Get the root directory of these rules and keep them as a default attribute
640665
# in order to avoid unnecessary repository fetching restarts.

0 commit comments

Comments
 (0)