blob: 9919e21610d50cfc0af8fd2dc881ec7eaeb6e63a [file]
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A module extension for working with uv."""
load("//python/private:auth.bzl", "AUTH_ATTRS", "get_auth")
load(":dist_manifest.bzl", "DEFAULT_MANIFEST_FILENAME", "dist_manifest_resolver")
load(":toolchain_types.bzl", "UV_TOOLCHAIN_TYPE")
load(":uv_repository.bzl", "uv_repository")
load(":uv_toolchain_config.bzl", "apply_configure", "define_toolchains", "set_config")
load(":uv_toolchains_repo.bzl", "uv_toolchains_repo")
_DOC = """\
A module extension for working with uv.
Basic usage:
```starlark
uv = use_extension(
"@rules_python//python/uv:uv.bzl",
"uv",
# Use `dev_dependency` so that the toolchains are not defined pulled when
# your module is used elsewhere.
dev_dependency = True,
)
uv.configure(version = "0.13.5")
```
To run `uv` directly:
```sh
bazel run @rules_python//python/uv/bin:uv -- --help
# Or, if `use_repo(uv, "uv")` is in MODULE.bazel:
bazel run @uv//:uv -- --help
```
Since this is only for locking the requirements files, it should be always
marked as a `dev_dependency`.
:::{versionchanged} 2.5.0
Added `@uv//:uv` and {obj}`//python/uv/bin:uv` (aliases for
{obj}`//python/uv:current_toolchain`) to run the configured `uv` binary
directly.
:::
"""
_DEFAULT_ATTRS = {
"base_url": attr.string(
doc = """\
Base URL to download metadata about the binaries and the binaries themselves.
""",
),
"compatible_with": attr.label_list(
doc = """\
The compatible with constraint values for toolchain resolution.
""",
),
"manifest_filename": attr.string(
doc = """\
The distribution manifest filename to use for the metadata fetching from GH. The
defaults for this are set in `rules_python` MODULE.bazel file that one can override
for a specific version.
""",
default = DEFAULT_MANIFEST_FILENAME,
),
"platform": attr.string(
doc = """\
The platform string used in the UV repository to denote the platform triple.
""",
),
"target_settings": attr.label_list(
doc = """\
The `target_settings` to add to platform definitions that then get used in `toolchain`
definitions.
""",
),
"version": attr.string(
doc = """\
The version of uv to configure the sources for. If this is not specified it will be the
last version used in the module or the default version set by `rules_python`.
""",
),
} | AUTH_ATTRS
default = tag_class(
doc = """
Set the uv configuration defaults.
:::{versionchanged} 2.5.0
`netrc` and `auth_patterns` now apply to the versions configured with
{obj}`uv.configure`; previously they were accepted but ignored.
:::
""",
attrs = _DEFAULT_ATTRS,
)
configure = tag_class(
doc = """\
Build the `uv` toolchain configuration by appending the provided configuration.
The information is appended to the version configuration that is specified by
{attr}`version` attribute, or if the version is unspecified, the version of the
last {obj}`uv.configure` call in the current module, or the version from the
defaults is used.
Complex configuration example:
```starlark
# Configure the base_url for the default version.
uv.configure(base_url = "my_mirror")
# Add an extra platform that can be used with your version.
uv.configure(
platform = "extra-platform",
target_settings = ["//my_config_setting_label"],
compatible_with = ["@platforms//os:exotic"],
)
# Add an extra platform that can be used with your version.
uv.configure(
platform = "patched-binary",
target_settings = ["//my_super_config_setting"],
urls = ["https://example.zip"],
sha256 = "deadbeef",
)
```
""",
attrs = _DEFAULT_ATTRS | {
"sha256": attr.string(
doc = "The sha256 of the downloaded artifact if the {attr}`urls` is specified.",
),
"urls": attr.string_list(
doc = """\
The urls to download the binary from. If this is used, {attr}`base_url` and
{attr}`manifest_filename` are ignored for the given version.
::::note
If the `urls` are specified, they need to be specified for all of the platforms
for a particular version.
::::
""",
),
},
)
def process_modules(
module_ctx,
hub_name = "uv",
uv_repository = uv_repository,
toolchain_type = str(UV_TOOLCHAIN_TYPE),
hub_repo = uv_toolchains_repo,
get_auth = get_auth):
"""Parse the modules to get the config for 'uv' toolchains.
Args:
module_ctx: the context.
hub_name: the name of the hub repository.
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.
"""
# default values to apply for version specific config
defaults = {
"base_url": "",
"manifest_filename": "",
"platforms": {
# The structure is as follows:
# "platform_name": struct(
# compatible_with = [],
# target_settings = [],
# ),
#
# NOTE: urls and sha256 cannot be set in defaults
},
"version": "",
}
for mod in module_ctx.modules:
if not (mod.is_root or mod.name == "rules_python"):
continue
for tag in mod.tags.default:
set_config(
defaults,
version = tag.version,
base_url = tag.base_url,
manifest_filename = tag.manifest_filename,
platform = tag.platform,
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 [
"version",
"manifest_filename",
"platforms",
]:
if not defaults.get(key, None):
fail("defaults need to be set for '{}'".format(key))
# resolved per-version configuration; see `apply_configure`.
versions = {}
for mod in module_ctx.modules:
if not (mod.is_root or mod.name == "rules_python"):
continue
apply_configure(
versions,
mod.tags.configure,
defaults = defaults,
override = mod.is_root,
)
return define_toolchains(
hub_name = hub_name,
versions = versions,
uv_repository = uv_repository,
hub_repo = hub_repo,
toolchain_type = toolchain_type,
resolve_sources = dist_manifest_resolver(module_ctx, get_auth = get_auth),
)
def _uv_toolchain_extension(module_ctx):
process_modules(
module_ctx,
hub_name = "uv",
)
uv = module_extension(
doc = _DOC,
implementation = _uv_toolchain_extension,
tag_classes = {
"configure": configure,
"default": default,
},
)