diff --git a/docs/uv-patching.md b/docs/uv-patching.md index f96896ead..ff3e98e56 100644 --- a/docs/uv-patching.md +++ b/docs/uv-patching.md @@ -203,6 +203,13 @@ uv.override_package( convey them. - Generated pure-Python builds reject `toolchains` and `env`; those attributes augment the native build toolchain and environment. +- Native build `env` values can use `$(EXECROOT)/` to anchor paths supplied by + a toolchain, for example `CPPFLAGS = "-I$(EXECROOT)/$(DEP_INC)"` and + `LDFLAGS = "$(EXECROOT)/$(DEP_LIB_A)"`. The anchor remains valid after the + PEP 517 backend changes into the unpacked source tree. +- Native builds select the configured C++ compiler, archiver, linker, and strip + tools by default. Explicit `CC`, `CXX`, `AR`, `LD`, and `STRIP` values in + `env` override those selections. - Post-install patches to prebuilt wheels must preserve every original path used for collision and regular-package merge planning, including its file-or-directory kind and package classification. Ordinary added paths are diff --git a/e2e/cases/pep517-frontend-exec-group/BUILD.bazel b/e2e/cases/pep517-frontend-exec-group/BUILD.bazel index 49e473806..fffa8afd8 100644 --- a/e2e/cases/pep517-frontend-exec-group/BUILD.bazel +++ b/e2e/cases/pep517-frontend-exec-group/BUILD.bazel @@ -2,11 +2,19 @@ load("@aspect_rules_py//uv/private/pep517_whl:rule.bzl", "pep517_native_whl") load("@bazel_lib//lib:transitions.bzl", "platform_transition_filegroup") load("@bazel_skylib//rules:build_test.bzl", "build_test") load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("@rules_cc//cc/toolchains:cc_toolchain.bzl", legacy_cc_toolchain = "cc_toolchain") +load("@rules_cc//cc/toolchains:tool.bzl", "cc_tool") +load("@rules_cc//cc/toolchains:tool_map.bzl", "cc_tool_map") +load("@rules_cc//cc/toolchains:toolchain.bzl", "cc_toolchain") load("@rules_shell//shell:sh_binary.bzl", "sh_binary") -load(":toolchain.bzl", "fake_cc_toolchain") +load(":toolchain.bzl", "fake_cc_toolchain", "legacy_cc_toolchain_config") _CC_TOOLCHAIN = "//pep517-frontend-exec-group:cc_toolchain" +_CONFIGURED_CC_TOOLCHAIN = "//pep517-frontend-exec-group:configured_cc_toolchain" + +_LEGACY_CC_TOOLCHAIN = "//pep517-frontend-exec-group:legacy_cc_toolchain" + # Keep the host first so the default exec group selects it. The native wheel's # `target` exec group requires the transitioned target constraints and selects # the second platform instead. Platform flags are applied in the configuration @@ -38,6 +46,32 @@ platform( ], ) +platform( + name = "linux_configured", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], + flags = [ + "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", + "--extra_execution_platforms=@platforms//host,//pep517-frontend-exec-group:linux_configured", + "--extra_toolchains=" + _CONFIGURED_CC_TOOLCHAIN, + ], +) + +platform( + name = "linux_legacy", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], + flags = [ + "--@aspect_rules_py//uv/private/constraints/platform:platform_libc=glibc", + "--extra_execution_platforms=@platforms//host,//pep517-frontend-exec-group:linux_legacy", + "--extra_toolchains=" + _LEGACY_CC_TOOLCHAIN, + ], +) + config_setting( name = "linux_aarch64_config", constraint_values = [ @@ -92,6 +126,90 @@ toolchain( toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) +write_file( + name = "unusual_c_driver", + out = "unusual_c_driver.sh", + content = [ + "#!/usr/bin/env bash", + "printf '%s\\n' c-action", + ], + is_executable = True, +) + +write_file( + name = "unusual_cxx_driver", + out = "unusual_cxx_driver.sh", + content = [ + "#!/usr/bin/env bash", + "printf '%s\\n' cxx-action", + ], + is_executable = True, +) + +cc_tool( + name = "configured_c_tool", + src = ":unusual_c_driver", +) + +cc_tool( + name = "configured_cxx_tool", + src = ":unusual_cxx_driver", +) + +cc_tool_map( + name = "configured_tool_map", + tools = { + "@rules_cc//cc/toolchains/actions:c_compile": ":configured_c_tool", + "@rules_cc//cc/toolchains/actions:cpp_compile_actions": ":configured_cxx_tool", + "@rules_cc//cc/toolchains/actions:link_actions": ":configured_c_tool", + "@rules_cc//cc/toolchains/actions:strip": ":configured_c_tool", + "@rules_cc//cc/toolchains/actions:ar_actions": ":configured_c_tool", + }, +) + +cc_toolchain( + name = "configured_cc", + tool_map = ":configured_tool_map", +) + +toolchain( + name = "configured_cc_toolchain", + toolchain = ":configured_cc", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) + +filegroup( + name = "legacy_tools", + srcs = [ + "legacy_archiver.sh", + "legacy_compiler.sh", + ], +) + +legacy_cc_toolchain_config( + name = "legacy_cc_config", + archiver = "legacy_archiver.sh", + compiler = "legacy_compiler.sh", +) + +legacy_cc_toolchain( + name = "legacy_cc", + all_files = ":legacy_tools", + ar_files = ":legacy_tools", + compiler_files = ":legacy_tools", + dwp_files = ":legacy_tools", + linker_files = ":legacy_tools", + objcopy_files = ":legacy_tools", + strip_files = ":legacy_tools", + toolchain_config = ":legacy_cc_config", +) + +toolchain( + name = "legacy_cc_toolchain", + toolchain = ":legacy_cc", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) + write_file( name = "frontend_script", out = "frontend.sh", @@ -124,6 +242,66 @@ sh_binary( srcs = [":frontend_script"], ) +write_file( + name = "configured_frontend_script", + out = "configured_frontend.sh", + content = [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "c_actual=\"$(\"${CC}\")\"", + "cxx_actual=\"$(\"${CXX}\")\"", + "if [[ \"${c_actual}\" != c-action || \"${cxx_actual}\" != cxx-action ]]; then", + " echo \"configured tools reported ${c_actual} and ${cxx_actual}\" >&2", + " exit 1", + "fi", + "wheel_dir=\"${!#}\"", + "mkdir -p \"${wheel_dir}\"", + ], + is_executable = True, +) + +sh_binary( + name = "configured_frontend", + srcs = [":configured_frontend_script"], +) + +write_file( + name = "legacy_frontend_script", + out = "legacy_frontend.sh", + content = [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "execroot=\"${PWD}\"", + "if [[ \"${AR-unset}\" != */legacy_archiver.sh || \"${CC-unset}\" != */legacy_compiler.sh || \"${CXX-unset}\" != */legacy_compiler.sh || \"${LD-unset}\" != */legacy_compiler.sh || \"${STRIP-unset}\" != */legacy_compiler.sh ]]; then", + " echo \"legacy tools were not selected: AR=${AR-unset} CC=${CC-unset} CXX=${CXX-unset} LD=${LD-unset} STRIP=${STRIP-unset}\" >&2", + " exit 1", + "fi", + "wheel_dir=\"${!#}\"", + "wheel_dir=\"${execroot}/${wheel_dir}\"", + "work_dir=\"${wheel_dir}.tmp\"", + "/bin/mkdir -p \"${wheel_dir}\" \"${work_dir}/poison\"", + "for tool in cc c++ ar ld strip; do", + " printf '%s\\n' '#!/bin/sh' 'echo ambient tool selected >&2' 'exit 97' > \"${work_dir}/poison/${tool}\"", + " /bin/chmod +x \"${work_dir}/poison/${tool}\"", + "done", + "export PATH=\"${work_dir}/poison:/usr/bin:/bin\"", + "cd \"${work_dir}\"", + "printf '%s\\n' 'int c_value(void) { return 42; }' > dep.c", + "printf '%s\\n' 'extern \"C\" int c_value(void);' 'int main() { return c_value() == 42 ? 0 : 1; }' > main.cc", + "\"${execroot}/${CC}\" -c dep.c -o dep.o", + "\"${execroot}/${AR}\" rcs libdep.a dep.o", + "\"${execroot}/${CXX}\" -c main.cc -o main.o", + "\"${execroot}/${CXX}\" main.o libdep.a -o native", + "./native", + ], + is_executable = True, +) + +sh_binary( + name = "legacy_frontend", + srcs = [":legacy_frontend_script"], +) + write_file( name = "stub_sdist", out = "stub.tar.gz", @@ -142,6 +320,22 @@ pep517_native_whl( version = "0.0.0", ) +pep517_native_whl( + name = "configured_wheel", + src = ":stub_sdist", + tags = ["manual"], + tool = ":configured_frontend", + version = "0.0.0", +) + +pep517_native_whl( + name = "legacy_wheel", + src = ":stub_sdist", + tags = ["manual"], + tool = ":legacy_frontend", + version = "0.0.0", +) + platform_transition_filegroup( name = "wheel_linux_aarch64", srcs = [":wheel"], @@ -154,6 +348,18 @@ platform_transition_filegroup( target_platform = ":linux_x86_64", ) +platform_transition_filegroup( + name = "wheel_linux_configured", + srcs = [":configured_wheel"], + target_platform = ":linux_configured", +) + +platform_transition_filegroup( + name = "wheel_linux_legacy", + srcs = [":legacy_wheel"], + target_platform = ":linux_legacy", +) + build_test( name = "frontend_exec_platform_test", targets = [ @@ -161,3 +367,13 @@ build_test( ":wheel_linux_x86_64", ], ) + +build_test( + name = "configured_compiler_tools_test", + targets = [":wheel_linux_configured"], +) + +build_test( + name = "legacy_toolchain_test", + targets = [":wheel_linux_legacy"], +) diff --git a/e2e/cases/pep517-frontend-exec-group/legacy_archiver.sh b/e2e/cases/pep517-frontend-exec-group/legacy_archiver.sh new file mode 100755 index 000000000..345b84894 --- /dev/null +++ b/e2e/cases/pep517-frontend-exec-group/legacy_archiver.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +export PATH=/usr/bin:/bin +exec /usr/bin/ar "$@" diff --git a/e2e/cases/pep517-frontend-exec-group/legacy_compiler.sh b/e2e/cases/pep517-frontend-exec-group/legacy_compiler.sh new file mode 100755 index 000000000..59d2b33ce --- /dev/null +++ b/e2e/cases/pep517-frontend-exec-group/legacy_compiler.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +export PATH=/usr/bin:/bin +exec /usr/bin/cc "$@" diff --git a/e2e/cases/pep517-frontend-exec-group/toolchain.bzl b/e2e/cases/pep517-frontend-exec-group/toolchain.bzl index 053992565..54eb33666 100644 --- a/e2e/cases/pep517-frontend-exec-group/toolchain.bzl +++ b/e2e/cases/pep517-frontend-exec-group/toolchain.bzl @@ -1,5 +1,9 @@ """Synthetic C++ toolchains for the PEP 517 execution-group test.""" +load("@rules_cc//cc:cc_toolchain_config_lib.bzl", "feature", "tool_path") # buildifier: disable=deprecated-function +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") +load("@rules_cc//cc/toolchains:cc_toolchain_config_info.bzl", "CcToolchainConfigInfo") + def _fake_cc_toolchain_impl(ctx): compiler = ctx.file.compiler return [ @@ -18,3 +22,39 @@ fake_cc_toolchain = rule( ), }, ) + +def _legacy_cc_toolchain_config_impl(ctx): + compiler = ctx.file.compiler.basename + return cc_common.create_cc_toolchain_config_info( + ctx = ctx, + toolchain_identifier = "legacy-no-action-configs", + host_system_name = "local", + target_system_name = "local", + target_cpu = "x86_64", + target_libc = "unknown", + compiler = "legacy", + abi_version = "unknown", + abi_libc_version = "unknown", + features = [feature(name = "no_legacy_features", enabled = True)], + tool_paths = [ + tool_path(name = "ar", path = ctx.file.archiver.basename), + tool_path(name = "cpp", path = compiler), + tool_path(name = "dwp", path = compiler), + tool_path(name = "gcc", path = compiler), + tool_path(name = "gcov", path = compiler), + tool_path(name = "ld", path = compiler), + tool_path(name = "nm", path = compiler), + tool_path(name = "objcopy", path = compiler), + tool_path(name = "objdump", path = compiler), + tool_path(name = "strip", path = compiler), + ], + ) + +legacy_cc_toolchain_config = rule( + implementation = _legacy_cc_toolchain_config_impl, + attrs = { + "archiver": attr.label(allow_single_file = True, mandatory = True), + "compiler": attr.label(allow_single_file = True, mandatory = True), + }, + provides = [CcToolchainConfigInfo], +) diff --git a/e2e/cases/snapshots/sdist_build.uv_sdist_fallback.cowsay.BUILD.bazel b/e2e/cases/snapshots/sdist_build.uv_sdist_fallback.cowsay.BUILD.bazel index 8c0a1bc3a..41bc0ae23 100644 --- a/e2e/cases/snapshots/sdist_build.uv_sdist_fallback.cowsay.BUILD.bazel +++ b/e2e/cases/snapshots/sdist_build.uv_sdist_fallback.cowsay.BUILD.bazel @@ -15,16 +15,6 @@ pep517_native_whl( tool = ":build_tool", version = "6.0", monitor_memory = True, - toolchains = [ - "@bazel_tools//tools/cpp:current_cc_toolchain", - ], - env = { - "AR": "$(AR)", - "CC": "$(CC)", - "CXX": "$(CC)", - "LD": "$(LD)", - "STRIP": "$(STRIP)", - }, visibility = ["//visibility:public"], ) diff --git a/e2e/cases/snapshots/sdist_build.uv_sdist_jdk_build.jpype1.BUILD.bazel b/e2e/cases/snapshots/sdist_build.uv_sdist_jdk_build.jpype1.BUILD.bazel index b1e69aa46..e5360683d 100644 --- a/e2e/cases/snapshots/sdist_build.uv_sdist_jdk_build.jpype1.BUILD.bazel +++ b/e2e/cases/snapshots/sdist_build.uv_sdist_jdk_build.jpype1.BUILD.bazel @@ -15,17 +15,11 @@ pep517_native_whl( tool = ":build_tool", version = "1.7.1", toolchains = [ - "@bazel_tools//tools/cpp:current_cc_toolchain", "@@bazel_tools//tools/jdk:current_java_runtime", ], env = { - "AR": "$(AR)", - "CC": "$(CC)", - "CXX": "$(CC)", "JAVA": "$(JAVA)", "JAVA_HOME": "$(JAVABASE)", - "LD": "$(LD)", - "STRIP": "$(STRIP)", }, visibility = ["//visibility:public"], ) diff --git a/e2e/cases/snapshots/sdist_build.uv_sdist_native_build.python_geohash.BUILD.bazel b/e2e/cases/snapshots/sdist_build.uv_sdist_native_build.python_geohash.BUILD.bazel index 0dcfc5651..728ecb396 100644 --- a/e2e/cases/snapshots/sdist_build.uv_sdist_native_build.python_geohash.BUILD.bazel +++ b/e2e/cases/snapshots/sdist_build.uv_sdist_native_build.python_geohash.BUILD.bazel @@ -17,16 +17,6 @@ pep517_native_whl( resource_set = "mem_4g", pre_build_patches = ["@@//uv-sdist-native-build:require_cxx_driver.patch"], pre_build_patch_strip = 1, - toolchains = [ - "@bazel_tools//tools/cpp:current_cc_toolchain", - ], - env = { - "AR": "$(AR)", - "CC": "$(CC)", - "CXX": "$(CC)", - "LD": "$(LD)", - "STRIP": "$(STRIP)", - }, visibility = ["//visibility:public"], ) diff --git a/e2e/cases/uv-pyproject-cases/pyahocorasick/BUILD.bazel b/e2e/cases/uv-pyproject-cases/pyahocorasick/BUILD.bazel index 7df784da9..8a5c1fd16 100644 --- a/e2e/cases/uv-pyproject-cases/pyahocorasick/BUILD.bazel +++ b/e2e/cases/uv-pyproject-cases/pyahocorasick/BUILD.bazel @@ -10,21 +10,13 @@ load("@aspect_rules_py//py:defs.bzl", "py_test") # this case to a different build_helper.py branch; the version # assertion in __test__.py guards against that. # -# Failure mode without the fix: once a native sdist build is in play, -# every path-shaped thing build_helper.py exports to its compiler -# subprocess must survive the cwd change into the worktree. Two -# cooperating fixes from the zbarsky-openai patch chain make this -# work: +# Every tool path build_helper.py exports to its compiler subprocess must +# survive the cwd change into the unpacked worktree. Two pieces make this work: # -# 1. `tmp_root = path.abspath(opts.outdir) + ".tmp"` (from -# `f12313870`). Was relative — `bazel-out/.../whl.tmp` — and -# invalid from any cwd other than the action's execroot. -# 2. CC/CXX wrappers under `tmp_root/.aspect_rules_py_compilers/` -# (from `5d81044`), pointed to by `CC` / `CXX` / `CPP` / -# `LDSHARED` / `LDCXXSHARED` so the Bazel-relative -# `$(CC)`-expansion (e.g. `external/llvm+/toolchain/gcc`) gets -# replaced with an absolute wrapper path before setup.py invokes -# the compiler from inside the worktree. +# 1. `tmp_root = path.abspath(opts.outdir) + ".tmp"` remains valid after the +# cwd change. +# 2. Absolute CC/CXX wrappers under `tmp_root/.aspect_rules_py_compilers/` +# resolve the configured compiler execpaths before setup.py invokes them. # # Constrained to linux for the same reason as uv-sdist-native-build: # the host-side cc toolchain plumbing that lets a `setup.py diff --git a/uv/private/extension/defs.bzl b/uv/private/extension/defs.bzl index 821dd0729..3bfb0afe9 100644 --- a/uv/private/extension/defs.bzl +++ b/uv/private/extension/defs.bzl @@ -850,11 +850,11 @@ _override_package_tag = tag_class( ), "toolchains": attr.label_list( default = [], - doc = "Extra toolchain targets appended to the generated pep517_native_whl(...) call's `toolchains` list. Each target's TemplateVariableInfo make-variables become available for $(VAR) expansion in `env`.", + doc = "Extra toolchain targets forwarded to the generated pep517_native_whl(...) call's `toolchains` list. Each target's TemplateVariableInfo make-variables become available for $(VAR) expansion in `env`.", ), "env": attr.string_dict( default = {}, - doc = "Extra environment variables merged into the build action's `env` dict. Values may reference $(VAR) make-variables sourced from the default CC toolchain or any extra `toolchains` listed above.", + doc = "Extra environment variables merged into the build action's `env` dict. Values may reference $(VAR) make-variables sourced from extra `toolchains` listed above. Prefix an execroot-relative path with `$(EXECROOT)/` so it remains valid after the backend changes into the unpacked source tree. Omit CC/CXX/AR/LD/STRIP to use the configured C++ action tools.", ), "pre_build_patches": attr.label_list( default = [], diff --git a/uv/private/pep517_whl/BUILD.bazel b/uv/private/pep517_whl/BUILD.bazel index edd88402f..32cf9a3d1 100644 --- a/uv/private/pep517_whl/BUILD.bazel +++ b/uv/private/pep517_whl/BUILD.bazel @@ -8,8 +8,9 @@ load("//py:defs.bzl", "py_binary", "py_library", "py_test") load(":rule.bzl", "pep517_native_whl", "pep517_whl") load( ":test.bzl", - "compiler_driver_test_suite", + "execroot_collision_toolchain", "hostile_python_env_target", + "pep517_native_whl_execroot_collision_test", "pep517_native_whl_toolchain_env_test", ) @@ -26,9 +27,10 @@ bzl_library( name = "rule", srcs = ["rule.bzl"], deps = [ - ":compiler", "//py/private/toolchain:types", "@bazel_lib//lib:resource_sets", + "@rules_cc//cc:action_names_bzl", + "@rules_cc//cc/common", ], ) @@ -81,14 +83,11 @@ bzl_library( srcs = ["test.bzl"], visibility = ["//uv:__subpackages__"], deps = [ - ":compiler", ":rule", "@bazel_skylib//lib:unittest", ], ) -compiler_driver_test_suite(name = "compiler_driver_test") - # --- Analysis test fixtures ---------------------------------------------- # # pep517_native_whl only needs its attrs to type-check at analysis time; @@ -151,18 +150,14 @@ pep517_native_whl( name = "__toolchain_env_fixture", src = ":__stub_sdist", env = { - "CC": "$(CC)", - "CXX": "$(CC)", - "AR": "$(AR)", - "LD": "$(LD)", - "STRIP": "$(STRIP)", "JAVA_HOME": "$(JAVABASE)", "JAVA": "$(JAVA)", + "CPPFLAGS": "-I$(EXECROOT)/relative/include", + "LDFLAGS": "$(EXECROOT)/relative/libdep.a", }, tags = ["manual"], tool = ":__stub_tool", toolchains = [ - "@bazel_tools//tools/cpp:current_cc_toolchain", "@bazel_tools//tools/jdk:current_java_runtime", ], version = "0.0.1", @@ -173,7 +168,18 @@ pep517_native_whl_toolchain_env_test( target_under_test = ":__toolchain_env_fixture", ) -bzl_library( - name = "compiler", - srcs = ["compiler.bzl"], +execroot_collision_toolchain(name = "__execroot_collision_toolchain") + +pep517_native_whl( + name = "__execroot_collision_fixture", + src = ":__stub_sdist", + tags = ["manual"], + tool = ":__stub_tool", + toolchains = [":__execroot_collision_toolchain"], + version = "0.0.1", +) + +pep517_native_whl_execroot_collision_test( + name = "execroot_collision_test", + target_under_test = ":__execroot_collision_fixture", ) diff --git a/uv/private/pep517_whl/build_helper.py b/uv/private/pep517_whl/build_helper.py index 11a1bebe9..f3252f04b 100644 --- a/uv/private/pep517_whl/build_helper.py +++ b/uv/private/pep517_whl/build_helper.py @@ -28,15 +28,10 @@ ) -# `$(CC)` etc. from the pep517_native_whl rule expands to a Bazel -# workspace-relative path (e.g. external/llvm+/toolchain/gcc) that -# resolves from the action execroot but not from the build -# subprocess's cwd inside the unpacked worktree. To keep CC reachable -# after that cwd change, we drop a tiny wrapper into tmp_root (which -# is absolute, so its path survives the cwd change) and point CC / -# CXX / CPP / LDSHARED / LDCXXSHARED at the wrapper. The wrapper -# strips `-fdebug-default-version=4` (older toolchains reject it) -# and then `execv`s the compiler at its resolved absolute path. +# pep517_native_whl supplies compiler execpaths relative to the action +# execroot, which do not resolve from the backend's unpacked worktree. Point +# CC / CXX / CPP / LDSHARED / LDCXXSHARED at absolute wrappers under tmp_root; +# they strip `-fdebug-default-version=4` and exec the resolved compiler. _DEBUG_FLAG = "-fdebug-default-version=4" _COMPILER_WRAPPER = """#!/usr/bin/env python3 import os @@ -63,9 +58,8 @@ def _darwin_sysroot(): def _absolutize_path(value): """Resolve a relative path to absolute, leaving absolute/empty values untouched. - Shared by _resolve_compiler_path (CC/CXX) and _absolutize_java_tool_paths - (JAVA_HOME/JAVA): both toolchains expand workspace-relative refs from the - action execroot, which break once the PEP 517 backend chdirs into the + Shared by _resolve_compiler_path (CC/CXX) and _absolutize_tool_paths. + Toolchain execroot-relative paths break once the PEP 517 backend chdirs into the unpacked sdist. Centralizing the policy keeps the two paths in lockstep and gives future toolchains (FC, RUSTC, ...) a single primitive to call. """ @@ -80,7 +74,10 @@ def _resolve_compiler_path(env, key, default): parts = shlex.split(current) if not parts: return default - return _absolutize_path(parts[0]) + compiler = parts[0] + if path.dirname(compiler): + return _absolutize_path(compiler) + return shutil.which(compiler, path=env.get("PATH", defpath)) or compiler def _make_compiler_wrapper(tmpdir, name, compiler_path, sysroot=None): @@ -106,24 +103,28 @@ def _override_tool(env, key, wrapper): env[key] = shlex.join(parts) -def _absolutize_java_tool_paths(env): - """Resolve Bazel's Java toolchain paths while the helper still runs from - the action execroot. - - $(JAVA)/$(JAVABASE) expand workspace-relative; the PEP 517 backend later - chdirs into the unpacked sdist, which would invalidate those refs. The - JDK inputs are declared via the rule's `toolchains` attr (see - _collect_toolchain_inputs_and_vars), so a missing path fails loudly in - the backend rather than silently shipping a broken relative env. - """ +def _absolutize_tool_paths(env): + """Resolve toolchain paths before the backend changes cwd.""" for key in ("JAVA_HOME", "JAVA"): value = env.get(key) if value: env[key] = _absolutize_path(value) + for key in ("AR", "LD", "STRIP"): + value = env.get(key) + if not value: + continue + parts = shlex.split(value) + if parts and path.dirname(parts[0]): + parts[0] = _absolutize_path(parts[0]) + env[key] = shlex.join(parts) -def _compiler_env(tmpdir): + +def _compiler_env(tmpdir, execroot_marker=None): env = dict(os.environ) + if execroot_marker: + execroot = os.getcwd() + env = {key: value.replace(execroot_marker, execroot) for key, value in env.items()} # The helper's launcher exports RUNFILES_DIR, RUNFILES_MANIFEST_FILE, and # JAVA_RUNFILES: # https://github.com/hermeticbuild/hermetic-launcher/blob/381814d0818af0573263323dc0dd0e4e208fc3fa/README.md#runfiles-discovery @@ -146,10 +147,9 @@ def _compiler_env(tmpdir): env["TEMP"] = tmpdir env["TEMPDIR"] = tmpdir - # The Java toolchain expands its paths relative to the execroot. Resolve - # them while the helper still runs there; the PEP 517 backend later runs - # inside the unpacked source tree. - _absolutize_java_tool_paths(env) + # Bazel expands tool paths relative to the execroot. Resolve them while the + # helper still runs there; bare tool names deliberately remain on PATH. + _absolutize_tool_paths(env) cc_path = _resolve_compiler_path(env, "CC", "cc") cxx_path = _resolve_compiler_path(env, "CXX", "c++") @@ -178,6 +178,7 @@ def _compiler_env(tmpdir): ("LDCXXSHARED", cxx), ]: _override_tool(env, key, wrapper) + return env @@ -234,6 +235,7 @@ def _legacy_metadata_conflicts_with_pyproject(worktree): PARSER.add_argument("--validate-anyarch", action="store_true") PARSER.add_argument("--patch-strip", type=int, default=0, help="Strip count for patch (-p)") PARSER.add_argument("--patch", action="append", default=[], dest="patches", help="Patch file to apply (repeatable)") +PARSER.add_argument("--execroot-marker", help="Token in env values to replace with the absolute execroot") opts, _ = PARSER.parse_known_args() tmp_root = path.abspath(opts.outdir) + ".tmp" @@ -278,7 +280,7 @@ def _legacy_metadata_conflicts_with_pyproject(worktree): # and re-point CC/CXX/etc. through wrapper scripts in tmp_root so the # Bazel-supplied workspace-relative compiler paths survive the cwd # change into the worktree. -build_env = _compiler_env(tmp_root) +build_env = _compiler_env(tmp_root, opts.execroot_marker) if _legacy_metadata_conflicts_with_pyproject(t): print( diff --git a/uv/private/pep517_whl/compiler.bzl b/uv/private/pep517_whl/compiler.bzl deleted file mode 100644 index a70ea5042..000000000 --- a/uv/private/pep517_whl/compiler.bzl +++ /dev/null @@ -1,39 +0,0 @@ -"""Strict C/C++ driver matching for private PEP 517 wheel actions.""" - -_COMPILER_PAIRS = [ - ("clang", "clang++"), - ("gcc", "g++"), -] - -def _driver_suffix(basename, driver_basename): - if basename == driver_basename: - return "" - if not basename.startswith(driver_basename + "-"): - return None - version = basename[len(driver_basename) + 1:] - return "-" + version if version and version.isdigit() else None - -def compiler_driver_paths(compiler_path, available_paths): - """Return selected C/C++ paths for a recognized C compiler, or None.""" - basename = compiler_path.split("/")[-1] - for cc_basename, cxx_basename in _COMPILER_PAIRS: - suffix = _driver_suffix(basename, cc_basename) - if suffix == None: - continue - - cxx_path = cxx_basename + suffix - dirname_index = compiler_path.rfind("/") - if dirname_index != -1: - cxx_path = compiler_path[:dirname_index] + "/" + cxx_path - if cxx_path not in available_paths: - cxx_path = compiler_path - return struct(cxx = cxx_path) - return None - -def cxx_driver_fallback_path(compiler_path): - """Return a recognized C++ driver path for same-driver fallback, or None.""" - basename = compiler_path.split("/")[-1] - for _, cxx_basename in _COMPILER_PAIRS: - if _driver_suffix(basename, cxx_basename) != None: - return compiler_path - return None diff --git a/uv/private/pep517_whl/rule.bzl b/uv/private/pep517_whl/rule.bzl index f1fdd328a..95ed4105e 100644 --- a/uv/private/pep517_whl/rule.bzl +++ b/uv/private/pep517_whl/rule.bzl @@ -6,11 +6,13 @@ build backend the sdist declares in its `[build-system]` table. """ load("@bazel_lib//lib:resource_sets.bzl", "resource_set", "resource_set_attr") +load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES") +load("@rules_cc//cc/common:cc_common.bzl", "cc_common") load("//py/private/toolchain:types.bzl", "NATIVE_BUILD_TOOLCHAIN", "PY_TOOLCHAIN") -load("//uv/private/pep517_whl:compiler.bzl", "compiler_driver_paths", "cxx_driver_fallback_path") _CC_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/cpp:toolchain_type") _TARGET_EXEC_GROUP = "target" +_EXECROOT_MARKER = "__ASPECT_RULES_PY_EXECROOT__" _INHERITED_PYTHON_ENV = ( "PYTHONHOME", @@ -84,41 +86,65 @@ def _collect_toolchain_inputs_and_vars(ctx): known_variables.update(target[platform_common.TemplateVariableInfo].variables) return extra_inputs, known_variables -def _cc_toolchain_inputs_and_compilers(ctx): - """Return the target execution group's C++ files and C/C++ drivers.""" +def _cc_toolchain_inputs_and_tools(ctx): + """Return the target execution group's C++ files and selected build tools.""" cc_toolchain = ctx.exec_groups[_TARGET_EXEC_GROUP].toolchains[_CC_TOOLCHAIN_TYPE] if hasattr(cc_toolchain, "cc_provider_in_toolchain") and hasattr(cc_toolchain, "cc"): cc_toolchain = cc_toolchain.cc if not cc_toolchain or not hasattr(cc_toolchain, "all_files"): - return None, None, None + return None, {} files = cc_toolchain.all_files - files_list = files.to_list() - files_by_path = {f.path: f for f in files_list} - compiler_file = None - if hasattr(cc_toolchain, "compiler_executable"): - compiler_basename = cc_toolchain.compiler_executable.split("/")[-1] - for f in files_list: - if f.basename == compiler_basename: - compiler_file = f - break - if not compiler_file: - for f in files_list: - if compiler_driver_paths(f.path, files_by_path) != None: - compiler_file = f - break - if not compiler_file: - for f in files_list: - if cxx_driver_fallback_path(f.path) != None: - compiler_file = f - break - - # Preserve the current same-driver behavior when the selected toolchain - # files do not expose a matching same-directory C++ companion, including - # toolchains that expose only a C++ wrapper. - compiler_path = compiler_file.path if compiler_file else None - driver_paths = compiler_driver_paths(compiler_path, files_by_path) if compiler_path else None - cxx_path = driver_paths.cxx if driver_paths else compiler_path - return files, compiler_path, cxx_path + + # Minimal C++ ToolchainInfo implementations can still supply a compiler + # and its files without a CcToolchainInfo feature configuration. + if not hasattr(cc_toolchain, "ar_executable"): + compiler = getattr(cc_toolchain, "compiler_executable", None) + if not compiler: + return files, {} + return files, {"CC": compiler, "CXX": compiler} + + feature_configuration = cc_common.configure_features( + ctx = ctx, + cc_toolchain = cc_toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + action_names = { + "AR": ACTION_NAMES.cpp_link_static_library, + "CC": ACTION_NAMES.c_compile, + "CXX": ACTION_NAMES.cpp_compile, + "LD": ACTION_NAMES.cpp_link_dynamic_library, + "STRIP": ACTION_NAMES.strip, + } + + tools = { + key: cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = action_name, + ) + for key, action_name in action_names.items() + if cc_common.action_is_enabled( + feature_configuration = feature_configuration, + action_name = action_name, + ) + } + + missing = [key for key in action_names if not tools.get(key)] + if missing: + # Legacy C++ toolchains can omit action configs while still exposing + # usable tools through CcToolchainInfo. Action-only providers may + # fabricate these fields, so require each fallback to be an input. + file_paths = {file.path: True for file in files.to_list()} + legacy_tools = { + "AR": cc_toolchain.ar_executable, + "CC": cc_toolchain.compiler_executable, + "CXX": cc_toolchain.compiler_executable, + "LD": cc_toolchain.ld_executable, + "STRIP": cc_toolchain.strip_executable, + } + tools.update({key: legacy_tools[key] for key in missing if legacy_tools[key] in file_paths}) + + return files, {key: value for key, value in tools.items() if value} def _pep517_whl(ctx): archive = ctx.file.src @@ -157,17 +183,20 @@ def _pep517_native_whl(ctx): env = _common_env(ctx) extra_inputs, known_variables = _collect_toolchain_inputs_and_vars(ctx) - cc_files, cc_compiler, cxx_compiler = _cc_toolchain_inputs_and_compilers(ctx) + if "EXECROOT" in known_variables: + fail("A toolchain listed in `toolchains` exports the reserved `EXECROOT` make-variable.") + known_variables["EXECROOT"] = _EXECROOT_MARKER + + cc_files, cc_tools = _cc_toolchain_inputs_and_tools(ctx) if cc_files: extra_inputs.append(cc_files) for k, v in ctx.attr.env.items(): env[k] = ctx.expand_make_variables("env", v, known_variables) - if cc_compiler: - env["CC"] = cc_compiler - if cxx_compiler: - env["CXX"] = cxx_compiler + for key, value in cc_tools.items(): + if key not in ctx.attr.env: + env[key] = value ctx.actions.run( mnemonic = "PySdistNativeBuild", @@ -175,6 +204,8 @@ def _pep517_native_whl(ctx): executable = ctx.executable.tool, toolchain = None, arguments = ctx.attr.args + patch_args + _memory_args(ctx) + [ + "--execroot-marker", + _EXECROOT_MARKER, archive.path, wheel_dir.path, ], @@ -243,7 +274,7 @@ Consumes a sdist artifact and performs a build of that artifact with the specified Python dependencies under the configured Python toolchain to produce a platform-specific bdist we can subsequently install or deploy. -Toolchains the build action depends on are passed via the standard `toolchains` +Extra toolchains the build action depends on are passed via the standard `toolchains` attribute and each target's `DefaultInfo.files`, `ToolchainInfo.all_files`, and `TemplateVariableInfo.variables` are forwarded to the action. The `env` attribute maps environment variable names to strings that may reference @@ -260,9 +291,13 @@ constraints of the target platform. doc = "Environment variables to set on the build action. Values may " + "contain `$(VAR)` references to make-variables exposed by any " + "target in the rule's `toolchains` attribute (via " + - "`TemplateVariableInfo`).", + "`TemplateVariableInfo`). Prefix an execroot-relative path with " + + "`$(EXECROOT)/` so it remains valid after the backend changes into " + + "the unpacked source tree. Omit CC/CXX/AR/LD/STRIP to use the " + + "configured C++ action tools.", ), }, + fragments = ["cpp"], exec_groups = { # Create an exec group which depends on a toolchain which can only be # resolved to exec_compatible_with constraints equal to the target. This diff --git a/uv/private/pep517_whl/test.bzl b/uv/private/pep517_whl/test.bzl index d92b3749c..9d24a5899 100644 --- a/uv/private/pep517_whl/test.bzl +++ b/uv/private/pep517_whl/test.bzl @@ -5,8 +5,7 @@ Inspecting the action at analysis time avoids actually running a PEP 517 build to verify the env wiring. """ -load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") -load("//uv/private/pep517_whl:compiler.bzl", "compiler_driver_paths", "cxx_driver_fallback_path") +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") _ACTION_ENV = "//command_line_option:action_env" _HOST_ENV = [ @@ -48,9 +47,8 @@ hostile_python_env_target = rule( }, ) -# Env vars sourced from the CC toolchain's TemplateVariableInfo. SYSROOT is -# omitted because some toolchains legitimately have an empty sysroot and -# it isn't exposed as a TemplateVariableInfo make variable today. +# Env vars selected from the configured C++ action tools. SYSROOT is omitted +# because some toolchains legitimately have an empty sysroot. _CC_ENV_KEYS = ["CC", "CXX", "AR", "LD", "STRIP"] # Env vars sourced from the Java runtime toolchain's TemplateVariableInfo. @@ -85,68 +83,35 @@ def _toolchain_env_test_impl(ctx): "${} should resolve to a non-empty toolchain path".format(key), ) + args = build_actions[0].argv + marker_index = args.index("--execroot-marker") + marker = args[marker_index + 1] + asserts.equals(env, "-I{}/relative/include".format(marker), action_env.get("CPPFLAGS")) + asserts.equals(env, "{}/relative/libdep.a".format(marker), action_env.get("LDFLAGS")) + action_inputs = [f.path for f in build_actions[0].inputs.to_list()] - asserts.true( - env, - action_env.get("CXX") in action_inputs, - "CXX should come from the selected toolchain inputs", - ) - cc = action_env.get("CC") - driver_paths = compiler_driver_paths(cc, {path: True for path in action_inputs}) - asserts.equals( - env, - driver_paths.cxx if driver_paths else cc, - action_env.get("CXX"), - "CXX should use the declared companion or selected compiler fallback", - ) + for key in _CC_ENV_KEYS: + asserts.true( + env, + action_env.get(key) in action_inputs, + "{} should select a declared C++ action tool".format(key), + ) return analysistest.end(env) pep517_native_whl_toolchain_env_test = analysistest.make(_toolchain_env_test_impl) -def _compiler_driver_paths_test_impl(ctx): - env = unittest.begin(ctx) - exact = compiler_driver_paths("gcc", {"gcc": True, "g++": True}) - asserts.equals(env, "g++", exact.cxx) - - versioned = compiler_driver_paths( - "toolchain/bin/clang-22", - { - "toolchain/bin/clang-22": True, - "toolchain/bin/clang++-22": True, - }, - ) - asserts.equals(env, "toolchain/bin/clang++-22", versioned.cxx) - - for near_miss in ["clang-cl", "clang-format", "gcc-ar"]: - asserts.equals( - env, - None, - compiler_driver_paths(near_miss, {near_miss: True}), - "{} should not be treated as a compiler driver".format(near_miss), - ) - - fallback = compiler_driver_paths("toolchain/bin/gcc", {"toolchain/bin/gcc": True}) - asserts.equals(env, "toolchain/bin/gcc", fallback.cxx) - - for cxx_driver in [ - "g++", - "toolchain/bin/g++-12", - "clang++", - "toolchain/bin/clang++-22", - ]: - asserts.equals(env, cxx_driver, cxx_driver_fallback_path(cxx_driver)) +def _execroot_collision_toolchain_impl(_ctx): + return [platform_common.TemplateVariableInfo({"EXECROOT": "collision"})] - for near_miss in ["g++-ar", "clang++-format", "clang++foo"]: - asserts.equals( - env, - None, - cxx_driver_fallback_path(near_miss), - "{} should not be treated as a C++ driver".format(near_miss), - ) - return unittest.end(env) +execroot_collision_toolchain = rule(implementation = _execroot_collision_toolchain_impl) -compiler_driver_paths_test = unittest.make(_compiler_driver_paths_test_impl) +def _execroot_collision_test_impl(ctx): + env = analysistest.begin(ctx) + asserts.expect_failure(env, "exports the reserved `EXECROOT` make-variable") + return analysistest.end(env) -def compiler_driver_test_suite(name): - unittest.suite(name, compiler_driver_paths_test) +pep517_native_whl_execroot_collision_test = analysistest.make( + _execroot_collision_test_impl, + expect_failure = True, +) diff --git a/uv/private/pep517_whl/tests/native_dep/BUILD.bazel b/uv/private/pep517_whl/tests/native_dep/BUILD.bazel new file mode 100644 index 000000000..cd2c47e8c --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/BUILD.bazel @@ -0,0 +1,106 @@ +load("@bazel_lib//:bzl_library.bzl", "bzl_library") +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@tar.bzl//tar:mtree.bzl", "mtree_mutate", "mtree_spec") +load("@tar.bzl//tar:tar.bzl", "tar_rule") +load("//uv/private/pep517_whl:rule.bzl", "pep517_native_whl") +load(":defs.bzl", "dep_makevars") + +# A native source build consumes an in-repo cc_library through the same +# toolchain/env shape emitted by uv.override_package. Its paths are valid from +# the execroot, but not after the backend changes into the unpacked sdist. +cc_library( + name = "dep", + testonly = True, + srcs = ["dep.c"], + hdrs = ["dep.h"], +) + +write_file( + name = "ar_override", + out = "ar_override.sh", + content = [ + "#!/bin/sh", + "exec /usr/bin/ar \"$@\"", + ], + is_executable = True, +) + +write_file( + name = "cc_override", + out = "cc_override.sh", + content = [ + "#!/bin/sh", + "exec /usr/bin/cc \"$@\"", + ], + is_executable = True, +) + +dep_makevars( + name = "dep_makevars", + testonly = True, + ar = ":ar_override", + cc = ":cc_override", + hdr = "dep.h", + lib = ":dep", + visibility = ["//uv/private/pep517_whl/tests/native_dep/tool_names:__subpackages__"], +) + +_SDIST_SRCS = glob( + ["sdist/**"], + exclude = [ + "sdist/**/__pycache__/**", + "sdist/**/*.pyc", + ], +) + +mtree_spec( + name = "sdist_mtree_raw", + testonly = True, + srcs = _SDIST_SRCS, + include_runfiles = False, +) + +mtree_mutate( + name = "sdist_mtree", + testonly = True, + mtree = ":sdist_mtree_raw", + strip_prefix = package_name(), +) + +tar_rule( + name = "sdist", + testonly = True, + srcs = _SDIST_SRCS, + mtree = ":sdist_mtree", + visibility = ["//uv/private/pep517_whl/tests/native_dep/tool_names:__subpackages__"], +) + +pep517_native_whl( + name = "whl", + testonly = True, + src = ":sdist", + env = { + "CPPFLAGS": "-I$(EXECROOT)/$(DEP_INC)", + "LDFLAGS": "$(EXECROOT)/$(DEP_LIB_A)", + }, + tags = ["manual"], + tool = "//uv/private/pep517_whl:__build_helper", + toolchains = [ + ":dep_makevars", + ], + version = "0.0.1", +) + +build_test( + name = "native_dep_env_test", + targets = [":whl"], +) + +bzl_library( + name = "defs", + srcs = ["defs.bzl"], + visibility = ["//uv:__subpackages__"], + deps = ["@rules_cc//cc/common:cc_info.bzl"], +) diff --git a/uv/private/pep517_whl/tests/native_dep/defs.bzl b/uv/private/pep517_whl/tests/native_dep/defs.bzl new file mode 100644 index 000000000..be4c2c2fb --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/defs.bzl @@ -0,0 +1,38 @@ +"""Expose an in-repo cc_library's header and archive as native-build make vars.""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") + +def _dep_makevars_impl(ctx): + archives = [ + archive + for linker_input in ctx.attr.lib[CcInfo].linking_context.linker_inputs.to_list() + for lib in linker_input.libraries + for archive in [lib.static_library or lib.pic_static_library] + if archive + ] + if not archives: + fail("cc_library {} produced no static archive".format(ctx.attr.lib.label)) + + header = ctx.file.hdr + archive = archives[0] + ar = ctx.file.ar + cc = ctx.file.cc + return [ + DefaultInfo(files = depset([header, archive, ar, cc])), + platform_common.TemplateVariableInfo({ + "DEP_INC": header.dirname, + "DEP_LIB_A": archive.path, + "DEP_AR": ar.path, + "DEP_CC": cc.path, + }), + ] + +dep_makevars = rule( + implementation = _dep_makevars_impl, + attrs = { + "ar": attr.label(allow_single_file = True, mandatory = True), + "cc": attr.label(allow_single_file = True, mandatory = True), + "hdr": attr.label(allow_single_file = True, mandatory = True), + "lib": attr.label(providers = [CcInfo], mandatory = True), + }, +) diff --git a/uv/private/pep517_whl/tests/native_dep/dep.c b/uv/private/pep517_whl/tests/native_dep/dep.c new file mode 100644 index 000000000..b2cd3c71c --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/dep.c @@ -0,0 +1,3 @@ +#include "dep.h" + +int dep_answer(void) { return 42; } diff --git a/uv/private/pep517_whl/tests/native_dep/dep.h b/uv/private/pep517_whl/tests/native_dep/dep.h new file mode 100644 index 000000000..7e45bda56 --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/dep.h @@ -0,0 +1,6 @@ +#ifndef ASPECT_RULES_PY_TEST_DEP_H +#define ASPECT_RULES_PY_TEST_DEP_H + +int dep_answer(void); + +#endif diff --git a/uv/private/pep517_whl/tests/native_dep/sdist/backend.py b/uv/private/pep517_whl/tests/native_dep/sdist/backend.py new file mode 100644 index 000000000..b9070f831 --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/sdist/backend.py @@ -0,0 +1,75 @@ +"""Build a native extension whose compile/link inputs live outside the sdist.""" + +from __future__ import annotations + +import base64 +import csv +import hashlib +import io +import os +import shlex +import subprocess +from pathlib import Path +from zipfile import ZIP_DEFLATED, ZipFile + +_DIST_INFO = "native_dep_ext-0.0.1.dist-info" +_EXT = "native_dep_ext.so" + + +def _compile() -> None: + for key in ("AR", "LD", "STRIP"): + value = os.environ[key] + command = shlex.split(value) + expected = os.environ.get(f"EXPECTED_{key}") + if expected is not None and command != shlex.split(expected): + raise RuntimeError(f"{key} must remain {expected!r}, got {value!r}") + executable = Path(command[0]) + if executable.parent != Path(".") and (not executable.is_absolute() or not executable.exists()): + raise RuntimeError(f"{key} must be an absolute toolchain command, got {value!r}") + + cc = shlex.split(os.environ["CC"]) + cppflags = shlex.split(os.environ["CPPFLAGS"]) + ldflags = shlex.split(os.environ["LDFLAGS"]) + subprocess.run([*cc, "-fPIC", *cppflags, "-c", "mod.c", "-o", "mod.o"], check=True) + ar = shlex.split(os.environ["AR"]) + archive_args = ["-static", "-o", "mod.a", "mod.o"] if "libtool" in Path(ar[0]).name else ["rcs", "mod.a", "mod.o"] + subprocess.run([*ar, *archive_args], check=True) + subprocess.run([*cc, "-shared", "mod.o", *ldflags, "-o", _EXT], check=True) + + +def get_requires_for_build_wheel(config_settings=None): + del config_settings + return [] + + +def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + del config_settings, metadata_directory + _compile() + + files = { + _EXT: Path(_EXT).read_bytes(), + f"{_DIST_INFO}/METADATA": ( + "Metadata-Version: 2.1\nName: native-dep-ext\nVersion: 0.0.1\n" + ).encode(), + f"{_DIST_INFO}/WHEEL": ( + "Wheel-Version: 1.0\n" + "Generator: rules_py native-dep test backend\n" + "Root-Is-Purelib: false\n" + "Tag: py3-none-linux_x86_64\n" + ).encode(), + } + + rows = [] + for name, content in files.items(): + digest = base64.urlsafe_b64encode(hashlib.sha256(content).digest()).rstrip(b"=") + rows.append([name, "sha256=" + digest.decode(), str(len(content))]) + rows.append([f"{_DIST_INFO}/RECORD", "", ""]) + record = io.StringIO() + csv.writer(record, lineterminator="\n").writerows(rows) + files[f"{_DIST_INFO}/RECORD"] = record.getvalue().encode() + + wheel_name = "native_dep_ext-0.0.1-py3-none-linux_x86_64.whl" + with ZipFile(Path(wheel_directory, wheel_name), "w", ZIP_DEFLATED) as wheel: + for name, content in files.items(): + wheel.writestr(name, content) + return wheel_name diff --git a/uv/private/pep517_whl/tests/native_dep/sdist/mod.c b/uv/private/pep517_whl/tests/native_dep/sdist/mod.c new file mode 100644 index 000000000..dba1ab6de --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/sdist/mod.c @@ -0,0 +1,3 @@ +#include "dep.h" + +int mod_value(void) { return dep_answer(); } diff --git a/uv/private/pep517_whl/tests/native_dep/sdist/pyproject.toml b/uv/private/pep517_whl/tests/native_dep/sdist/pyproject.toml new file mode 100644 index 000000000..1ff07ac36 --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/sdist/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires = [] +build-backend = "backend" +backend-path = ["."] + +[project] +name = "native_dep_ext" +version = "0.0.1" diff --git a/uv/private/pep517_whl/tests/native_dep/tool_names/BUILD.bazel b/uv/private/pep517_whl/tests/native_dep/tool_names/BUILD.bazel new file mode 100644 index 000000000..a2847b60c --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/tool_names/BUILD.bazel @@ -0,0 +1,27 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("//uv/private/pep517_whl:rule.bzl", "pep517_native_whl") + +pep517_native_whl( + name = "whl", + testonly = True, + src = "//uv/private/pep517_whl/tests/native_dep:sdist", + env = { + "AR": "$(EXECROOT)/$(DEP_AR)", + "CC": "$(EXECROOT)/$(DEP_CC)", + "CPPFLAGS": "-I$(EXECROOT)/$(DEP_INC)", + "EXPECTED_LD": "$(EXECROOT)/$(DEP_CC) '--ld-flag=two words'", + "EXPECTED_STRIP": "$(EXECROOT)/$(DEP_CC) '--strip-flag=two words'", + "LD": "$(EXECROOT)/$(DEP_CC) '--ld-flag=two words'", + "LDFLAGS": "$(EXECROOT)/$(DEP_LIB_A)", + "STRIP": "$(EXECROOT)/$(DEP_CC) '--strip-flag=two words'", + }, + tags = ["manual"], + tool = "//uv/private/pep517_whl:__build_helper", + toolchains = ["//uv/private/pep517_whl/tests/native_dep:dep_makevars"], + version = "0.0.1", +) + +build_test( + name = "native_dep_tool_names_test", + targets = [":whl"], +) diff --git a/uv/private/pep517_whl/tests/native_dep/tool_names/bare/BUILD.bazel b/uv/private/pep517_whl/tests/native_dep/tool_names/bare/BUILD.bazel new file mode 100644 index 000000000..0ec700d5f --- /dev/null +++ b/uv/private/pep517_whl/tests/native_dep/tool_names/bare/BUILD.bazel @@ -0,0 +1,29 @@ +load("@bazel_skylib//rules:build_test.bzl", "build_test") +load("//uv/private/pep517_whl:rule.bzl", "pep517_native_whl") + +pep517_native_whl( + name = "whl", + testonly = True, + src = "//uv/private/pep517_whl/tests/native_dep:sdist", + env = { + "AR": "env 'AR_MARKER=external/two words' /usr/bin/ar", + "CC": "cc", + "CPPFLAGS": "-I$(EXECROOT)/$(DEP_INC)", + "CXX": "c++", + "EXPECTED_AR": "env 'AR_MARKER=external/two words' /usr/bin/ar", + "EXPECTED_LD": "env 'LD_MARKER=external/two words' /usr/bin/ld", + "EXPECTED_STRIP": "env 'STRIP_MARKER=external/two words' /usr/bin/strip", + "LD": "env 'LD_MARKER=external/two words' /usr/bin/ld", + "LDFLAGS": "$(EXECROOT)/$(DEP_LIB_A)", + "STRIP": "env 'STRIP_MARKER=external/two words' /usr/bin/strip", + }, + tags = ["manual"], + tool = "//uv/private/pep517_whl:__build_helper", + toolchains = ["//uv/private/pep517_whl/tests/native_dep:dep_makevars"], + version = "0.0.1", +) + +build_test( + name = "native_dep_bare_tools_test", + targets = [":whl"], +) diff --git a/uv/private/sdist_build/repository.bzl b/uv/private/sdist_build/repository.bzl index 356a821cd..c144a48f0 100644 --- a/uv/private/sdist_build/repository.bzl +++ b/uv/private/sdist_build/repository.bzl @@ -258,40 +258,29 @@ def _sdist_build_impl(repository_ctx): strip = repository_ctx.attr.pre_build_patch_strip, ) - # For native builds, emit a baked-in CC toolchain + CC/CXX/AR/LD/STRIP - # env block. Targets in `toolchains` expose `TemplateVariableInfo`; - # the env values below are make-variable references resolved at - # action analysis time. - # - # CXX starts at $(CC); pep517_native_whl replaces it with a matching - # same-directory clang++ / g++ from the selected toolchain when present. - # - # `extra_toolchains` and `extra_env` augment (do not replace) the - # defaults — set via `uv.override_package(toolchains = [...], - # env = {...})` to layer JDK / Rust / etc. plumbing on top. + # pep517_native_whl selects the C++ action tools directly; legacy CC, + # AR, LD, and STRIP make variables can be synthetic. Only forward explicit + # toolchains/env for JDK, Rust, and other package-specific overrides. toolchain_attrs = "" if is_native: - toolchains = [ - "@bazel_tools//tools/cpp:current_cc_toolchain", - ] + list(repository_ctx.attr.extra_toolchains) - env = { - "AR": "$(AR)", - "CC": "$(CC)", - "CXX": "$(CC)", - "LD": "$(LD)", - "STRIP": "$(STRIP)", - } - env.update(repository_ctx.attr.extra_env) - toolchain_attrs = """ - toolchains = [ -{toolchains} - ], + toolchains = repository_ctx.attr.extra_toolchains + extra_env = repository_ctx.attr.extra_env + env_attr = "" + if extra_env: + env_attr = """ env = {{ {env} }},""".format( - toolchains = "\n".join([" \"{}\",".format(t) for t in toolchains]), - env = "\n".join([" \"{}\": \"{}\",".format(k, v) for k, v in sorted(env.items())]), - ) + env = "\n".join([" \"{}\": \"{}\",".format(k, v) for k, v in sorted(extra_env.items())]), + ) + if toolchains: + toolchain_attrs = """ + toolchains = [ +{toolchains} + ],""".format( + toolchains = "\n".join([" \"{}\",".format(t) for t in toolchains]), + ) + toolchain_attrs += env_attr resource_set_attr = "" if repository_ctx.attr.resource_set != "default": @@ -367,11 +356,11 @@ sdist_build = repository_rule( "pre_build_patch_strip": attr.int(default = 0), "extra_toolchains": attr.string_list( default = [], - doc = "Toolchain labels appended to the default CC toolchain in the generated pep517_native_whl(...) `toolchains` list. Set via `uv.override_package(toolchains = [...])`.", + doc = "Toolchain labels forwarded to the generated pep517_native_whl(...) `toolchains` list. Set via `uv.override_package(toolchains = [...])`.", ), "extra_env": attr.string_dict( default = {}, - doc = "Environment variables merged into the default CC env dict in the generated pep517_native_whl(...) `env` dict. Values may reference $(VAR) make-variables from any toolchain. Set via `uv.override_package(env = {...})`.", + doc = "Environment variables forwarded to the generated pep517_native_whl(...) `env` dict. Values may reference $(VAR) make-variables from extra toolchains. Prefix an execroot-relative path with `$(EXECROOT)/` so it remains valid after the backend changes into the unpacked source tree. Set via `uv.override_package(env = {...})`.", ), }, )