Skip to content

Commit 76cde88

Browse files
authored
Merge branch 'main' into pip_compile_test_with_proxy
2 parents d0e2471 + 948fcec commit 76cde88

File tree

7 files changed

+155
-16
lines changed

7 files changed

+155
-16
lines changed

CHANGELOG.md

Lines changed: 4 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
* (pypi) `compile_pip_requirements` test rule works behind the proxy
100102

101103
{#v0-0-0-added}
@@ -110,6 +112,8 @@ END_UNRELEASED_TEMPLATE
110112
Set the `RULES_PYTHON_ENABLE_PIPSTAR=1` environment variable to enable it.
111113
* (utils) Add a way to run a REPL for any `rules_python` target that returns
112114
a `PyInfo` provider.
115+
* (uv) Handle `.netrc` and `auth_patterns` auth when downloading `uv`. Work towards
116+
[#1975](https://github.com/bazel-contrib/rules_python/issues/1975).
113117
* (toolchains) Arbitrary python-build-standalone runtimes can be registered
114118
and activated with custom flags. See the [Registering custom runtimes]
115119
docs and {obj}`single_version_platform_override()` API docs for more

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.

python/uv/private/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,15 @@ bzl_library(
6262
":toolchain_types_bzl",
6363
":uv_repository_bzl",
6464
":uv_toolchains_repo_bzl",
65+
"//python/private:auth_bzl",
6566
],
6667
)
6768

6869
bzl_library(
6970
name = "uv_repository_bzl",
7071
srcs = ["uv_repository.bzl"],
7172
visibility = ["//python/uv:__subpackages__"],
73+
deps = ["//python/private:auth_bzl"],
7274
)
7375

7476
bzl_library(

python/uv/private/uv.bzl

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ EXPERIMENTAL: This is experimental and may be removed without notice
1818
A module extension for working with uv.
1919
"""
2020

21+
load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
2122
load(":toolchain_types.bzl", "UV_TOOLCHAIN_TYPE")
2223
load(":uv_repository.bzl", "uv_repository")
2324
load(":uv_toolchains_repo.bzl", "uv_toolchains_repo")
@@ -77,7 +78,7 @@ The version of uv to configure the sources for. If this is not specified it will
7778
last version used in the module or the default version set by `rules_python`.
7879
""",
7980
),
80-
}
81+
} | AUTH_ATTRS
8182

