From e871d944626347b13d74370c7455adadf6ba5bb3 Mon Sep 17 00:00:00 2001 From: Ricardo Delfin Date: Mon, 6 Jul 2026 18:11:02 +0100 Subject: [PATCH] fix(packaging): build the .publish twine binary for the exec platform The .publish target generated by py_wheel wraps twine in a native_binary whose src attribute uses cfg = "target". When the build uses a cross-compilation platform (e.g. --platforms=linux_aarch64), this causes bazel to resolve twine for the target platform rather than the host, which fails because rules_python_publish_deps only provides wheels for the host platform. Twine is a developer tool that uploads wheels to PyPI; it only ever runs on the developer's workstation and should always be built for the exec platform regardless of the target. Replace the native_binary call with a minimal inline rule, _py_wheel_publish_binary, that is identical except its src attribute uses cfg = "exec". --- python/packaging.bzl | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/python/packaging.bzl b/python/packaging.bzl index 537aa6090f..569bf3f401 100644 --- a/python/packaging.bzl +++ b/python/packaging.bzl @@ -14,7 +14,6 @@ """Public API for for building wheels.""" -load("@bazel_skylib//rules:native_binary.bzl", "native_binary") load("//python:py_binary.bzl", "py_binary") load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") load("//python/private:py_package.bzl", "py_package_lib") @@ -86,6 +85,39 @@ This also has the advantage that stamping information is included in the wheel's }, ) +def _py_wheel_publish_binary_impl(ctx): + out_name = ctx.attr.out if ctx.attr.out else ctx.label.name + out = ctx.actions.declare_file(out_name) + ctx.actions.symlink( + target_file = ctx.executable.src, + output = out, + is_executable = True, + ) + runfiles = ctx.runfiles(files = ctx.files.data) + runfiles = runfiles.merge_all([ + dep[DefaultInfo].default_runfiles + for dep in ctx.attr.data + [ctx.attr.src] + ]) + return [DefaultInfo(executable = out, files = depset([out]), runfiles = runfiles)] + +# Like bazel_skylib's native_binary but with cfg = "exec" on src, so the +# twine binary is always built for the exec (host) platform regardless of the +# target platform. This is correct because twine is a developer tool that runs +# on the host to upload wheels to PyPI, never on the cross-compilation target. +_py_wheel_publish_binary = rule( + implementation = _py_wheel_publish_binary_impl, + executable = True, + attrs = { + "src": attr.label( + executable = True, + mandatory = True, + cfg = "exec", + ), + "data": attr.label_list(allow_files = True), + "out": attr.string(), + }, +) + def py_wheel( name, twine = None, @@ -202,7 +234,7 @@ def py_wheel( twine_args.append("$(rootpath :{})/*".format(dist_target)) if twine_binary: - native_binary( + _py_wheel_publish_binary( name = "{}.publish".format(name), src = twine_binary, out = select({