diff --git a/uv/private/extension.bzl b/uv/private/extension.bzl index 0ed967e6..45ec9481 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 @@ -551,6 +561,22 @@ def _sbuild_repos(_module_ctx, lock_specs, annotation_specs, override_specs): 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( name = name, @@ -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..9bf08ce2 100644 --- a/uv/private/sdist_build/repository.bzl +++ b/uv/private/sdist_build/repository.bzl @@ -8,7 +8,7 @@ sibling `rule.bzl` file for the implementation of `sdist_build`. 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..abac4bd1 100644 --- a/uv/private/sdist_build/rule.bzl +++ b/uv/private/sdist_build/rule.bzl @@ -6,6 +6,7 @@ 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" @@ -59,6 +60,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 +89,42 @@ 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")