Skip to content

Commit f88e083

Browse files
authored
fix(pypi): handle multiple versions of the same package when parsing requirements files (#2377)
This change makes it possible to handle local versions of packages, which is extremely useful with PyTorch. With this change, it is possible to have different local versions of the same package in the `requirements.txt` file translated to valid `whl_library` repositories. Fixes #2337
1 parent 68d1b41 commit f88e083

File tree

5 files changed

+68
-5
lines changed

5 files changed

+68
-5
lines changed

.bazelrc

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
55
# To update these lines, execute
66
# `bazel run @rules_bazel_integration_test//tools:update_deleted_packages`
7-
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/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,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/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered
8-
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/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,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/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/pythonconfig,gazelle/python/private,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered
7+
build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,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/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,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/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered
8+
query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,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/py_proto_library,examples/bzlmod/py_proto_library/example.com/another_proto,examples/bzlmod/py_proto_library/example.com/proto,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,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/another_proto,examples/py_proto_library/example.com/proto,gazelle,gazelle/manifest,gazelle/manifest/generate,gazelle/manifest/hasher,gazelle/manifest/test,gazelle/modules_mapping,gazelle/python,gazelle/python/private,gazelle/pythonconfig,tests/integration/compile_pip_requirements,tests/integration/compile_pip_requirements_test_from_external_repo,tests/integration/custom_commands,tests/integration/ignore_root_user_error,tests/integration/ignore_root_user_error/submodule,tests/integration/local_toolchains,tests/integration/pip_parse,tests/integration/pip_parse/empty,tests/integration/py_cc_toolchain_registered
99

1010
test --test_output=errors
1111

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ Other changes:
7575
{#v0-0-0-fixed}
7676
### Fixed
7777
* (toolchains) stop depending on `uname` to get the value of the host platform.
78+
* (pypi): Correctly handle multiple versions of the same package in the requirements
79+
files which is useful when including different PyTorch builds (e.g. <pytorch+cpu> vs <pytorch+cu118> ) for different target platforms.
80+
Fixes ([2337](https://github.com/bazelbuild/rules_python/issues/2337)).
7881

7982
{#v0-0-0-added}
8083
### Added

examples/bzlmod/MODULE.bazel.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/private/pypi/parse_requirements.bzl

+15-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ load(":index_sources.bzl", "index_sources")
3232
load(":parse_requirements_txt.bzl", "parse_requirements_txt")
3333
load(":whl_target_platforms.bzl", "select_whls")
3434

35+
def _extract_version(entry):
36+
"""Extract the version part from the requirement string.
37+
38+
39+
Args:
40+
entry: {type}`str` The requirement string.
41+
"""
42+
version_start = entry.find("==")
43+
if version_start != -1:
44+
# Extract everything after '==' until the next space or end of the string
45+
version, _, _ = entry[version_start + 2:].partition(" ")
46+
return version
47+
return None
48+
3549
def parse_requirements(
3650
ctx,
3751
*,
@@ -92,7 +106,7 @@ def parse_requirements(
92106
# are returned as just the base package name. e.g., `foo[bar]` results
93107
# in an entry like `("foo", "foo[bar] == 1.0 ...")`.
94108
requirements_dict = {
95-
normalize_name(entry[0]): entry
109+
(normalize_name(entry[0]), _extract_version(entry[1])): entry
96110
for entry in sorted(
97111
parse_result.requirements,
98112
# Get the longest match and fallback to original WORKSPACE sorting,

tests/pypi/parse_requirements/parse_requirements_tests.bzl

+46
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ load("//python/private/pypi:parse_requirements.bzl", "parse_requirements", "sele
1919

2020
def _mock_ctx():
2121
testdata = {
22+
"requirements_different_package_version": """\
23+
foo==0.0.1+local --hash=sha256:deadbeef
24+
foo==0.0.1 --hash=sha256:deadb00f
25+
""",
2226
"requirements_direct": """\
2327
foo[extra] @ https://some-url
2428
""",
@@ -382,6 +386,48 @@ def _test_env_marker_resolution(env):
382386

383387
_tests.append(_test_env_marker_resolution)
384388

389+
def _test_different_package_version(env):
390+
got = parse_requirements(
391+
ctx = _mock_ctx(),
392+
requirements_by_platform = {
393+
"requirements_different_package_version": ["linux_x86_64"],
394+
},
395+
)
396+
env.expect.that_dict(got).contains_exactly({
397+
"foo": [
398+
struct(
399+
distribution = "foo",
400+
extra_pip_args = [],
401+
requirement_line = "foo==0.0.1 --hash=sha256:deadb00f",
402+
srcs = struct(
403+
requirement = "foo==0.0.1",
404+
shas = ["deadb00f"],
405+
version = "0.0.1",
406+
),
407+
target_platforms = ["linux_x86_64"],
408+
whls = [],
409+
sdist = None,
410+
is_exposed = True,
411+
),
412+
struct(
413+
distribution = "foo",
414+
extra_pip_args = [],
415+
requirement_line = "foo==0.0.1+local --hash=sha256:deadbeef",
416+
srcs = struct(
417+
requirement = "foo==0.0.1+local",
418+
shas = ["deadbeef"],
419+
version = "0.0.1+local",
420+
),
421+
target_platforms = ["linux_x86_64"],
422+
whls = [],
423+
sdist = None,
424+
is_exposed = True,
425+
),
426+
],
427+
})
428+
429+
_tests.append(_test_different_package_version)
430+
385431
def parse_requirements_test_suite(name):
386432
"""Create the test suite.
387433

0 commit comments

Comments
 (0)