Skip to content

Commit

Permalink
fix(pypi): use local version specifiers for patched whl output (#2365)
Browse files Browse the repository at this point in the history
Before this change the installation of the patched wheels using `uv` or
similar would break. This change fixes that by using local version
specifier, which is better than using a build tag when installing the
wheels.

Before the change:
```console
$ cd examples/bzlmod
$ bazel build @pip//requests:whl
$ uv pip install <path to requests wheel in the example>
error: The wheel filename "requests-2.25.1-patched-py2.py3-none-any.whl" has an invalid build tag: must start with a digit
```

After:
```
$ uv pip install <path to requests wheel in the example>
Resolved 5 packages in 288ms
Prepared 5 packages in 152ms
Installed 5 packages in 13ms
 + certifi==2024.8.30
 + chardet==4.0.0
 + idna==2.10
 + requests==2.25.1+patched (from file:///home/aignas/src/github/aignas/rules_python/examples/bzlmod/bazel-bzlmod/external/rules_python~~pip~pip_39_requests_py2_none_any_c210084e/requests-2.25.1+patched-py2.py3-none-any.whl)
 + urllib3==1.26.20
```
  • Loading branch information
aignas authored Nov 4, 2024
1 parent 3367f82 commit ae0aeff
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ A brief description of the categories of changes:
([617](https://github.com/bazelbuild/rules_python/issues/617)).
* (pypi) When {attr}`pip.parse.experimental_index_url` is set, we need to still
pass the `extra_pip_args` value when building an `sdist`.
* (pypi) The patched wheel filenames from now on are using local version specifiers
which fixes usage of the said wheels using standard package managers.

{#v0-0-0-added}
### Added
Expand Down
4 changes: 2 additions & 2 deletions examples/bzlmod/MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 34 additions & 11 deletions python/private/pypi/patch_whl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,39 @@ load(":parse_whl_name.bzl", "parse_whl_name")

_rules_python_root = Label("//:BUILD.bazel")

def patched_whl_name(original_whl_name):
"""Return the new filename to output the patched wheel.
Args:
original_whl_name: {type}`str` the whl name of the original file.
Returns:
{type}`str` an output name to write the patched wheel to.
"""
parsed_whl = parse_whl_name(original_whl_name)
version = parsed_whl.version
suffix = "patched"
if "+" in version:
# This already has some local version, so we just append one more
# identifier here. We comply with the spec and mark the file as patched
# by adding a local version identifier at the end.
#
# By doing this we can still install the package using most of the package
# managers
#
# See https://packaging.python.org/en/latest/specifications/version-specifiers/#local-version-identifiers
version = "{}.{}".format(version, suffix)
else:
version = "{}+{}".format(version, suffix)

return "{distribution}-{version}-{python_tag}-{abi_tag}-{platform_tag}.whl".format(
distribution = parsed_whl.distribution,
version = version,
python_tag = parsed_whl.python_tag,
abi_tag = parsed_whl.abi_tag,
platform_tag = parsed_whl.platform_tag,
)

def patch_whl(rctx, *, python_interpreter, whl_path, patches, **kwargs):
"""Patch a whl file and repack it to ensure that the RECORD metadata stays correct.
Expand Down Expand Up @@ -66,18 +99,8 @@ def patch_whl(rctx, *, python_interpreter, whl_path, patches, **kwargs):
for patch_file, patch_strip in patches.items():
rctx.patch(patch_file, strip = patch_strip)

# Generate an output filename, which we will be returning
parsed_whl = parse_whl_name(whl_input.basename)
whl_patched = "{}.whl".format("-".join([
parsed_whl.distribution,
parsed_whl.version,
(parsed_whl.build_tag or "") + "patched",
parsed_whl.python_tag,
parsed_whl.abi_tag,
parsed_whl.platform_tag,
]))

record_patch = rctx.path("RECORD.patch")
whl_patched = patched_whl_name(whl_input.basename)

repo_utils.execute_checked(
rctx,
Expand Down
3 changes: 3 additions & 0 deletions tests/pypi/patch_whl/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load(":patch_whl_tests.bzl", "patch_whl_test_suite")

patch_whl_test_suite(name = "patch_whl_tests")
40 changes: 40 additions & 0 deletions tests/pypi/patch_whl/patch_whl_tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2024 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.

""

load("@rules_testing//lib:test_suite.bzl", "test_suite")
load("//python/private/pypi:patch_whl.bzl", "patched_whl_name") # buildifier: disable=bzl-visibility

_tests = []

def _test_simple(env):
got = patched_whl_name("foo-1.2.3-py3-none-any.whl")
env.expect.that_str(got).equals("foo-1.2.3+patched-py3-none-any.whl")

_tests.append(_test_simple)

def _test_simple_local_version(env):
got = patched_whl_name("foo-1.2.3+special-py3-none-any.whl")
env.expect.that_str(got).equals("foo-1.2.3+special.patched-py3-none-any.whl")

_tests.append(_test_simple_local_version)

def patch_whl_test_suite(name):
"""Create the test suite.
Args:
name: the name of the test suite
"""
test_suite(name = name, basic_tests = _tests)

0 comments on commit ae0aeff

Please sign in to comment.