Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion uv/private/extension/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ _override_package_tag = tag_class(
),
"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 the default CC toolchain or any extra `toolchains` listed above. Prefix a workspace-relative path with `$(EXECROOT)/` (e.g. `-I$(EXECROOT)/$(DEP_INC)`) to anchor it to an absolute path so it survives the build backend's chdir into the sdist worktree.",
),

# Pre-build patches: applied to extracted sdist source before wheel build.
Expand Down
10 changes: 8 additions & 2 deletions uv/private/pep517_whl/build_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def _override_tool(env, key, wrapper):
env[key] = shlex.join(parts)


def _compiler_env(tmpdir):
def _compiler_env(tmpdir, execroot_marker=None):
env = dict(os.environ)
# The helper's launcher exports RUNFILES_DIR, RUNFILES_MANIFEST_FILE, and
# JAVA_RUNFILES:
Expand Down Expand Up @@ -148,6 +148,11 @@ def _compiler_env(tmpdir):
("LDCXXSHARED", cxx),
]:
_override_tool(env, key, wrapper)

# Anchor `$(EXECROOT)` paths: our cwd is the execroot (the backend chdir happens later, in a subprocess).
if execroot_marker:
root = os.getcwd()
env = {key: value.replace(execroot_marker, root) for key, value in env.items()}
return env


Expand Down Expand Up @@ -204,6 +209,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", default=None, help="Token in env values to replace with the absolute execroot")
opts, _ = PARSER.parse_known_args()

tmp_root = path.abspath(opts.outdir) + ".tmp"
Expand Down Expand Up @@ -248,7 +254,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(
Expand Down
21 changes: 20 additions & 1 deletion uv/private/pep517_whl/rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ load("//uv/private/pep517_whl:compiler.bzl", "compiler_driver_paths", "cxx_drive
_CC_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/cpp:toolchain_type")
_TARGET_EXEC_GROUP = "target"

# `$(EXECROOT)` expands to this marker, which build_helper replaces with the
# absolute execroot at build time — the execroot isn't known at analysis. Lets
# an `env` path opt into anchoring so it survives the backend's chdir.
_EXECROOT_MARKER = "__ASPECT_RULES_PY_EXECROOT__"

_INHERITED_PYTHON_ENV = (
"PYTHONHOME",
"PYTHONPATH",
Expand Down Expand Up @@ -157,6 +162,15 @@ def _pep517_native_whl(ctx):
env = _common_env(ctx)
extra_inputs, known_variables = _collect_toolchain_inputs_and_vars(ctx)

# `EXECROOT` is reserved for the anchor below. If a toolchain already
# exports it, `$(EXECROOT)` would expand to that value instead of our
# marker and anchoring would silently no-op, so fail loudly instead.
if "EXECROOT" in known_variables:
fail("A toolchain listed in `toolchains` exports a reserved `EXECROOT` " +
"make-variable, which collides with the `$(EXECROOT)` anchor used to " +
"absolutize workspace-relative env paths. Rename that toolchain's variable.")
known_variables["EXECROOT"] = _EXECROOT_MARKER

cc_files, cc_compiler, cxx_compiler = _cc_toolchain_inputs_and_compilers(ctx)
if cc_files:
extra_inputs.append(cc_files)
Expand All @@ -175,6 +189,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,
],
Expand Down Expand Up @@ -260,7 +276,10 @@ 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 a workspace-relative path with " +
"`$(EXECROOT)/` (e.g. `-I$(EXECROOT)/$(DEP_INC)`) to anchor it to " +
"an absolute path so it survives the build backend's chdir into " +
"the sdist worktree.",
),
},
exec_groups = {
Expand Down
75 changes: 75 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
load("@bazel_lib//:bzl_library.bzl", "bzl_library")

# Regression: a native sdist consuming an in-repo cc_library via
# override_package(env=/toolchains=) must build. The dep's include dir and
# static archive reach the build as workspace-relative paths, which resolve
# from the execroot but not the PEP 517 backend's worktree cwd.
load("@bazel_skylib//rules:build_test.bzl", "build_test")
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")

cc_library(
name = "dep",
testonly = True,
srcs = ["dep.c"],
hdrs = ["dep.h"],
)

dep_makevars(
name = "dep_makevars",
testonly = True,
hdr = "dep.h",
lib = ":dep",
)

_SDIST_SRCS = glob(["sdist/**"])

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",
)

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"],
)
37 changes: 37 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Test shim: emulate `uv.override_package(toolchains=, env=)` by surfacing a
cc_library's include dir + static archive as make-vars."""

load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")

def _dep_makevars_impl(ctx):
cc_info = ctx.attr.lib[CcInfo]
archives = []
for linker_input in cc_info.linking_context.linker_inputs.to_list():
for lib in linker_input.libraries:
archive = lib.static_library or lib.pic_static_library
if archive:
archives.append(archive)
if not archives:
fail("cc_library {} produced no static archive".format(ctx.attr.lib.label))
archive = archives[0]
header = ctx.file.hdr

return [
# header + archive must be staged as build-action inputs;
# pep517_native_whl stages each toolchain dep's DefaultInfo.files.
DefaultInfo(files = depset([header, archive])),
platform_common.TemplateVariableInfo({
# Deliberately workspace-relative (execroot-valid, worktree-invalid):
# the input under test.
"DEP_INC": header.dirname,
"DEP_LIB_A": archive.path,
}),
]

dep_makevars = rule(
implementation = _dep_makevars_impl,
attrs = {
"lib": attr.label(providers = [CcInfo], mandatory = True),
"hdr": attr.label(allow_single_file = True, mandatory = True),
},
)
3 changes: 3 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/dep.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "dep.h"

int dep_answer(void) { return 42; }
6 changes: 6 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/dep.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef ASPECT_RULES_PY_TEST_DEP_H
#define ASPECT_RULES_PY_TEST_DEP_H

int dep_answer(void);

#endif
64 changes: 64 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/sdist/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""PEP 517 backend for the regression test: compiles mod.c with the
env-supplied CC/CPPFLAGS/LDFLAGS, so the build succeeds only if those
workspace-relative flag paths resolve from this backend's worktree cwd."""

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:
cc = shlex.split(os.environ.get("CC") or "cc")
cppflags = shlex.split(os.environ.get("CPPFLAGS", ""))
ldflags = shlex.split(os.environ.get("LDFLAGS", ""))
subprocess.run([*cc, "-fPIC", *cppflags, "-c", "mod.c", "-o", "mod.o"], 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: list[list[str]] = []
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
3 changes: 3 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/sdist/mod.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "dep.h"

int mod_value(void) { return dep_answer(); }
8 changes: 8 additions & 0 deletions uv/private/pep517_whl/tests/native_dep/sdist/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build-system]
requires = []
build-backend = "backend"
backend-path = ["."]

[project]
name = "native_dep_ext"
version = "0.0.1"
Loading