From ba2de0ee21a3cc2ea95883c7b99e4f68b7827ba9 Mon Sep 17 00:00:00 2001 From: "Reid D. McKenzie" Date: Thu, 18 Dec 2025 10:16:23 -0700 Subject: [PATCH 1/2] feat(uv): Properly crossbuild native sdists --- uv/private/extension.bzl | 29 ++++++++++++++++++++- uv/private/sdist_build/build_helper.py | 17 ++++++++++--- uv/private/sdist_build/repository.bzl | 11 +++++--- uv/private/sdist_build/rule.bzl | 35 ++++++++++++++++++++++++++ uv/private/transitions.bzl | 17 +++++++++++++ uv/private/whl_install/rule.bzl | 4 +-- 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 uv/private/transitions.bzl diff --git a/uv/private/extension.bzl b/uv/private/extension.bzl index 0ed967e6..386884f5 100644 --- a/uv/private/extension.bzl +++ b/uv/private/extension.bzl @@ -265,10 +265,20 @@ def _parse_annotations(module_ctx, hub_specs, venv_specs): Dep = TypedDict({"name": str}) record( - per_package=Dict[str, List[Dep]], + per_package=Dict[str, Annotation], default_build_deps=List[Dep], ) + The annotations file is structured as follows: + + ``` + version = 0.0.0 + [[package]] + name = "foo" + native = true + build-dependencies = [ {name = "bar"} ] + ``` + Args: module_ctx (module_ctx): The Bazel module context hub_specs (dict): The previously parsed hub specs @@ -550,6 +560,22 @@ def _sbuild_repos(_module_ctx, lock_specs, annotation_specs, override_specs): it["name"]: it for it in build_deps + venv_anns.default_build_deps } + + # FIXME: This is placeholder code. We need to decide if the + # sdist we're building contains native extensions. For now we're + # relying on annotations to do that. It would be better for the + # sdist_build repo rule to inspect the sdist's contents and + # search for well known file types such as `pyx` and `cxx`. That + # would also make it easier to support for instance Rust + # extensions. + is_native = ( + # Cythonized code + "cython" in build_deps or + # Mypyc code + "mypy" in build_deps or + # User has annotated the package as using C extensions or such + venv_anns.per_package.get(package["name"], {}).get("native", False) + ) # print("Creating sdist repo", name) sdist_build( @@ -560,6 +586,7 @@ def _sbuild_repos(_module_ctx, lock_specs, annotation_specs, override_specs): "@" + _venv_target(hub_name, venv_name, package["name"]) for package in build_deps.values() ], + is_native = is_native, ) def _whl_install_repo_name(hub, venv, package): diff --git a/uv/private/sdist_build/build_helper.py b/uv/private/sdist_build/build_helper.py index d92c0cae..87e72b63 100644 --- a/uv/private/sdist_build/build_helper.py +++ b/uv/private/sdist_build/build_helper.py @@ -4,7 +4,7 @@ import shutil import sys from os import getenv, listdir, path -from subprocess import call +from subprocess import check_call # Under Bazel, the source dir of a sdist to build is immutable. `build` and # other tools however are constitutionally incapable of not writing to the @@ -19,6 +19,8 @@ PARSER = ArgumentParser() PARSER.add_argument("srcdir") PARSER.add_argument("outdir") +PARSER.add_argument("--validate-anyarch", action="store_true") +PARSER.add_argument("--sandbox", action="store_true") opts, args = PARSER.parse_known_args() t = getenv("TMPDIR") # Provided by Bazel @@ -29,7 +31,7 @@ outdir = path.abspath(opts.outdir) -call([ +check_call([ sys.executable, "-m", "build", "--wheel", @@ -37,4 +39,13 @@ "--outdir", outdir, ], cwd=t) -print(listdir(outdir), file=sys.stderr) +inventory = listdir(outdir) +print(inventory, file=sys.stderr) + +if len(inventory) > 1: + print("Error: Built more than one wheel!", file=sys.stderr) + exit(1) + +if opts.validate_anyarch and not inventory[0].endswith("-none-any.whl"): + print("Error: Target was anyarch but built a none-any wheel!") + exit(1) diff --git a/uv/private/sdist_build/repository.bzl b/uv/private/sdist_build/repository.bzl index 2f2845f9..2cf7cd6b 100644 --- a/uv/private/sdist_build/repository.bzl +++ b/uv/private/sdist_build/repository.bzl @@ -6,9 +6,9 @@ Consues a given src (.tar.gz or other artifact) and deps. Produces a sibling `rule.bzl` file for the implementation of `sdist_build`. """ -def _sdist_build_impl(repository_ctx): +def _sdist_build_impl(repository_ctx): repository_ctx.file("BUILD.bazel", content = """ -load("@aspect_rules_py//uv/private/sdist_build:rule.bzl", "sdist_build") +load("@aspect_rules_py//uv/private/sdist_build:rule.bzl", "{rule}") load("@aspect_rules_py//py/unstable:defs.bzl", "py_venv") py_venv( @@ -16,15 +16,19 @@ py_venv( deps = {deps}, ) -sdist_build( +{rule}( name = "whl", src = "{src}", venv = ":build_venv", + args = {args}, visibility = ["//visibility:public"], ) """.format( src = repository_ctx.attr.src, deps = repr([str(it) for it in repository_ctx.attr.deps]), + # FIXME: This should probably be inferred by looking at the inventory of the sdist + rule = "sdist_build" if not repository_ctx.attr.is_native else "sdist_native_build", + args = repr(["--validate-anyarch"] if not repository_ctx.attr.is_native else []), )) sdist_build = repository_rule( @@ -32,5 +36,6 @@ sdist_build = repository_rule( attrs = { "src": attr.label(), "deps": attr.label_list(), + "is_native": attr.bool(), }, ) diff --git a/uv/private/sdist_build/rule.bzl b/uv/private/sdist_build/rule.bzl index afb755d6..668eec62 100644 --- a/uv/private/sdist_build/rule.bzl +++ b/uv/private/sdist_build/rule.bzl @@ -6,10 +6,13 @@ load("//py/private/py_venv:types.bzl", "VirtualenvInfo") # buildifier: disable=bzl-visibility load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN") +load("//uv/private:transitions.bzl", "transition_to_target") + TAR_TOOLCHAIN = "@tar.bzl//tar/toolchain:type" # UV_TOOLCHAIN = "@multitool//tools/uv:toolchain_type" + def _sdist_build(ctx): py_toolchain = ctx.toolchains[PY_TOOLCHAIN].py3_runtime tar = ctx.toolchains[TAR_TOOLCHAIN] @@ -59,6 +62,7 @@ def _sdist_build(ctx): executable = venv[VirtualenvInfo].home.path + "/bin/python3", arguments = [ ctx.file._helper.path, + ] + ctx.attr.args + [ unpacked_sdist.path, wheel_dir.path, ], @@ -87,12 +91,43 @@ sdist_build = rule( Consumes a sdist artifact and performs a build of that artifact with the specified Python dependencies under the configured Python toochain. +""", + attrs = { + "src": attr.label(), + "venv": attr.label(), + "args": attr.string_list(), + "_helper": attr.label(allow_single_file = True, default = Label(":build_helper.py")), + }, + toolchains = [ + # TODO: Py toolchain needs to be in the `host` configuration, not the + # `exec` configuration. May need to split toolchains or use a different + # one here. Ditto for the other tools. + PY_TOOLCHAIN, + TAR_TOOLCHAIN, + # UV_TOOLCHAIN, + # FIXME: Add in a cc toolchain here + ], +) + + +sdist_native_build = rule( + implementation = _sdist_build, + doc = """Sdist to whl build rule _with native extensions_. + +Consumes a sdist artifact and performs a build of that artifact with the +specified Python dependencies under the configured Python toochain. + +Forces the exec platform to match the target platform so that we can safely +build packages with native code. + """, attrs = { "src": attr.label(doc = ""), "venv": attr.label(doc = ""), + "args": attr.string_list(), "_helper": attr.label(allow_single_file = True, default = Label(":build_helper.py")), }, + cfg = transition_to_target, toolchains = [ # TODO: Py toolchain needs to be in the `host` configuration, not the # `exec` configuration. May need to split toolchains or use a different diff --git a/uv/private/transitions.bzl b/uv/private/transitions.bzl new file mode 100644 index 00000000..22115b91 --- /dev/null +++ b/uv/private/transitions.bzl @@ -0,0 +1,17 @@ +# Dirty awful hack thanks Ed. +# https://github.com/bazel-contrib/rules_oci/pull/590/files + +def _transition_to_target_impl(settings, attr): + return { + # String conversion is needed to prevent a crash with Bazel 6.x. + "//command_line_option:extra_execution_platforms": [ + str(platform) + for platform in settings["//command_line_option:platforms"] + ], + } + +transition_to_target = transition( + implementation = _transition_to_target_impl, + inputs = ["//command_line_option:platforms"], + outputs = ["//command_line_option:extra_execution_platforms"], +) diff --git a/uv/private/whl_install/rule.bzl b/uv/private/whl_install/rule.bzl index 8e22bec0..a4798d54 100644 --- a/uv/private/whl_install/rule.bzl +++ b/uv/private/whl_install/rule.bzl @@ -1,5 +1,5 @@ -"" -"" +""" +""" load("@rules_python//python:defs.bzl", "PyInfo") load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN", "UNPACK_TOOLCHAIN") From ea1eda18d6f545deb165380d49e47abcaccc8c06 Mon Sep 17 00:00:00 2001 From: "aspect-marvin[bot]" Date: Thu, 18 Dec 2025 17:32:53 +0000 Subject: [PATCH 2/2] Pre-commit changes --- uv/private/extension.bzl | 2 +- uv/private/sdist_build/repository.bzl | 2 +- uv/private/sdist_build/rule.bzl | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/uv/private/extension.bzl b/uv/private/extension.bzl index 386884f5..45ec9481 100644 --- a/uv/private/extension.bzl +++ b/uv/private/extension.bzl @@ -560,7 +560,7 @@ def _sbuild_repos(_module_ctx, lock_specs, annotation_specs, override_specs): it["name"]: it for it in build_deps + venv_anns.default_build_deps } - + # FIXME: This is placeholder code. We need to decide if the # sdist we're building contains native extensions. For now we're # relying on annotations to do that. It would be better for the diff --git a/uv/private/sdist_build/repository.bzl b/uv/private/sdist_build/repository.bzl index 2cf7cd6b..9bf08ce2 100644 --- a/uv/private/sdist_build/repository.bzl +++ b/uv/private/sdist_build/repository.bzl @@ -6,7 +6,7 @@ Consues a given src (.tar.gz or other artifact) and deps. Produces a sibling `rule.bzl` file for the implementation of `sdist_build`. """ -def _sdist_build_impl(repository_ctx): +def _sdist_build_impl(repository_ctx): repository_ctx.file("BUILD.bazel", content = """ load("@aspect_rules_py//uv/private/sdist_build:rule.bzl", "{rule}") load("@aspect_rules_py//py/unstable:defs.bzl", "py_venv") diff --git a/uv/private/sdist_build/rule.bzl b/uv/private/sdist_build/rule.bzl index 668eec62..abac4bd1 100644 --- a/uv/private/sdist_build/rule.bzl +++ b/uv/private/sdist_build/rule.bzl @@ -8,11 +8,9 @@ load("//py/private/py_venv:types.bzl", "VirtualenvInfo") load("//py/private/toolchain:types.bzl", "PY_TOOLCHAIN") load("//uv/private:transitions.bzl", "transition_to_target") - TAR_TOOLCHAIN = "@tar.bzl//tar/toolchain:type" # UV_TOOLCHAIN = "@multitool//tools/uv:toolchain_type" - def _sdist_build(ctx): py_toolchain = ctx.toolchains[PY_TOOLCHAIN].py3_runtime tar = ctx.toolchains[TAR_TOOLCHAIN] @@ -109,7 +107,6 @@ specified Python dependencies under the configured Python toochain. ], ) - sdist_native_build = rule( implementation = _sdist_build, doc = """Sdist to whl build rule _with native extensions_.