Skip to content
Merged
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
56 changes: 28 additions & 28 deletions modules/backend_maturin/private/tools/generate_cargo_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,33 @@ def derive_default_output(sdist_path: Path) -> str:
return "Cargo.lock"


def _find_cargo_toml_dir_from_pyproject(sdist_root: Path) -> Path | None:
pyproject_path = sdist_root / "pyproject.toml"
if not pyproject_path.exists():
return sdist_root

try:
import tomllib
except ImportError:
try:
from pip._vendor import tomli as tomllib # type: ignore[no-redef]
except ImportError:
print("Warning: tomllib/tomli not found. Searching for Cargo.toml instead.", file=sys.stderr)
return None

try:
with open(pyproject_path, "rb") as f:
pyproject_data = tomllib.load(f)
manifest_path = pyproject_data.get("tool", {}).get("maturin", {}).get("manifest-path", "Cargo.toml")
return (sdist_root / manifest_path).parent
except Exception as e:
print(
f"Warning: Failed to parse pyproject.toml: {e}. Searching for Cargo.toml instead.",
file=sys.stderr,
)
return None


def main():
parser = argparse.ArgumentParser(description="Generate Cargo.lock for an sdist.")
parser.add_argument("--sdist", required=True, help="Path to the sdist tarball.")
Expand Down Expand Up @@ -64,34 +91,7 @@ def main():

# Find Cargo.toml.
# We also want to support manifest-path if defined in pyproject.toml
cargo_toml_dir = sdist_root
pyproject_path = sdist_root / "pyproject.toml"
if pyproject_path.exists():
try:
import tomllib
except ImportError:
try:
from pip._vendor import tomli as tomllib # type: ignore[no-redef]
except ImportError:
tomllib = None # type: ignore[assignment]

if tomllib:
with open(pyproject_path, "rb") as f:
try:
pyproject_data = tomllib.load(f)
manifest_path = (
pyproject_data.get("tool", {}).get("maturin", {}).get("manifest-path", "Cargo.toml")
)
cargo_toml_dir = (sdist_root / manifest_path).parent
except Exception as e:
print(
f"Warning: Failed to parse pyproject.toml: {e}. Searching for Cargo.toml instead.",
file=sys.stderr,
)
cargo_toml_dir = None
else:
print("Warning: tomllib/tomli not found. Searching for Cargo.toml instead.", file=sys.stderr)
cargo_toml_dir = None
cargo_toml_dir = _find_cargo_toml_dir_from_pyproject(sdist_root)

if not cargo_toml_dir or not (cargo_toml_dir / "Cargo.toml").exists():
# Fallback to searching
Expand Down
29 changes: 13 additions & 16 deletions modules/backend_maturin/private/tools/rust_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ def get_pyo3_version(cargo_dir: Path):
return None


def _symlink_stdlib(src_path: Path, dst_path: Path, label: str) -> None:
if not src_path.exists():
raise RuntimeError(f"{label} missing at {src_path}")
if dst_path.exists() or dst_path.is_symlink():
dst_path.unlink(missing_ok=True)
dst_path.parent.mkdir(parents=True, exist_ok=True)
os.symlink(src_path.resolve(), dst_path)


def configure_rust_env(ctx, cargo_dir: Path, is_maturin: bool = False):
rust_config = None
for layer_config in load_layers(ctx):
Expand Down Expand Up @@ -198,24 +207,12 @@ def configure_rust_env(ctx, cargo_dir: Path, is_maturin: bool = False):
target_stdlib_dst_path = sysroot_dir / Path(f"lib/rustlib/{target_triple}/lib")

try:
if host_stdlib_src_path:
if not host_stdlib_src_path.exists():
raise RuntimeError(f"host_stdlib missing at {host_stdlib_src_path}")
if host_stdlib_dst_path.exists() or host_stdlib_dst_path.is_symlink():
host_stdlib_dst_path.unlink(missing_ok=True)

host_stdlib_dst_path.parent.mkdir(parents=True, exist_ok=True)
os.symlink(host_stdlib_src_path.resolve(), host_stdlib_dst_path)
if host_stdlib_src_path and host_stdlib_dst_path:
_symlink_stdlib(host_stdlib_src_path, host_stdlib_dst_path, "host_stdlib")

target_stdlib_src_path = ctx.prefix / Path(f"external/{cross_repo_name}/lib/rustlib/{target_triple}/lib")
if target_stdlib_src_path and target_stdlib_dst_path:
if not target_stdlib_src_path.exists():
raise RuntimeError(f"target_stdlib missing at {target_stdlib_src_path}")
if target_stdlib_src_path != host_stdlib_src_path:
if target_stdlib_dst_path.exists() or target_stdlib_dst_path.is_symlink():
target_stdlib_dst_path.unlink(missing_ok=True)
target_stdlib_dst_path.parent.mkdir(parents=True, exist_ok=True)
os.symlink(target_stdlib_src_path.resolve(), target_stdlib_dst_path)
if target_stdlib_src_path != host_stdlib_src_path:
_symlink_stdlib(target_stdlib_src_path, target_stdlib_dst_path, "target_stdlib")
except Exception as e:
raise RuntimeError(f"Failed to populate hermetic Rust sysroot: {e}") from e

