Skip to content

Commit 948fcec

Browse files
authored
fix(pypi): correctly aggregate the requirements files (#2932)
This implements the actual fix where we are aggregating the whls and sdists correctly from multiple different requirements lines. Fixes #2648. Closes #2658.
1 parent 02198f6 commit 948fcec

File tree

3 files changed

+97
-7
lines changed

3 files changed

+97
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ END_UNRELEASED_TEMPLATE
9696
* (pypi) When running under `bazel test`, be sure that temporary `requirements` file
9797
remains writable.
9898
* (py_test, py_binary) Allow external files to be used for main
99+
* (pypi) Correctly aggregate the sources when the hashes specified in the lockfile differ
100+
by platform even though the same version is used. Fixes [#2648](https://github.com/bazel-contrib/rules_python/issues/2648).
99101

100102
{#v0-0-0-added}
101103
### Added

python/private/pypi/parse_requirements.bzl

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def _package_srcs(
223223
env_marker_target_platforms,
224224
extract_url_srcs):
225225
"""A function to return sources for a particular package."""
226-
srcs = []
226+
srcs = {}
227227
for r in sorted(reqs.values(), key = lambda r: r.requirement_line):
228228
whls, sdist = _add_dists(
229229
requirement = r,
@@ -249,21 +249,31 @@ def _package_srcs(
249249
)]
250250
req_line = r.srcs.requirement_line
251251

252+
extra_pip_args = tuple(r.extra_pip_args)
252253
for dist in all_dists:
253-
srcs.append(
254+
key = (
255+
dist.filename,
256+
req_line,
257+
extra_pip_args,
258+
)
259+
entry = srcs.setdefault(
260+
key,
254261
struct(
255262
distribution = name,
256263
extra_pip_args = r.extra_pip_args,
257264
requirement_line = req_line,
258-
target_platforms = target_platforms,
265+
target_platforms = [],
259266
filename = dist.filename,
260267
sha256 = dist.sha256,
261268
url = dist.url,
262269
yanked = dist.yanked,
263270
),
264271
)
272+
for p in target_platforms:
273+
if p not in entry.target_platforms:
274+
entry.target_platforms.append(p)
265275

266-
return srcs
276+
return srcs.values()
267277

268278
def select_requirement(requirements, *, platform):
269279
"""A simple function to get a requirement for a particular platform.

tests/pypi/parse_requirements/parse_requirements_tests.bzl

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ foo[extra]==0.0.1 \
3838
foo @ git+https://github.com/org/foo.git@deadbeef
3939
""",
4040
"requirements_linux": """\
41-
foo==0.0.3 --hash=sha256:deadbaaf
41+
foo==0.0.3 --hash=sha256:deadbaaf --hash=sha256:5d15t
4242
""",
4343
# download_only = True
4444
"requirements_linux_download_only": """\
@@ -67,7 +67,7 @@ foo==0.0.4 @ https://example.org/foo-0.0.4.whl
6767
foo==0.0.5 @ https://example.org/foo-0.0.5.whl --hash=sha256:deadbeef
6868
""",
6969
"requirements_osx": """\
70-
foo==0.0.3 --hash=sha256:deadbaaf
70+
foo==0.0.3 --hash=sha256:deadbaaf --hash=sha256:deadb11f --hash=sha256:5d15t
7171
""",
7272
"requirements_osx_download_only": """\
7373
--platform=macosx_10_9_arm64
@@ -251,7 +251,7 @@ def _test_multi_os(env):
251251
struct(
252252
distribution = "foo",
253253
extra_pip_args = [],
254-
requirement_line = "foo==0.0.3 --hash=sha256:deadbaaf",
254+
requirement_line = "foo==0.0.3 --hash=sha256:deadbaaf --hash=sha256:5d15t",
255255
target_platforms = ["linux_x86_64"],
256256
url = "",
257257
filename = "",
@@ -515,6 +515,84 @@ def _test_git_sources(env):
515515

516516
_tests.append(_test_git_sources)
517517

518+
def _test_overlapping_shas_with_index_results(env):
519+
got = parse_requirements(
520+
ctx = _mock_ctx(),
521+
requirements_by_platform = {
522+
"requirements_linux": ["cp39_linux_x86_64"],
523+
"requirements_osx": ["cp39_osx_x86_64"],
524+
},
525+
get_index_urls = lambda _, __: {
526+
"foo": struct(
527+
sdists = {
528+
"5d15t": struct(
529+
url = "sdist",
530+
sha256 = "5d15t",
531+
filename = "foo-0.0.1.tar.gz",
532+
yanked = False,
533+
),
534+
},
535+
whls = {
536+
"deadb11f": struct(
537+
url = "super2",
538+
sha256 = "deadb11f",
539+
filename = "foo-0.0.1-py3-none-macosx_14_0_x86_64.whl",
540+
yanked = False,
541+
),
542+
"deadbaaf": struct(
543+
url = "super2",
544+
sha256 = "deadbaaf",
545+
filename = "foo-0.0.1-py3-none-any.whl",
546+
yanked = False,
547+
),
548+
},
549+
),
550+
},
551+
)
552+
553+
env.expect.that_collection(got).contains_exactly([
554+
struct(
555+
name = "foo",
556+
is_exposed = True,
557+
# TODO @aignas 2025-05-25: how do we rename this?
558+
is_multiple_versions = True,
559+
srcs = [
560+
struct(
561+
distribution = "foo",
562+
extra_pip_args = [],
563+
filename = "foo-0.0.1-py3-none-any.whl",
564+
requirement_line = "foo==0.0.3",
565+
sha256 = "deadbaaf",
566+
target_platforms = ["cp39_linux_x86_64", "cp39_osx_x86_64"],
567+
url = "super2",
568+
yanked = False,
569+
),
570+
struct(
571+
distribution = "foo",
572+
extra_pip_args = [],
573+
filename = "foo-0.0.1.tar.gz",
574+
requirement_line = "foo==0.0.3",
575+
sha256 = "5d15t",
576+
target_platforms = ["cp39_linux_x86_64", "cp39_osx_x86_64"],
577+
url = "sdist",
578+
yanked = False,
579+
),
580+
struct(
581+
distribution = "foo",
582+
extra_pip_args = [],
583+
filename = "foo-0.0.1-py3-none-macosx_14_0_x86_64.whl",
584+
requirement_line = "foo==0.0.3",
585+
sha256 = "deadb11f",
586+
target_platforms = ["cp39_osx_x86_64"],
587+
url = "super2",
588+
yanked = False,
589+
),
590+
],
591+
),
592+
])
593+
594+
_tests.append(_test_overlapping_shas_with_index_results)
595+
518596
def parse_requirements_test_suite(name):
519597
"""Create the test suite.
520598

0 commit comments

Comments
 (0)