diff --git a/docs/api/rules_python/python/uv/bin/index.md b/docs/api/rules_python/python/uv/bin/index.md new file mode 100644 index 0000000000..cb3a914c87 --- /dev/null +++ b/docs/api/rules_python/python/uv/bin/index.md @@ -0,0 +1,24 @@ +:::{default-domain} bzl +::: +:::{bzl:currentfile} //python/uv/bin:BUILD.bazel +::: + +# //python/uv/bin + +Executable alias for the configured `uv` binary. This target can be run +directly with `bazel run` or passed to tools such as +[`bazel_env.bzl`](https://github.com/buildbuddy-io/bazel_env.bzl) (or via +{obj}`//python/uv:current_toolchain` and `$(UV)`). + +::::{bzl:target} uv + +Alias for {obj}`//python/uv:current_toolchain` to directly run the configured +`uv` binary: + +```sh +bazel run @rules_python//python/uv/bin:uv -- --help +``` + +:::{versionadded} VERSION_NEXT_FEATURE +::: +:::: diff --git a/docs/api/rules_python/python/uv/index.md b/docs/api/rules_python/python/uv/index.md new file mode 100644 index 0000000000..652368944c --- /dev/null +++ b/docs/api/rules_python/python/uv/index.md @@ -0,0 +1,45 @@ +:::{default-domain} bzl +::: +:::{bzl:currentfile} //python/uv:BUILD.bazel +::: + +# //python/uv + +::::{bzl:target} current_toolchain + +Exposes the resolved `uv` toolchain for the target/execution platform as an +executable target and template variable provider (`$(UV)` and `$(UV_BIN)`). +Shorter aliases for this target are also available at +{obj}`//python/uv/bin:uv` and `@uv//:uv`. + +Can be run directly with `bazel run`: + +```sh +bazel run @rules_python//python/uv:current_toolchain -- --help +``` + +Or integrated with +[`bazel_env.bzl`](https://github.com/buildbuddy-io/bazel_env.bzl) to expose +`uv` on your local `PATH`: + +```bzl +bazel_env( + name = "bazel_env", + toolchains = { + "uv": "@rules_python//python/uv:current_toolchain", + }, + tools = { + "uv": "$(UV)", + }, +) +``` + +:::{versionadded} VERSION_NEXT_FEATURE +::: +:::: + +:::{bzl:target} uv_toolchain_type + +Toolchain type identifier for the `uv` toolchain, implemented by +{obj}`uv_toolchain`. +::: diff --git a/docs/pypi/lock.md b/docs/pypi/lock.md index 727795c1ef..51b5b12e5d 100644 --- a/docs/pypi/lock.md +++ b/docs/pypi/lock.md @@ -94,6 +94,9 @@ uv.configure(version = "0.13.5") Without `version`, the default version specified by `rules_python` is used. See {obj}`uv.configure` for using a mirror, explicit URLs or extra platforms. +To run the configured `uv` binary directly, run +`bazel run @rules_python//python/uv/bin:uv -- ` (or add +`use_repo(uv, "uv")` and run `bazel run @uv//:uv -- `). (uv-workspace-setup)= ##### WORKSPACE diff --git a/news/uv-bin-target.added.md b/news/uv-bin-target.added.md new file mode 100644 index 0000000000..c3142dc0fe --- /dev/null +++ b/news/uv-bin-target.added.md @@ -0,0 +1,6 @@ +(uv) Added `@uv//:uv`, {obj}`//python/uv/bin:uv`, and public visibility on +{obj}`//python/uv:current_toolchain` (along with the `$(UV)` make variable) to +run the configured `uv` binary directly (e.g. +`bazel run @rules_python//python/uv/bin:uv -- --help` or +`bazel run @uv -- --help`) +([#4371](https://github.com/bazel-contrib/rules_python/pull/4371)). diff --git a/python/uv/BUILD.bazel b/python/uv/BUILD.bazel index 9551051bd2..e2024650bd 100644 --- a/python/uv/BUILD.bazel +++ b/python/uv/BUILD.bazel @@ -32,11 +32,7 @@ current_toolchain( # Marked manual so that `bazel test //...` passes # even if no toolchain is registered. tags = ["manual"], - # EXPERIMENTAL: Visibility is restricted to allow for changes. - visibility = [ - "//:__subpackages__", - "@rules_python//examples:__subpackages__", - ], + visibility = ["//visibility:public"], ) bzl_library( diff --git a/python/uv/bin/BUILD.bazel b/python/uv/bin/BUILD.bazel new file mode 100644 index 0000000000..e357c70545 --- /dev/null +++ b/python/uv/bin/BUILD.bazel @@ -0,0 +1,14 @@ +load("//python/private:distribution_filegroup.bzl", "distribution_filegroup") + +package(default_visibility = ["//:__subpackages__"]) + +distribution_filegroup( + name = "distribution", +) + +alias( + name = "uv", + actual = "//python/uv:current_toolchain", + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/python/uv/private/current_toolchain.bzl b/python/uv/private/current_toolchain.bzl index 91a25cb50f..43b3a30c55 100644 --- a/python/uv/private/current_toolchain.bzl +++ b/python/uv/private/current_toolchain.bzl @@ -32,16 +32,29 @@ def _current_toolchain_impl(ctx): original_uv_executable = toolchain_info.uv_toolchain_info.uv[DefaultInfo].files_to_run.executable # Use `uv` as the name of the binary to make the help message well formatted - symlink_uv_executable = ctx.actions.declare_file("current_toolchain/uv".format(original_uv_executable.basename)) - ctx.actions.symlink(output = symlink_uv_executable, target_file = original_uv_executable) + if original_uv_executable.extension: + ext = "." + original_uv_executable.extension + else: + ext = "" + symlink_uv_executable = ctx.actions.declare_file( + "{}/uv{}".format(ctx.label.name, ext), + ) + ctx.actions.symlink( + output = symlink_uv_executable, + target_file = original_uv_executable, + is_executable = True, + ) new_default_info = DefaultInfo( files = depset([symlink_uv_executable]), - runfiles = toolchain_info.default_info.default_runfiles, + runfiles = ctx.runfiles(files = [original_uv_executable]).merge( + toolchain_info.default_info.default_runfiles, + ), executable = symlink_uv_executable, ) template_variable_info = platform_common.TemplateVariableInfo({ + "UV": symlink_uv_executable.path, "UV_BIN": symlink_uv_executable.path, }) diff --git a/python/uv/private/uv.bzl b/python/uv/private/uv.bzl index 1ed9794316..32462c09fb 100644 --- a/python/uv/private/uv.bzl +++ b/python/uv/private/uv.bzl @@ -36,8 +36,21 @@ uv = use_extension( 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} VERSION_NEXT_FEATURE +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 = { diff --git a/python/uv/private/uv_register_toolchains.bzl b/python/uv/private/uv_register_toolchains.bzl index 1da301159e..558d6d4232 100644 --- a/python/uv/private/uv_register_toolchains.bzl +++ b/python/uv/private/uv_register_toolchains.bzl @@ -139,7 +139,8 @@ def uv_register_toolchains( This is the WORKSPACE equivalent of the {obj}`uv` module extension. It creates one download repository per `uv` version and platform, and the toolchain hub repository `name` with a `toolchain` for each of them, which - it registers as `@{name}//:all`; {obj}`lock` can then be used. See the + it registers as `@{name}//:all`; {obj}`lock` can then be used, and `uv` can + be run directly via `bazel run @{name}//:uv -- `. See the {ref}`WORKSPACE setup guide ` for usage examples. Platforms without an archive in a manifest get no toolchain. The diff --git a/python/uv/private/uv_toolchains_repo.bzl b/python/uv/private/uv_toolchains_repo.bzl index 7e11e0adb6..2e62ad9292 100644 --- a/python/uv/private/uv_toolchains_repo.bzl +++ b/python/uv/private/uv_toolchains_repo.bzl @@ -19,6 +19,12 @@ load("//python/private:text_util.bzl", "render") _TEMPLATE = """\ load("@rules_python//python/uv/private:toolchains_hub.bzl", "toolchains_hub") +alias( + name = "uv", + actual = "@rules_python//python/uv:current_toolchain", + visibility = ["//visibility:public"], +) + {} """ diff --git a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock index f86bf246f1..f2849f4594 100644 --- a/tests/integration/bzlmod_lockfile/MODULE.bazel.lock +++ b/tests/integration/bzlmod_lockfile/MODULE.bazel.lock @@ -259,7 +259,7 @@ }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "a11TI1DxtsKzJJnFDIAhAoVNhub4iqECM+h0qTqOT68=", + "bzlTransitiveDigest": "wQHhpqOkC72inE+2jHKImemj6oOnGwbZY66g+HM79aU=", "usagesDigest": "UgEeJTkNLce2eABymcbLBebdHpF0D8qrT6A6PSESotA=", "recordedInputs": [ "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", diff --git a/tests/uv/toolchain/BUILD.bazel b/tests/uv/toolchain/BUILD.bazel index 9017396ffe..21f6d2ac15 100644 --- a/tests/uv/toolchain/BUILD.bazel +++ b/tests/uv/toolchain/BUILD.bazel @@ -3,14 +3,24 @@ load("//python:py_test.bzl", "py_test") genrule( name = "uv_help", outs = ["uv_help.txt"], - cmd = "$(UV_BIN) --python-fetch manual --help >$@", + cmd = 'test "$(UV)" = "$(UV_BIN)" && $(UV) --python-fetch manual --help >$@', toolchains = ["//python/uv:current_toolchain"], ) py_test( name = "uv_help_test", srcs = ["uv_help_test.py"], - data = [":uv_help"], - env = {"DATA": "$(rlocationpath :uv_help)"}, + data = [ + ":uv_help", + "//python/uv:current_toolchain", + "//python/uv/bin:uv", + "@uv", + ], + env = { + "DATA": "$(rlocationpath :uv_help)", + "UV_BIN": "$(rlocationpath @uv//:uv)", + "UV_CURRENT_TOOLCHAIN": "$(rlocationpath //python/uv:current_toolchain)", + "UV_RULES_PYTHON_UV": "$(rlocationpath //python/uv/bin:uv)", + }, deps = ["//python/runfiles"], ) diff --git a/tests/uv/toolchain/uv_help_test.py b/tests/uv/toolchain/uv_help_test.py index c4515277f7..eed10e7470 100755 --- a/tests/uv/toolchain/uv_help_test.py +++ b/tests/uv/toolchain/uv_help_test.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os +import subprocess import unittest from pathlib import Path @@ -20,7 +21,27 @@ def test_uv_help(self): uv_help = Path(uv_help_path).read_text() - self.assertIn("Usage: uv [OPTIONS] ", uv_help) + self.assertRegex(uv_help, r"Usage: uv(\.exe)? \[OPTIONS\] ") + + def test_uv_bin(self): + root = runfiles.CreateOrRaise().root() + + for env_var in ( + "UV_BIN", + "UV_CURRENT_TOOLCHAIN", + "UV_RULES_PYTHON_UV", + ): + with self.subTest(env_var=env_var): + uv_bin_path = root / os.environ[env_var] + result = subprocess.run( + [uv_bin_path, "--python-fetch", "manual", "--help"], + capture_output=True, + text=True, + check=True, + ) + self.assertRegex( + result.stdout, r"Usage: uv(\.exe)? \[OPTIONS\] " + ) if __name__ == "__main__":