Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix vendor Rust: limit to manifests from backend #532

Merged
merged 1 commit into from
Feb 5, 2025
Merged
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
85 changes: 65 additions & 20 deletions src/fromager/vendor_rust.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Vendor Rust crates into an sdist"""

import enum
import json
import logging
import os
Expand All @@ -22,8 +23,10 @@
},
}

# vendor when project depends on a Rust build system
RUST_BUILD_REQUIRES = frozenset({"setuptools-rust", "maturin"})

class RustBuildSystem(enum.StrEnum):
maturin = "maturin"
setuptools_rust = "setuptools-rust"


def _cargo_vendor(
Expand Down Expand Up @@ -86,28 +89,32 @@ def _cargo_config(project_dir: pathlib.Path) -> None:
tomlkit.dump(cfg, f)


def _should_vendor_rust(req: Requirement, project_dir: pathlib.Path) -> bool:
"""Detect if project has build requirement on Rust
def _detect_rust_build_backend(
req: Requirement, pyproject_toml: dict[str, typing.Any]
) -> RustBuildSystem | None:
"""Detect Rust requirement and return Rust build system

Detects setuptools-rust and maturin.
"""
pyproject_toml = dependencies.get_pyproject_contents(project_dir)
if not pyproject_toml:
logger.debug(f"{req.name}: has no pyproject.toml")
return False

build_backend = dependencies.get_build_backend(pyproject_toml)
build_system = dependencies.get_build_backend(pyproject_toml)
if build_system["build-backend"] == RustBuildSystem.maturin:
return RustBuildSystem.maturin

for reqstring in build_backend["requires"]:
for reqstring in build_system["requires"]:
req = Requirement(reqstring)
if req.name in RUST_BUILD_REQUIRES:
try:
# StrEnum.__contains__ does not work with str type
rbs = RustBuildSystem(req.name)
except ValueError:
pass
else:
logger.debug(
f"{req.name}: build-system requires {req.name}, vendoring crates"
f"{req.name}: build-system requires '{req.name}', vendoring crates"
)
return True
return rbs

logger.debug(f"{req.name}: no Rust build plugin detected")
return False
return None


def vendor_rust(
Expand All @@ -119,14 +126,52 @@ def vendor_rust(
``setuptools-rust`` or ``maturin``, and has a ``Cargo.toml``, otherwise
``False``.
"""
if not _should_vendor_rust(req, project_dir):
pyproject_toml = dependencies.get_pyproject_contents(project_dir)
if not pyproject_toml:
logger.debug(f"{req.name}: has no pyproject.toml")
return False

# check for Cargo.toml
manifests = list(project_dir.glob("**/Cargo.toml"))
backend = _detect_rust_build_backend(req, pyproject_toml)
manifests: list[pathlib.Path] = []
# By default, maturin and setuptools-rust use Cargo.toml from project
# root directory. Projects can specify a different file in optional
# "tool.setuptools-rust" or "tool.maturin" entries.
match backend:
case RustBuildSystem.maturin:
try:
tool_maturin: dict[str, typing.Any] = pyproject_toml["tool"]["maturin"]
except KeyError as e:
logger.debug(f"{req.name}: No additional maturin settings: {e}")
else:
if "manifest-path" in tool_maturin:
manifests.append(project_dir / tool_maturin["manifest-path"])
case RustBuildSystem.setuptools_rust:
ext_modules: list[dict[str, typing.Any]]
try:
ext_modules = pyproject_toml["tool"]["setuptools-rust"]["ext-modules"]
except KeyError as e:
logger.debug(f"{req.name}: No additional setuptools-rust settings: {e}")
else:
for ext_module in ext_modules:
if "path" in ext_module:
manifests.append(project_dir / ext_module["path"])
case None:
logger.debug(f"{req.name}: no Rust build system detected")
return False
case _ as unreachable:
typing.assert_never(unreachable)

if not manifests:
logger.debug(f"{req.name}: has no Cargo.toml files")
return False
# check for Cargo.toml in project root
root_cargo_toml = project_dir / "Cargo.toml"
if root_cargo_toml.is_file():
manifests.append(root_cargo_toml)
else:
logger.warning(
f"{req.name}: Rust build backend {backend} detected, but "
"no Cargo.toml files found."
)
return False

the_manifests = sorted(str(d.relative_to(project_dir)) for d in manifests)
logger.debug(f"{req.name}: {project_dir} has cargo manifests: {the_manifests}")
Expand Down
Loading