Skip to content

Commit 53daee5

Browse files
committed
Fix vendor Rust: limit to manifests from backend
Fromager was vendoring crates from all `Cargo.toml` files in a project. This approach is causing issues for projects that have cargo files in tests and example directories. The `vendor_rust()` function now only vendors crates from `Cargo.toml` in the project's root directory and additional cargo files listed in `tools.maturin` or `tools.setuptools-rust` entries. Fixes: #529 Signed-off-by: Christian Heimes <[email protected]>
1 parent c84ee7a commit 53daee5

File tree

2 files changed

+67
-22
lines changed

2 files changed

+67
-22
lines changed

src/fromager/vendor_rust.py

+65-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Vendor Rust crates into an sdist"""
22

3+
import enum
34
import json
45
import logging
56
import os
@@ -22,8 +23,10 @@
2223
},
2324
}
2425

25-
# vendor when project depends on a Rust build system
26-
RUST_BUILD_REQUIRES = frozenset({"setuptools-rust", "maturin"})
26+
27+
class RustBuildSystem(enum.StrEnum):
28+
maturin = "maturin"
29+
setuptools_rust = "setuptools-rust"
2730

2831

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

8891

89-
def _should_vendor_rust(req: Requirement, project_dir: pathlib.Path) -> bool:
90-
"""Detect if project has build requirement on Rust
92+
def _detect_rust_build_backend(
93+
req: Requirement, pyproject_toml: dict[str, typing.Any]
94+
) -> RustBuildSystem | None:
95+
"""Detect Rust requirement and return Rust build system
9196
9297
Detects setuptools-rust and maturin.
9398
"""
94-
pyproject_toml = dependencies.get_pyproject_contents(project_dir)
95-
if not pyproject_toml:
96-
logger.debug(f"{req.name}: has no pyproject.toml")
97-
return False
98-
99-
build_backend = dependencies.get_build_backend(pyproject_toml)
99+
build_system = dependencies.get_build_backend(pyproject_toml)
100+
if build_system["build-backend"] == RustBuildSystem.maturin:
101+
return RustBuildSystem.maturin
100102

101-
for reqstring in build_backend["requires"]:
103+
for reqstring in build_system["requires"]:
102104
req = Requirement(reqstring)
103-
if req.name in RUST_BUILD_REQUIRES:
105+
try:
106+
# StrEnum.__contains__ does not work with str type
107+
rbs = RustBuildSystem(req.name)
108+
except ValueError:
109+
pass
110+
else:
104111
logger.debug(
105-
f"{req.name}: build-system requires {req.name}, vendoring crates"
112+
f"{req.name}: build-system requires '{req.name}', vendoring crates"
106113
)
107-
return True
114+
return rbs
108115

109116
logger.debug(f"{req.name}: no Rust build plugin detected")
110-
return False
117+
return None
111118

112119

113120
def vendor_rust(
@@ -119,14 +126,52 @@ def vendor_rust(
119126
``setuptools-rust`` or ``maturin``, and has a ``Cargo.toml``, otherwise
120127
``False``.
121128
"""
122-
if not _should_vendor_rust(req, project_dir):
129+
pyproject_toml = dependencies.get_pyproject_contents(project_dir)
130+
if not pyproject_toml:
131+
logger.debug(f"{req.name}: has no pyproject.toml")
123132
return False
124133

125-
# check for Cargo.toml
126-
manifests = list(project_dir.glob("**/Cargo.toml"))
134+
backend = _detect_rust_build_backend(req, pyproject_toml)
135+
manifests: list[pathlib.Path] = []
136+
# By default, maturin and setuptools-rust use Cargo.toml from project
137+
# root directory. Projects can specify a different file in optional
138+
# "tool.setuptools-rust" or "tool.maturin" entries.
139+
match backend:
140+
case RustBuildSystem.maturin:
141+
try:
142+
tool_maturin: dict[str, typing.Any] = pyproject_toml["tool"]["maturin"]
143+
except KeyError as e:
144+
logger.debug(f"{req.name}: No additional maturin settings: {e}")
145+
else:
146+
if "manifest-path" in tool_maturin:
147+
manifests.append(project_dir / tool_maturin["manifest-path"])
148+
case RustBuildSystem.setuptools_rust:
149+
ext_modules: list[dict[str, typing.Any]]
150+
try:
151+
ext_modules = pyproject_toml["tool"]["setuptools-rust"]["ext-modules"]
152+
except KeyError as e:
153+
logger.debug(f"{req.name}: No additional setuptools-rust settings: {e}")
154+
else:
155+
for ext_module in ext_modules:
156+
if "path" in ext_module:
157+
manifests.append(project_dir / ext_module["path"])
158+
case None:
159+
logger.debug(f"{req.name}: no Rust build system detected")
160+
return False
161+
case _ as unreachable:
162+
typing.assert_never(unreachable)
163+
127164
if not manifests:
128-
logger.debug(f"{req.name}: has no Cargo.toml files")
129-
return False
165+
# check for Cargo.toml in project root
166+
root_cargo_toml = project_dir / "Cargo.toml"
167+
if root_cargo_toml.is_file():
168+
manifests.append(root_cargo_toml)
169+
else:
170+
logger.warning(
171+
f"{req.name}: Rust build backend {backend} detected, but "
172+
"no Cargo.toml files found."
173+
)
174+
return False
130175

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

tox.ini

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ commands =
2424
[testenv:linter]
2525
base_python=python3.11
2626
deps=
27-
ruff
27+
ruff >= 0.9.2
2828
packaging
2929
PyYAML
3030
commands =
@@ -37,7 +37,7 @@ skip_sdist = true
3737
[testenv:fix]
3838
base_python=python3.11
3939
deps=
40-
ruff
40+
ruff >= 0.9.2
4141
commands =
4242
ruff format src tests docs
4343
ruff check --fix src tests docs

0 commit comments

Comments
 (0)