Skip to content

feat(uv): handle credential helpers and .netrc #2872

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ END_UNRELEASED_TEMPLATE
Set the `RULES_PYTHON_ENABLE_PIPSTAR=1` environment variable to enable it.
* (utils) Add a way to run a REPL for any `rules_python` target that returns
a `PyInfo` provider.
* (uv) Handle `.netrc` and `auth_patterns` auth when downloading `uv`. Work towards
[#1975](https://github.com/bazel-contrib/rules_python/issues/1975).

{#v0-0-0-removed}
### Removed
Expand Down
2 changes: 2 additions & 0 deletions python/uv/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ bzl_library(
":toolchain_types_bzl",
":uv_repository_bzl",
":uv_toolchains_repo_bzl",
"//python/private:auth_bzl",
],
)

bzl_library(
name = "uv_repository_bzl",
srcs = ["uv_repository.bzl"],
visibility = ["//python/uv:__subpackages__"],
deps = ["//python/private:auth_bzl"],
)

bzl_library(
Expand Down
35 changes: 28 additions & 7 deletions python/uv/private/uv.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ EXPERIMENTAL: This is experimental and may be removed without notice
A module extension for working with uv.
"""

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

default = tag_class(
doc = """\
Expand Down Expand Up @@ -133,7 +134,7 @@ for a particular version.
},
)

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

config[key] = value

config.setdefault("auth_patterns", {}).update(auth_patterns)
config.setdefault("platforms", {})
if not platform:
if compatible_with or target_settings or urls:
Expand Down Expand Up @@ -173,7 +175,8 @@ def process_modules(
hub_name = "uv",
uv_repository = uv_repository,
toolchain_type = str(UV_TOOLCHAIN_TYPE),
hub_repo = uv_toolchains_repo):
hub_repo = uv_toolchains_repo,
get_auth = get_auth):
"""Parse the modules to get the config for 'uv' toolchains.

Args:
Expand All @@ -182,6 +185,7 @@ def process_modules(
uv_repository: the rule to create a uv_repository override.
toolchain_type: the toolchain type to use here.
hub_repo: the hub repo factory function to use.
get_auth: the auth function to use.

Returns:
the result of the hub_repo. Mainly used for tests.
Expand Down Expand Up @@ -216,6 +220,8 @@ def process_modules(
compatible_with = tag.compatible_with,
target_settings = tag.target_settings,
override = mod.is_root,
netrc = tag.netrc,
auth_patterns = tag.auth_patterns,
)

for key in [
Expand Down Expand Up @@ -271,6 +277,8 @@ def process_modules(
sha256 = tag.sha256,
urls = tag.urls,
override = mod.is_root,
netrc = tag.netrc,
auth_patterns = tag.auth_patterns,
)

if not versions:
Expand Down Expand Up @@ -301,6 +309,11 @@ def process_modules(
for platform, src in config.get("urls", {}).items()
if src.urls
}
auth = {
"auth_patterns": config.get("auth_patterns"),
"netrc": config.get("netrc"),
}
auth = {k: v for k, v in auth.items() if v}

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

for platform_name, platform in platforms.items():
Expand All @@ -327,6 +342,7 @@ def process_modules(
platform = platform_name,
urls = urls[platform_name].urls,
sha256 = urls[platform_name].sha256,
**auth
)

toolchain_names.append(toolchain_name)
Expand Down Expand Up @@ -363,7 +379,7 @@ def _overlap(first_collection, second_collection):

return False

def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename, platforms):
def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename, platforms, get_auth = get_auth, **auth_attrs):
"""Download the results about remote tool sources.

This relies on the tools using the cargo packaging to infer the actual
Expand Down Expand Up @@ -431,10 +447,13 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename
"aarch64-apple-darwin"
]
"""
auth_attr = struct(**auth_attrs)
dist_manifest = module_ctx.path(manifest_filename)
urls = [base_url + "/" + manifest_filename]
result = module_ctx.download(
base_url + "/" + manifest_filename,
url = urls,
output = dist_manifest,
auth = get_auth(module_ctx, urls, ctx_attr = auth_attr),
)
if not result.success:
fail(result)
Expand All @@ -454,11 +473,13 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename

checksum_fname = checksum["name"]
checksum_path = module_ctx.path(checksum_fname)
urls = ["{}/{}".format(base_url, checksum_fname)]
downloads[checksum_path] = struct(
download = module_ctx.download(
"{}/{}".format(base_url, checksum_fname),
url = urls,
output = checksum_path,
block = False,
auth = get_auth(module_ctx, urls, ctx_attr = auth_attr),
),
archive_fname = fname,
platforms = checksum["target_triples"],
Expand All @@ -473,7 +494,7 @@ def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url, manifest_filename

sha256, _, checksummed_fname = module_ctx.read(checksum_path).partition(" ")
checksummed_fname = checksummed_fname.strip(" *\n")
if archive_fname != checksummed_fname:
if checksummed_fname and archive_fname != checksummed_fname:
fail("The checksum is for a different file, expected '{}' but got '{}'".format(
archive_fname,
checksummed_fname,
Expand Down
5 changes: 4 additions & 1 deletion python/uv/private/uv_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ EXPERIMENTAL: This is experimental and may be removed without notice
Create repositories for uv toolchain dependencies
"""