Expand Down
45 changes: 22 additions & 23 deletions pycross/private/backends.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,24 @@ def _collect_registrations(modules, warn = _print_warn):

for pyproject_backend in tag.pyproject_backends:
if pyproject_backend in backend_to_rule and not module.is_root:
if pyproject_backend in root_pyproject_backends:
continue
warn(
"Ignoring duplicate pyproject backend '{}' -> '{}' from module '{}' (already mapped to '{}')".format(
pyproject_backend,
name,
module.name,
backend_to_rule[pyproject_backend],
),
)
else:
backend_to_rule[pyproject_backend] = name
if module.is_root:
root_pyproject_backends[pyproject_backend] = True

if tag.default:
if default_backend and not module.is_root:
if root_default:
continue
if pyproject_backend not in root_pyproject_backends:
warn(
"Ignoring duplicate pyproject backend '{}' -> '{}' from module '{}' (already mapped to '{}')".format(
pyproject_backend,
name,
module.name,
backend_to_rule[pyproject_backend],
),
)
continue
backend_to_rule[pyproject_backend] = name
if module.is_root:
root_pyproject_backends[pyproject_backend] = True

if not tag.default:
continue
if default_backend and not module.is_root:
if not root_default:
warn(
"Ignoring default backend '{}' from module '{}' (already set to '{}' by module '{}')".format(
name,
Expand All @@ -90,10 +89,10 @@ def _collect_registrations(modules, warn = _print_warn):
default_backend_module,
),
)
else:
default_backend = name
default_backend_module = module.name
root_default = module.is_root
continue
default_backend = name
default_backend_module = module.name
root_default = module.is_root

if not default_backend:
fail("No default build backend registered. Set `default = True` on one `backends.register` tag.")
Expand Down
34 changes: 16 additions & 18 deletions pycross/private/build/rules/common_attrs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,19 @@ def resolve_path_tools(ctx):
file = info.executable,
files_to_run = target[DefaultInfo].files_to_run,
))
else:
exe = target[DefaultInfo].files_to_run.executable
if not exe:
files = target[DefaultInfo].files.to_list()
if files:
exe = files[0]
if not exe:
fail("Tool target must provide an executable: " + str(target.label))
result.append(struct(
name = exe.basename,
file = exe,
files_to_run = target[DefaultInfo].files_to_run,
))
continue

exe = target[DefaultInfo].files_to_run.executable
if not exe:
files = target[DefaultInfo].files.to_list()
exe = files[0] if files else None
if not exe:
fail("Tool target must provide an executable: " + str(target.label))
result.append(struct(
name = exe.basename,
file = exe,
files_to_run = target[DefaultInfo].files_to_run,
))
return result

