Skip to content

Commit 3871306

Browse files
authored
fix(pypi): pass the correct versions to get_index_urls and fix cache invalidation (#3758)
Fix the versions of packages that we are recording to a `MODULE.bazel.lock` file facts by passing all of the versions and packages to the `get_index` function. Summary: - Parse ALL requirements files (not just platform-matched), pass all versions to get_index so lockfile facts are platform-independent. - Return None when versions mismatch (re-fetch), drop removed packages so you can start immediately. - Include files with no matching platforms so packages aren't lost - Add bzlmod lockfile integration test - Update CI to run new test in bazel-in-bazel subset - Bump Bazel 9.0.0rc1 → 9.1.0 in bazel-in-bazel tests Fixes #3756
1 parent 9dc505b commit 3871306

21 files changed

Lines changed: 1150 additions & 11 deletions

.bazelci/presubmit.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,17 +472,17 @@ tasks:
472472
<<: *common_bazelinbazel_config
473473
name: "tests/integration bazel-in-bazel: macOS (subset)"
474474
platform: macos_arm64
475-
build_targets: ["//tests/integration:local_toolchains_test_bazel_self"]
476-
test_targets: ["//tests/integration:local_toolchains_test_bazel_self"]
475+
build_targets: ["//tests/integration:subset"]
476+
test_targets: ["//tests/integration:subset"]
477477
# The bazelinbazel tests were disabled on Windows to save CI jobs slots, and
478478
# have bitrotted a bit. For now, just run a subset of what we're most
479479
# interested in.
480480
integration_test_bazelinbazel_windows:
481481
<<: *common_bazelinbazel_config
482482
name: "tests/integration bazel-in-bazel: Windows (subset)"
483483
platform: windows
484-
build_targets: ["//tests/integration:local_toolchains_test_bazel_self"]
485-
test_targets: ["//tests/integration:local_toolchains_test_bazel_self"]
484+
build_targets: ["//tests/integration:subset"]
485+
test_targets: ["//tests/integration:subset"]
486486

487487
integration_test_compile_pip_requirements_ubuntu:
488488
<<: *reusable_build_test_all

.bazelignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ gazelle/examples/bzlmod_build_file_generation/bazel-bzlmod_build_file_generation
3030
gazelle/examples/bzlmod_build_file_generation/bazel-out
3131
gazelle/examples/bzlmod_build_file_generation/bazel-testlog
3232
sphinxdocs
33+
tests/integration/bzlmod_lockfile/bazel-bzlmod_lockfile
3334
tests/integration/compile_pip_requirements/bazel-compile_pip_requirements
3435
tests/integration/local_toolchains/bazel-local_toolchains
3536
tests/integration/py_cc_toolchain_registered/bazel-py_cc_toolchain_registered

.bazelrc.deleted_packages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ common --deleted_packages=gazelle/modules_mapping
2929
common --deleted_packages=gazelle/python
3030
common --deleted_packages=gazelle/pythonconfig
3131
common --deleted_packages=gazelle/python/private
32+
common --deleted_packages=tests/integration/bzlmod_lockfile
3233
common --deleted_packages=tests/integration/compile_pip_requirements
3334
common --deleted_packages=tests/integration/compile_pip_requirements_test_from_external_repo
3435
common --deleted_packages=tests/integration/custom_commands

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ END_UNRELEASED_TEMPLATE
104104
[20260325]: https://github.com/astral-sh/python-build-standalone/releases/tag/20260325
105105
[20260414]: https://github.com/astral-sh/python-build-standalone/releases/tag/20260414
106106

107+
{#v2-0-1}
108+
## [2.0.1] - 2026-05-08
109+
110+
[2.0.1]: https://github.com/bazel-contrib/rules_python/releases/tag/2.0.1
111+
112+
{#v2-0-1-fixed}
113+
### Fixed
114+
115+
* (pypi) Fix the versions of packages that we are recording to a `MODULE.bazel.lock` file
116+
facts by passing all of the versions to the `get_index` function.
117+
Fixes [#3756](https://github.com/bazel-contrib/rules_python/issues/3756).
118+
107119
{#v2-0-0}
108120
## [2.0.0] - 2026-04-09
109121

MODULE.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ bazel_binaries.local(
226226
)
227227
bazel_binaries.download(version = "7.7.0")
228228
bazel_binaries.download(version = "8.5.1")
229-
bazel_binaries.download(version = "9.0.0rc1")
229+
bazel_binaries.download(version = "9.1.0")
230230
use_repo(
231231
bazel_binaries,
232232
"bazel_binaries",
@@ -235,7 +235,7 @@ use_repo(
235235
"bazel_binaries_bazelisk",
236236
"build_bazel_bazel_7_7_0",
237237
"build_bazel_bazel_8_5_1",
238-
"build_bazel_bazel_9_0_0rc1",
238+
"build_bazel_bazel_9_1_0",
239239
# "build_bazel_bazel_rolling",
240240
"build_bazel_bazel_self",
241241
)

python/private/pypi/parse_requirements.bzl

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def parse_requirements(
8989
evaluate_markers = evaluate_markers or (lambda _requirements: {})
9090
options = {}
9191
requirements = {}
92+
all_files_parsed = {}
9293
reqs_with_env_markers = {}
9394
index_url = None
9495
extra_index_urls = []
@@ -100,6 +101,13 @@ def parse_requirements(
100101
# needed for the whl_library declarations later.
101102
parse_result = parse_requirements_txt(contents)
102103

104+
# Save parsed results from ALL files, even those with no matching
105+
# platforms. This ensures the distributions dict (used for index URL
106+
# queries) includes packages from all platform files, making the
107+
# lockfile facts platform-independent.
108+
if file not in all_files_parsed:
109+
all_files_parsed[file] = parse_result.requirements
110+
103111
tokenized_options = []
104112
for opt in parse_result.options:
105113
for p in opt.split(" "):
@@ -130,6 +138,10 @@ def parse_requirements(
130138

131139
# This may call to Python, so execute it early (before calling to the
132140
# internet below) and ensure that we call it only once.
141+
#
142+
# TODO @aignas 2026-05-10: remove this assumption in the code because we
143+
# are always using pipstar, so we can do the marker evaluation when we are
144+
# parsing the files.
133145
resolved_marker_platforms = evaluate_markers(reqs_with_env_markers)
134146
logger.trace(lambda: "Evaluated env markers from:\n{}\n\nTo:\n{}".format(
135147
reqs_with_env_markers,
@@ -185,13 +197,21 @@ def parse_requirements(
185197

186198
index_urls = {}
187199
if get_index_urls:
200+
# Collect all distributions from all requirements files irrespective
201+
# of python_version and platform markers. This ensures that the index
202+
# is queried for all packages, not just those matching the current
203+
# platform's markers.
188204
distributions = {}
189-
for reqs in requirements_by_platform.values():
190-
for req in reqs.values():
191-
if req.srcs.url:
205+
for entries in all_files_parsed.values():
206+
for entry in entries:
207+
name, req_line = entry
208+
srcs = index_sources(req_line)
209+
if srcs.url:
192210
continue
211+
versions = distributions.setdefault(normalize_name(name), {})
212+
versions[srcs.version] = None
193213

194-
distributions.setdefault(req.distribution, []).append(req.srcs.version)
214+
distributions = {k: sorted(v.keys()) for k, v in distributions.items()}
195215

196216
index_urls = get_index_urls(
197217
ctx,

python/private/pypi/pypi_cache.bzl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,15 @@ def _get_from_facts(facts, known_facts, index_url, requested_versions, facts_ver
212212
requested_versions = requested_versions,
213213
)
214214
if result:
215+
if len(result) != len(requested_versions):
216+
# If the results are incomplete, return None, so that we can
217+
# fetch sources from the internet again.
218+
return None
219+
220+
# Only persist the accessed (requested) packages. Packages that
221+
# exist in known_facts but are not in requested_versions (e.g.
222+
# removed from all requirements files) are dropped from the
223+
# computed facts so they get cleaned up from the lockfile.
215224
_store_facts(facts, facts_version, index_url, result)
216225
return result
217226

python/private/pypi/requirements_files_by_platform.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ def requirements_files_by_platform(
213213
default_platforms,
214214
input_platforms,
215215
))
216-
continue
217216

218217
if logger:
219218
logger.debug(lambda: "Configured platforms for file {} are {}".format(file, plats))
@@ -238,4 +237,8 @@ def requirements_files_by_platform(
238237
for plat, file in requirements.items():
239238
ret.setdefault(file, []).append(_platform(plat, python_version = python_version))
240239

240+
for file, _plats in files_by_platform:
241+
if file not in ret and _plats != None:
242+
ret[file] = []
243+
241244
return ret

tests/integration/BUILD.bazel

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ default_test_runner(
3737
visibility = ["//visibility:public"],
3838
)
3939

40+
rules_python_integration_test(
41+
name = "bzlmod_lockfile_test",
42+
bazel_versions = ["9.1.0"],
43+
)
44+
45+
test_suite(
46+
name = "subset",
47+
tags = ["manual"],
48+
tests = [
49+
"bzlmod_lockfile_test_bazel_9.1.0",
50+
"local_toolchains_test_bazel_self",
51+
],
52+
)
53+
4054
rules_python_integration_test(
4155
name = "compile_pip_requirements_test",
4256
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Bazel configuration flags
2+
3+
build --enable_runfiles
4+
common --lockfile_mode=error
5+
6+
common --experimental_isolated_extension_usages
7+
8+
# https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file
9+
try-import %workspace%/user.bazelrc

0 commit comments

Comments
 (0)