load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")

UV_BUILD_TMPL = """\
# Generated by repositories.bzl
load("@rules_python//python/uv:uv_toolchain.bzl", "uv_toolchain")
Expand All @@ -43,6 +45,7 @@ def _uv_repo_impl(repository_ctx):
url = repository_ctx.attr.urls,
sha256 = repository_ctx.attr.sha256,
stripPrefix = strip_prefix,
auth = get_auth(repository_ctx, repository_ctx.attr.urls),
)

binary = "uv.exe" if is_windows else "uv"
Expand Down Expand Up @@ -70,5 +73,5 @@ uv_repository = repository_rule(
"sha256": attr.string(mandatory = False),
"urls": attr.string_list(mandatory = True),
"version": attr.string(mandatory = True),
},
} | AUTH_ATTRS,
)
23 changes: 22 additions & 1 deletion tests/uv/uv/uv_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _mod(*, name = None, default = [], configure = [], is_root = True):
)

def _process_modules(env, **kwargs):
result = process_modules(hub_repo = struct, **kwargs)
result = process_modules(hub_repo = struct, get_auth = lambda *_, **__: None, **kwargs)

return env.expect.that_struct(
struct(
Expand All @@ -124,6 +124,8 @@ def _default(
platform = None,
target_settings = None,
version = None,
netrc = None,
auth_patterns = None,
**kwargs):
return struct(
base_url = base_url,
Expand All @@ -132,6 +134,8 @@ def _default(
platform = platform,
target_settings = [] + (target_settings or []), # ensure that the type is correct
version = version,
netrc = netrc,
auth_patterns = {} | (auth_patterns or {}), # ensure that the type is correct
**kwargs
)

Expand Down Expand Up @@ -377,6 +381,11 @@ def _test_complex_configuring(env):
platform = "linux",
compatible_with = ["@platforms//os:linux"],
),
_configure(
version = "1.0.4",
netrc = "~/.my_netrc",
auth_patterns = {"foo": "bar"},
), # use auth
],
),
),
Expand All @@ -388,18 +397,21 @@ def _test_complex_configuring(env):
"1_0_1_osx",
"1_0_2_osx",
"1_0_3_linux",
"1_0_4_osx",
])
uv.implementations().contains_exactly({
"1_0_0_osx": "@uv_1_0_0_osx//:uv_toolchain",
"1_0_1_osx": "@uv_1_0_1_osx//:uv_toolchain",
"1_0_2_osx": "@uv_1_0_2_osx//:uv_toolchain",
"1_0_3_linux": "@uv_1_0_3_linux//:uv_toolchain",
"1_0_4_osx": "@uv_1_0_4_osx//:uv_toolchain",
})
uv.compatible_with().contains_exactly({
"1_0_0_osx": ["@platforms//os:os"],
"1_0_1_osx": ["@platforms//os:os"],
"1_0_2_osx": ["@platforms//os:different"],
"1_0_3_linux": ["@platforms//os:linux"],
"1_0_4_osx": ["@platforms//os:os"],
})
uv.target_settings().contains_exactly({})
env.expect.that_collection(calls).contains_exactly([
Expand Down Expand Up @@ -431,6 +443,15 @@ def _test_complex_configuring(env):
"urls": ["https://example.org/1.0.3/linux"],
"version": "1.0.3",
},
{
"auth_patterns": {"foo": "bar"},
"name": "uv_1_0_4_osx",
"netrc": "~/.my_netrc",
"platform": "osx",
"sha256": "deadb00f",
"urls": ["https://example.org/1.0.4/osx"],
"version": "1.0.4",
},
])

_tests.append(_test_complex_configuring)
Expand Down