diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel index e46989e9fd..3da17a6eb2 100644 --- a/examples/bzlmod/MODULE.bazel +++ b/examples/bzlmod/MODULE.bazel @@ -43,6 +43,13 @@ python.toolchain( # rules based on the `python_version` arg values. use_repo(python, "python_3_10", "python_3_9", "python_versions") +# EXPERIMENTAL: This is experimental and may be removed without notice +uv = use_extension("@rules_python//python/uv:extensions.bzl", "uv") +uv.toolchain(uv_version = "0.2.23") +use_repo(uv, "uv_toolchains") + +register_toolchains("@uv_toolchains//:all") + # This extension allows a user to create modifications to how rules_python # creates different wheel repositories. Different attributes allow the user # to modify the BUILD file, and copy files. diff --git a/python/BUILD.bazel b/python/BUILD.bazel index 7a69ac8051..e83b79c914 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -11,6 +11,7 @@ # 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. + """This package contains two sets of rules: 1) the "core" Python rules, which were historically bundled with Bazel and @@ -41,6 +42,7 @@ filegroup( "//python/pip_install:distribution", "//python/private:distribution", "//python/runfiles:distribution", + "//python/uv:distribution", ], visibility = ["//:__pkg__"], ) diff --git a/python/private/python.bzl b/python/private/python.bzl index 56566790d3..2791ae9e38 100644 --- a/python/private/python.bzl +++ b/python/private/python.bzl @@ -17,6 +17,7 @@ load("@bazel_features//:features.bzl", "bazel_features") load("//python:repositories.bzl", "python_register_toolchains") load(":pythons_hub.bzl", "hub_repo") +load(":text_util.bzl", "render") load(":toolchains_repo.bzl", "multi_toolchain_aliases") load(":util.bzl", "IS_BAZEL_6_4_OR_HIGHER") @@ -25,20 +26,6 @@ load(":util.bzl", "IS_BAZEL_6_4_OR_HIGHER") _MAX_NUM_TOOLCHAINS = 9999 _TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS)) -def _toolchain_prefix(index, name): - """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting. - - Examples: - _toolchain_prefix( 2, "foo") == "_0002_foo_" - _toolchain_prefix(2000, "foo") == "_2000_foo_" - """ - return "_{}_{}_".format(_left_pad_zero(index, _TOOLCHAIN_INDEX_PAD_LENGTH), name) - -def _left_pad_zero(index, length): - if index < 0: - fail("index must be non-negative") - return ("0" * length + str(index))[-length:] - # Printing a warning msg not debugging, so we have to disable # the buildifier check. # buildifier: disable=print @@ -202,7 +189,7 @@ def _python_impl(module_ctx): name = "pythons_hub", default_python_version = default_toolchain.python_version, toolchain_prefixes = [ - _toolchain_prefix(index, toolchain.name) + render.toolchain_prefix(index, toolchain.name, _TOOLCHAIN_INDEX_PAD_LENGTH) for index, toolchain in enumerate(toolchains) ], toolchain_python_versions = [t.python_version for t in toolchains], diff --git a/python/private/text_util.bzl b/python/private/text_util.bzl index 702a08e281..8a018e7969 100644 --- a/python/private/text_util.bzl +++ b/python/private/text_util.bzl @@ -99,11 +99,27 @@ def _render_tuple(items, *, value_repr = repr): ")", ]) +def _toolchain_prefix(index, name, pad_length): + """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting. + + Examples: + toolchain_prefix( 2, "foo", 4) == "_0002_foo_" + toolchain_prefix(2000, "foo", 4) == "_2000_foo_" + """ + return "_{}_{}_".format(_left_pad_zero(index, pad_length), name) + +def _left_pad_zero(index, length): + if index < 0: + fail("index must be non-negative") + return ("0" * length + str(index))[-length:] + render = struct( alias = _render_alias, dict = _render_dict, indent = _indent, + left_pad_zero = _left_pad_zero, list = _render_list, select = _render_select, tuple = _render_tuple, + toolchain_prefix = _toolchain_prefix, ) diff --git a/python/uv/BUILD.bazel b/python/uv/BUILD.bazel new file mode 100644 index 0000000000..3961c908ac --- /dev/null +++ b/python/uv/BUILD.bazel @@ -0,0 +1,78 @@ +# 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. + +# EXPERIMENTAL: This is experimental and may be removed without notice + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//python/uv/private:current_toolchain.bzl", "current_toolchain") + +package(default_visibility = ["//:__subpackages__"]) + +filegroup( + name = "distribution", + srcs = glob(["**"]) + [ + "//python/uv/private:distribution", + ], + visibility = ["//:__subpackages__"], +) + +# For stardoc to reference the files +exports_files(["defs.bzl"]) + +toolchain_type( + name = "uv_toolchain_type", + visibility = ["//visibility:public"], +) + +current_toolchain( + name = "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 = ["@rules_python//examples:__subpackages__"], +) + +bzl_library( + name = "defs", + srcs = ["defs.bzl"], + # EXPERIMENTAL: Visibility is restricted to allow for changes. + visibility = ["//:__subpackages__"], +) + +bzl_library( + name = "extensions", + srcs = ["extensions.bzl"], + # EXPERIMENTAL: Visibility is restricted to allow for changes. + visibility = ["//:__subpackages__"], + deps = [":repositories"], +) + +bzl_library( + name = "repositories", + srcs = ["repositories.bzl"], + # EXPERIMENTAL: Visibility is restricted to allow for changes. + visibility = ["//:__subpackages__"], + deps = [ + "//python/uv/private:toolchains_repo", + "//python/uv/private:versions", + ], +) + +bzl_library( + name = "toolchain", + srcs = ["toolchain.bzl"], + # EXPERIMENTAL: Visibility is restricted to allow for changes. + visibility = ["//:__subpackages__"], +) diff --git a/python/uv/defs.bzl b/python/uv/defs.bzl new file mode 100644 index 0000000000..20b426a355 --- /dev/null +++ b/python/uv/defs.bzl @@ -0,0 +1,23 @@ +# 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. + +""" +EXPERIMENTAL: This is experimental and may be removed without notice + +A toolchain for uv +""" + +load("//python/uv/private:providers.bzl", _UvToolchainInfo = "UvToolchainInfo") + +UvToolchainInfo = _UvToolchainInfo diff --git a/python/uv/extensions.bzl b/python/uv/extensions.bzl new file mode 100644 index 0000000000..82560eb17c --- /dev/null +++ b/python/uv/extensions.bzl @@ -0,0 +1,50 @@ +# 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. + +""" +EXPERIMENTAL: This is experimental and may be removed without notice + +A module extension for working with uv. +""" + +load("//python/uv:repositories.bzl", "uv_register_toolchains") + +_DOC = """\ +A module extension for working with uv. +""" + +uv_toolchain = tag_class(attrs = { + "uv_version": attr.string(doc = "Explicit version of uv.", mandatory = True), +}) + +def _uv_toolchain_extension(module_ctx): + for mod in module_ctx.modules: + for toolchain in mod.tags.toolchain: + if not mod.is_root: + fail( + "Only the root module may configure the uv toolchain.", + "This prevents conflicting registrations with any other modules.", + "NOTE: We may wish to enforce a policy where toolchain configuration is only allowed in the root module, or in rules_python. See https://github.com/bazelbuild/bazel/discussions/22024", + ) + + uv_register_toolchains( + uv_version = toolchain.uv_version, + register_toolchains = False, + ) + +uv = module_extension( + doc = _DOC, + implementation = _uv_toolchain_extension, + tag_classes = {"toolchain": uv_toolchain}, +) diff --git a/python/uv/private/BUILD.bazel b/python/uv/private/BUILD.bazel new file mode 100644 index 0000000000..56dab35757 --- /dev/null +++ b/python/uv/private/BUILD.bazel @@ -0,0 +1,45 @@ +# 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. + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +filegroup( + name = "distribution", + srcs = glob(["**"]), + visibility = ["//python/uv:__pkg__"], +) + +bzl_library( + name = "current_toolchain", + srcs = ["current_toolchain.bzl"], + visibility = ["//python/uv:__subpackages__"], +) + +bzl_library( + name = "toolchain_types", + srcs = ["toolchain_types.bzl"], + visibility = ["//python/uv:__subpackages__"], +) + +bzl_library( + name = "toolchains_repo", + srcs = ["toolchains_repo.bzl"], + visibility = ["//python/uv:__subpackages__"], +) + +bzl_library( + name = "versions", + srcs = ["versions.bzl"], + visibility = ["//python/uv:__subpackages__"], +) diff --git a/python/uv/private/current_toolchain.bzl b/python/uv/private/current_toolchain.bzl new file mode 100644 index 0000000000..cd4a5926d2 --- /dev/null +++ b/python/uv/private/current_toolchain.bzl @@ -0,0 +1,56 @@ +# 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. + +"""This module implements an alias rule to the resolved toolchain. +""" + +load("//python/uv/private:toolchain_types.bzl", "UV_TOOLCHAIN_TYPE") + +_DOC = """\ +Exposes a concrete toolchain which is the result of Bazel resolving the +toolchain for the execution or target platform. +Workaround for https://github.com/bazelbuild/bazel/issues/14009 +""" + +# Forward all the providers +def _current_toolchain_impl(ctx): + toolchain_info = ctx.toolchains[UV_TOOLCHAIN_TYPE] + + # Bazel requires executable rules to create the executable themselves, + # so we create a symlink in this rule so that it appears this rule created its executable. + original_uv_executable = toolchain_info.uv_toolchain_info.uv[DefaultInfo].files_to_run.executable + symlink_uv_executable = ctx.actions.declare_file("uv_symlink_{}".format(original_uv_executable.basename)) + ctx.actions.symlink(output = symlink_uv_executable, target_file = original_uv_executable) + + new_default_info = DefaultInfo( + files = depset([symlink_uv_executable]), + runfiles = toolchain_info.default_info.default_runfiles, + executable = symlink_uv_executable, + ) + + return [ + toolchain_info, + new_default_info, + toolchain_info.template_variable_info, + toolchain_info.uv_toolchain_info, + ] + +# Copied from java_toolchain_alias +# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl +current_toolchain = rule( + implementation = _current_toolchain_impl, + toolchains = [UV_TOOLCHAIN_TYPE], + doc = _DOC, + executable = True, +) diff --git a/python/uv/private/providers.bzl b/python/uv/private/providers.bzl new file mode 100644 index 0000000000..ac1ef310ea --- /dev/null +++ b/python/uv/private/providers.bzl @@ -0,0 +1,31 @@ +# 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. + +"""This module contains the definitions of all providers.""" + +UvToolchainInfo = provider( + doc = "Information about how to invoke the uv executable.", + fields = { + "uv": """ +:type: Target + +The uv binary `Target` +""", + "version": """ +:type: str + +The uv version +""", + }, +) diff --git a/python/uv/private/toolchain_types.bzl b/python/uv/private/toolchain_types.bzl new file mode 100644 index 0000000000..031e1ab0e0 --- /dev/null +++ b/python/uv/private/toolchain_types.bzl @@ -0,0 +1,22 @@ +# 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. + +"""Labels to identify toolchain types. + +This is a separate file because things needing the toolchain types (in +particular, toolchain() registrations) shouldn't need to load the entire +implementation of the toolchain. +""" + +UV_TOOLCHAIN_TYPE = Label("//python/uv:uv_toolchain_type") diff --git a/python/uv/private/toolchains_repo.bzl b/python/uv/private/toolchains_repo.bzl new file mode 100644 index 0000000000..12ae134c94 --- /dev/null +++ b/python/uv/private/toolchains_repo.bzl @@ -0,0 +1,50 @@ +# 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. + +"Creates a repository to hold toolchains" + +_TOOLCHAIN_TEMPLATE = """ +toolchain( + name = "{name}", + target_compatible_with = {compatible_with}, + toolchain = "{toolchain_label}", + toolchain_type = "{toolchain_type}", +) +""" + +def _toolchains_repo_impl(repository_ctx): + build_content = "" + for toolchain_name in repository_ctx.attr.toolchain_names: + toolchain_label = repository_ctx.attr.toolchain_labels[toolchain_name] + toolchain_compatible_with = repository_ctx.attr.toolchain_compatible_with[toolchain_name] + + build_content += _TOOLCHAIN_TEMPLATE.format( + name = toolchain_name, + toolchain_type = repository_ctx.attr.toolchain_type, + toolchain_label = toolchain_label, + compatible_with = json.encode(toolchain_compatible_with), + ) + + repository_ctx.file("BUILD.bazel", build_content) + +uv_toolchains_repo = repository_rule( + _toolchains_repo_impl, + doc = "Generates a toolchain hub repository", + attrs = { + "toolchain_compatible_with": attr.string_list_dict(doc = "A list of platform constraints for this toolchain, keyed by toolchain name.", mandatory = True), + "toolchain_labels": attr.string_dict(doc = "The name of the toolchain implementation target, keyed by toolchain name.", mandatory = True), + "toolchain_names": attr.string_list(doc = "List of toolchain names", mandatory = True), + "toolchain_type": attr.string(doc = "The toolchain type of the toolchains", mandatory = True), + }, +) diff --git a/python/uv/private/versions.bzl b/python/uv/private/versions.bzl new file mode 100644 index 0000000000..f7f0b9e0fe --- /dev/null +++ b/python/uv/private/versions.bzl @@ -0,0 +1,94 @@ +# 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. + +"""Version and integrity information for downloaded artifacts""" + +UV_PLATFORMS = { + "aarch64-apple-darwin": struct( + default_repo_name = "rules_python_uv_darwin_aarch64", + compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:aarch64", + ], + ), + "aarch64-unknown-linux-gnu": struct( + default_repo_name = "rules_python_uv_linux_aarch64", + compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], + ), + "powerpc64le-unknown-linux-gnu": struct( + default_repo_name = "rules_python_uv_linux_ppc", + compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:ppc", + ], + ), + "s390x-unknown-linux-gnu": struct( + default_repo_name = "rules_python_uv_linux_s390x", + compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:s390x", + ], + ), + "x86_64-apple-darwin": struct( + default_repo_name = "rules_python_uv_darwin_x86_64", + compatible_with = [ + "@platforms//os:macos", + "@platforms//cpu:x86_64", + ], + ), + "x86_64-pc-windows-msvc": struct( + default_repo_name = "rules_python_uv_windows_x86_64", + compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], + ), + "x86_64-unknown-linux-gnu": struct( + default_repo_name = "rules_python_uv_linux_x86_64", + compatible_with = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], + ), +} + +# From: https://github.com/astral-sh/uv/releases +UV_TOOL_VERSIONS = { + "0.2.23": { + "aarch64-apple-darwin": struct( + sha256 = "1d41beb151ace9621a0e729d661cfb04d6375bffdaaf0e366d1653576ce3a687", + ), + "aarch64-unknown-linux-gnu": struct( + sha256 = "c35042255239b75d29b9fd4b0845894b91284ed3ff90c2595d0518b4c8902329", + ), + "powerpc64le-unknown-linux-gnu": struct( + sha256 = "ca16c9456d297e623164e3089d76259c6d70ac40c037dd2068accc3bb1b09d5e", + ), + "s390x-unknown-linux-gnu": struct( + sha256 = "55f8c2aa089f382645fce9eed3ee002f2cd48de4696568e7fd63105a02da568c", + ), + "x86_64-apple-darwin": struct( + sha256 = "960d2ae6ec31bcf5da3f66083dedc527712115b97ee43eae903d74a43874fa72", + ), + "x86_64-pc-windows-msvc": struct( + sha256 = "66f80537301c686a801b91468a43dbeb0881bd6d51857078c24f29e5dca8ecf1", + ), + "x86_64-unknown-linux-gnu": struct( + sha256 = "4384db514959beb4de1dcdf7f1f2d5faf664f7180820b0e7a521ef2147e33d1d", + ), + }, +} diff --git a/python/uv/repositories.bzl b/python/uv/repositories.bzl new file mode 100644 index 0000000000..7f27e8bdc5 --- /dev/null +++ b/python/uv/repositories.bzl @@ -0,0 +1,122 @@ +# 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. + +""" +EXPERIMENTAL: This is experimental and may be removed without notice + +Create repositories for uv toolchain dependencies +""" + +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +load("//python/uv/private:toolchain_types.bzl", "UV_TOOLCHAIN_TYPE") +load("//python/uv/private:toolchains_repo.bzl", "uv_toolchains_repo") +load("//python/uv/private:versions.bzl", "UV_PLATFORMS", "UV_TOOL_VERSIONS") + +UV_BUILD_TMPL = """\ +# Generated by repositories.bzl +load("@rules_python//python/uv:toolchain.bzl", "uv_toolchain") + +uv_toolchain( + name = "uv_toolchain", + uv = "{binary}", + version = "{version}", +) +""" + +def _uv_repo_impl(repository_ctx): + platform = repository_ctx.attr.platform + uv_version = repository_ctx.attr.uv_version + + is_windows = "windows" in platform + + suffix = ".zip" if is_windows else ".tar.gz" + filename = "uv-{platform}{suffix}".format( + platform = platform, + suffix = suffix, + ) + url = "https://github.com/astral-sh/uv/releases/download/{version}/{filename}".format( + version = uv_version, + filename = filename, + ) + if filename.endswith(".tar.gz"): + strip_prefix = filename[:-len(".tar.gz")] + else: + strip_prefix = "" + + repository_ctx.download_and_extract( + url = url, + sha256 = UV_TOOL_VERSIONS[repository_ctx.attr.uv_version][repository_ctx.attr.platform].sha256, + stripPrefix = strip_prefix, + ) + + binary = "uv.exe" if is_windows else "uv" + repository_ctx.file( + "BUILD.bazel", + UV_BUILD_TMPL.format( + binary = binary, + version = uv_version, + ), + ) + +uv_repository = repository_rule( + _uv_repo_impl, + doc = "Fetch external tools needed for uv toolchain", + attrs = { + "platform": attr.string(mandatory = True, values = UV_PLATFORMS.keys()), + "uv_version": attr.string(mandatory = True, values = UV_TOOL_VERSIONS.keys()), + }, +) + +# buildifier: disable=unnamed-macro +def uv_register_toolchains(uv_version = None, register_toolchains = True): + """Convenience macro which does typical toolchain setup + + Skip this macro if you need more control over the toolchain setup. + + Args: + uv_version: The uv toolchain version to download. + register_toolchains: If true, repositories will be generated to produce and register `uv_toolchain` targets. + """ + if not uv_version: + fail("uv_version is required") + + toolchain_names = [] + toolchain_labels_by_toolchain = {} + toolchain_compatible_with_by_toolchain = {} + + for platform in UV_PLATFORMS.keys(): + uv_repository_name = UV_PLATFORMS[platform].default_repo_name + + maybe( + uv_repository, + name = uv_repository_name, + uv_version = uv_version, + platform = platform, + ) + + toolchain_name = uv_repository_name + "_toolchain" + toolchain_names.append(toolchain_name) + toolchain_labels_by_toolchain[toolchain_name] = "@{}//:uv_toolchain".format(uv_repository_name) + toolchain_compatible_with_by_toolchain[toolchain_name] = UV_PLATFORMS[platform].compatible_with + + uv_toolchains_repo( + name = "uv_toolchains", + toolchain_type = str(UV_TOOLCHAIN_TYPE), + toolchain_names = toolchain_names, + toolchain_labels = toolchain_labels_by_toolchain, + toolchain_compatible_with = toolchain_compatible_with_by_toolchain, + ) + + if register_toolchains: + native.register_toolchains("@uv_toolchains//:all") diff --git a/python/uv/toolchain.bzl b/python/uv/toolchain.bzl new file mode 100644 index 0000000000..dbfda0b70c --- /dev/null +++ b/python/uv/toolchain.bzl @@ -0,0 +1,64 @@ +# 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. + +""" +EXPERIMENTAL: This is experimental and may be removed without notice + +This module implements the uv toolchain rule +""" + +load("//python/uv/private:providers.bzl", "UvToolchainInfo") + +def _uv_toolchain_impl(ctx): + uv = ctx.attr.uv + + default_info = DefaultInfo( + files = uv.files, + runfiles = uv[DefaultInfo].default_runfiles, + ) + uv_toolchain_info = UvToolchainInfo( + uv = uv, + version = ctx.attr.version, + ) + template_variable_info = platform_common.TemplateVariableInfo({ + "UV_BIN": uv[DefaultInfo].files_to_run.executable.path, + }) + + # Export all the providers inside our ToolchainInfo + # so the current_toolchain rule can grab and re-export them. + toolchain_info = platform_common.ToolchainInfo( + default_info = default_info, + template_variable_info = template_variable_info, + uv_toolchain_info = uv_toolchain_info, + ) + return [ + default_info, + toolchain_info, + template_variable_info, + ] + +uv_toolchain = rule( + implementation = _uv_toolchain_impl, + attrs = { + "uv": attr.label( + doc = "A static uv binary.", + mandatory = True, + allow_single_file = True, + executable = True, + cfg = "target", + ), + "version": attr.string(mandatory = True, doc = "Version of the uv binary."), + }, + doc = "Defines a uv toolchain.", +)