COMMON_BUILD_ATTRS = {
Expand Down Expand Up @@ -188,11 +188,9 @@ def group_tool_deps(tool_deps_list):
"""
result = {}
for dep in tool_deps_list:
if PycrossPackageInfo in dep:
name = dep[PycrossPackageInfo].package_name
if name not in result:
result[name] = []
result[name].append(dep)
if PycrossPackageInfo not in dep:
continue
result.setdefault(dep[PycrossPackageInfo].package_name, []).append(dep)
return result

def get_wheel(target):
Expand Down
46 changes: 19 additions & 27 deletions pycross/private/build/tools/utils/cc_toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,18 @@ def parse_ar_and_guess_ranlib(ar: str | None) -> Tuple[List[str], Path | None]:
ar_list[0] = resolved_ar

stem = ar_path.stem
if stem == "ar" or stem.endswith(("-ar", "_ar")):
ranlib_stem = stem[:-2] + "ranlib"
ranlib_name = ranlib_stem + ar_path.suffix

if ar_path.is_absolute():
guessed_ranlib_path = ar_path.parent / ranlib_name
if guessed_ranlib_path.exists():
return ar_list, guessed_ranlib_path
else:
resolved_ranlib = shutil.which(ranlib_name)
if resolved_ranlib:
return ar_list, Path(resolved_ranlib)
if not (stem == "ar" or stem.endswith(("-ar", "_ar"))):
return ar_list, None

ranlib_stem = stem[:-2] + "ranlib"
ranlib_name = ranlib_stem + ar_path.suffix

return ar_list, None
if ar_path.is_absolute():
guessed_ranlib_path = ar_path.parent / ranlib_name
return ar_list, guessed_ranlib_path if guessed_ranlib_path.exists() else None
else:
resolved_ranlib = shutil.which(ranlib_name)
return ar_list, Path(resolved_ranlib) if resolved_ranlib else None


_FILTERED_LINKER_ARGS = (
Expand Down Expand Up @@ -81,23 +79,17 @@ def parse_ar_and_guess_ranlib(ar: str | None) -> Tuple[List[str], Path | None]:

def get_wrapper_flags(cflags: str) -> List[str]:
"""Extract target and sysroot flags to forward to compiler wrappers."""
possible_flags = ["-target", "--target", "--sysroot", "-isysroot", "-mmacosx-version-min"]
possible_flags = ("-target", "--target", "--sysroot", "-isysroot", "-mmacosx-version-min")
result = []
split_cflags = shlex.split(cflags)
for i, flag in enumerate(split_cflags):
for possible_flag in possible_flags:
if not (flag.startswith(possible_flag)):
continue
if "=" in flag:
flag, value = flag.split("=", 1)
additions = [f"{flag}={value}"]
else:
flag, value = flag, split_cflags[i + 1]
additions = [flag, value]

if not flag == possible_flag:
continue
result.extend(additions)
flag_name, sep, value = flag.partition("=")
if flag_name not in possible_flags:
continue
if sep:
result.append(f"{flag_name}={value}")
else:
result.extend([flag_name, split_cflags[i + 1]])
return result


Expand Down
27 changes: 15 additions & 12 deletions pycross/private/cycle_dep_needed.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ load("//pycross/private:util.bzl", "parse_package_key")

# ---- BFS reachability -------------------------------------------------------

def _node_env(markers_env, node_extra):
base_extra = markers_env.get("extra", "")
if type(base_extra) == "list":
if base_extra[0] == node_extra:
return markers_env
env = dict(markers_env)
env["extra"] = [node_extra] + base_extra[1:]
return env
if base_extra == node_extra:
return markers_env
env = dict(markers_env)
env["extra"] = node_extra
return env

def _active_neighbors(edges, node, markers_env):
"""Returns the list of neighbor nodes reachable from `node` given marker values.

Expand All @@ -45,18 +59,7 @@ def _active_neighbors(edges, node, markers_env):
marker_str = edge.get("marker")
if marker_str:
if node_env == None:
base_extra = markers_env.get("extra", "")
if type(base_extra) == "list":
if base_extra[0] == node_extra:
node_env = markers_env
else:
node_env = dict(markers_env)
node_env["extra"] = [node_extra] + base_extra[1:]
elif base_extra == node_extra:
node_env = markers_env
else:
node_env = dict(markers_env)
node_env["extra"] = node_extra
node_env = _node_env(markers_env, node_extra)
parsed_marker = pypackaging.markers.parse(marker_str)
if not pypackaging.markers.evaluate(parsed_marker, node_env):
continue
Expand Down
27 changes: 14 additions & 13 deletions pycross/private/cycle_member_marker_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ def find_unconditional_deps(member, other_members, edges):
dep = edge["dep"]
if dep not in cycle_members:
continue
has_marker = bool(edge.get("marker"))
if not has_marker and dep not in visited:
visited[dep] = True
if dep != member:
unconditional[dep] = True
queue.append(dep)
if edge.get("marker") or dep in visited:
continue
visited[dep] = True
if dep != member:
unconditional[dep] = True
queue.append(dep)

return sorted(unconditional.keys())

Expand Down Expand Up @@ -116,10 +116,11 @@ def compute_reachability_groups(member, other_members, edges):
collapsible = {} # node -> predecessor
for node in other_members:
node_inbound = inbound.get(node, [])
if len(node_inbound) == 1:
pred, has_marker = node_inbound[0]
if not has_marker and pred != member and pred in members_set:
collapsible[node] = pred
if len(node_inbound) != 1:
continue
pred, has_marker = node_inbound[0]
if not has_marker and pred != member and pred in members_set:
collapsible[node] = pred

# Step 3: resolve chains. If C -> B -> A in the collapsible map, C's
# representative is A (the first non-collapsible ancestor).
Expand Down Expand Up @@ -373,10 +374,10 @@ def pycross_cycle_member_marker_deps(
if cg.eval_match_name and native.existing_rule(cg.eval_match_name):
match_name = cg.eval_match_name
else:
if edges_json == None:
edges_json = json.encode(edges)

if not native.existing_rule(cg.pair_name):
if edges_json == None:
edges_json = json.encode(edges)

# Reachability evaluator: returns FeatureFlagInfo("true"/"false")
pycross_cycle_dep_needed(
name = cg.pair_name,
Expand Down
5 changes: 2 additions & 3 deletions pycross/private/format_extension.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -379,13 +379,12 @@ def make_format_extension(
for tag in module.tags.package:
ws_name = tag.workspace
if not ws_name:
if len(workspaces_in_module) == 1:
ws_name = workspaces_in_module[0]
else:
if len(workspaces_in_module) != 1:
fail("package '{}' must specify workspace (module defines workspaces: {})".format(
tag.name,
", ".join(workspaces_in_module) if workspaces_in_module else "none",
))
ws_name = workspaces_in_module[0]

if ws_name not in workspaces_in_module:
fail("package '{}': workspace '{}' not declared by this module".format(tag.name, ws_name))
Expand Down
Loading
Loading