8283
default = tag_class(
8384
doc = """\
@@ -133,7 +134,7 @@ for a particular version.
133134
},
134135
)
135136

136-
def _configure(config, *, platform, compatible_with, target_settings, urls = [], sha256 = "", override = False, **values):
137+
def _configure(config, *, platform, compatible_with, target_settings, auth_patterns, urls = [], sha256 = "", override = False, **values):
137138
"""Set the value in the config if the value is provided"""
138139
for key, value in values.items():
139140
if not value:
@@ -144,6 +145,7 @@ def _configure(config, *, platform, compatible_with, target_settings, urls = [],
144145

145146
config[key] = value
146147

148+
config.setdefault("auth_patterns", {}).update(auth_patterns)
147149
config.setdefault("platforms", {})
148150
if not platform:
149151
if compatible_with or target_settings or urls:
@@ -173,7 +175,8 @@ def process_modules(
173175
hub_name = "uv",
174176
uv_repository = uv_repository,
175177
toolchain_type = str(UV_TOOLCHAIN_TYPE),
176-
hub_repo = uv_toolchains_repo):
178+
hub_repo = uv_toolchains_repo,
179+
get_auth = get_auth):
177180
"""Parse the modules to get the config for 'uv' toolchains.
178181
179182
Args:
@@ -182,6 +185,7 @@ def process_modules(
182185
uv_repository: the rule to create a uv_repository override.
183186
toolchain_type: the toolchain type to use here.
184187
hub_repo: the hub repo factory function to use.
188+
get_auth: the auth function to use.
185189
186190
Returns:
187191
the result of the hub_repo. Mainly used for tests.
@@ -216,6 +220,8 @@ def process_modules(
216220
compatible_with = tag.compatible_with,
217221
target_settings = tag.target_settings,
218222
override = mod.is_root,
223+
netrc = tag.netrc,
224+
auth_patterns = tag.auth_patterns,
219225
)
220226

221227
for key in [
@@ -271,6 +277,8 @@ def process_modules(
271277
sha256 = tag.sha256,
272278
urls = tag.urls,
273279
override = mod.is_root,
280+
netrc = tag.netrc,
281+
auth_patterns = tag.auth_patterns,
274282
)
275283

276284
if not versions:
@@ -301,6 +309,11 @@ def process_modules(
301309
for platform, src in config.get("urls", {}).items()
302310
if src.urls
303311
}
312+
auth = {
313+
"auth_patterns": config.get("auth_patterns"),
314+
"netrc": config.get("netrc"),
315+
}
316+
auth = {k: v for k, v in auth.items() if v}
304317

305318
# Or fallback to fetching them from GH manifest file
306319
# Example file: https://github.com/astral-sh/uv/releases/download/0.6.3/dist-manifest.json
@@ -313,6 +326,8 @@ def process_modules(
313326
),
314327
manifest_filename = config["manifest_filename"],
315328
platforms = sorted(platforms),
329+
get_auth = get_auth,
330+
**auth
316331
)
317332

318333
for platform_name, platform in platforms.items():
@@ -327,6 +342,7 @@ def process_modules(
327342
platform = platform_name,
328343
urls = urls[platform_name].urls,
329344
sha256 = urls[platform_name].sha256,
345+
**auth
330346
)
331347

332348
toolchain_names.append(toolchain_name)
@@ -363,7 +379,7 @@ def _overlap(first_collection, second_collection):
363379

364380
return False
365381

366-
def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename, platforms):
382+
def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename, platforms, get_auth = get_auth, **auth_attrs):
367383
"""Download the results about remote tool sources.
368384
369385
This relies on the tools using the cargo packaging to infer the actual
@@ -431,10 +447,13 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename
431447
"aarch64-apple-darwin"
432448
]
433449
"""
450+
auth_attr = struct(**auth_attrs)
434451
dist_manifest = module_ctx.path(manifest_filename)
452+
urls = [base_url + "/" + manifest_filename]
435453
result = module_ctx.download(
436-
base_url + "/" + manifest_filename,
454+
url = urls,
437455
output = dist_manifest,
456+
auth = get_auth(module_ctx, urls, ctx_attr = auth_attr),
438457
)
439458
if not result.success:
440459
fail(result)
@@ -454,11 +473,13 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename
454473

455474
checksum_fname = checksum["name"]
456475
checksum_path = module_ctx.path(checksum_fname)
476+
urls = ["{}/{}".format(base_url, checksum_fname)]
457477
downloads[checksum_path] = struct(
458478
download = module_ctx.download(
459-
"{}/{}".format(base_url, checksum_fname),
479+
url = urls,
460480
output = checksum_path,
461481
block = False,
482+
auth = get_auth(module_ctx, urls, ctx_attr = auth_attr),
462483
),
463484
archive_fname = fname,
464485
platforms = checksum["target_triples"],
@@ -473,7 +494,7 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename
473494

474495
sha256, _, checksummed_fname = module_ctx.read(checksum_path).partition(" ")
475496
checksummed_fname = checksummed_fname.strip(" *\n")
476-
if archive_fname != checksummed_fname:
497+
if checksummed_fname and archive_fname != checksummed_fname:
477498
fail("The checksum is for a different file, expected '{}' but got '{}'".format(
478499
archive_fname,
479500
checksummed_fname,

python/uv/private/uv_repository.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ EXPERIMENTAL: This is experimental and may be removed without notice
1818
Create repositories for uv toolchain dependencies
1919
"""
2020

21+
load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
22+
2123
UV_BUILD_TMPL = """\
2224
# Generated by repositories.bzl
2325
load("@rules_python//python/uv:uv_toolchain.bzl", "uv_toolchain")
@@ -43,6 +45,7 @@ def _uv_repo_impl(repository_ctx):
4345
url = repository_ctx.attr.urls,
4446
sha256 = repository_ctx.attr.sha256,
4547
stripPrefix = strip_prefix,
48+
auth = get_auth(repository_ctx, repository_ctx.attr.urls),
4649
)
4750

4851
binary = "uv.exe" if is_windows else "uv"
@@ -70,5 +73,5 @@ uv_repository = repository_rule(
7073
"sha256": attr.string(mandatory = False),
7174
"urls": attr.string_list(mandatory = True),
7275
"version": attr.string(mandatory = True),
73-
},
76+
} | AUTH_ATTRS,
7477
)

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)