From 165a7da4a14ae8d47f12e42a08328ae9239fa61b Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Tue, 22 Sep 2026 14:50:05 +0530 Subject: [PATCH] Discover a Cargo.toml in a subdirectory when none sits at the scan root introspect_cargo only ever looked at root divided by Cargo dot toml, with no search, so any repository whose crate lives in a subdirectory got no crate nodes or dependency edges at all, even though the AST pass had already parsed every rs file there. The common case is a Tauri app, whose default scaffold keeps its manifest under its own frontend adjacent crate folder rather than the project root. Add a bounded depth search that only runs when the root manifest is missing, reusing the same noise directory pruning the AST scanner already applies so a vendored or example manifest nested inside a dependency folder never wins over the project's own. A workspace manifest found this way now resolves its own members relative to its own directory rather than the outer scan root, since those two can now differ. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh (cherry picked from commit 577e51749a86efcebcd1967baab651e273a58c59) --- graphify/cargo_introspect.py | 42 +++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/graphify/cargo_introspect.py b/graphify/cargo_introspect.py index fa03ed309..026c50e94 100644 --- a/graphify/cargo_introspect.py +++ b/graphify/cargo_introspect.py @@ -2,11 +2,13 @@ from __future__ import annotations +import os from pathlib import Path from typing import Any _CONFIDENCE_EXTRACTED = "EXTRACTED" +_MAX_DISCOVERY_DEPTH = 2 def _load_toml(path: Path) -> dict[str, Any]: @@ -24,6 +26,37 @@ def _load_toml(path: Path) -> dict[str, Any]: return tomllib.load(manifest) +def _discover_root_manifest(root: Path) -> Path | None: + """Find a Cargo.toml under *root* when none sits directly at the root. + + A bounded-depth search rather than an unbounded walk: a Tauri app's + crate lives in src-tauri/, a polyglot repo's in rust/ or crates/, but + going arbitrarily deep risks matching a vendored or example manifest + nested well inside an unrelated dependency (#3678). Reuses the same + noise-directory pruning the AST scanner already applies (venvs, caches, + node_modules, build output) so those never get searched into. A + genuinely ambiguous layout with more than one candidate at the same + depth is resolved by sorted path order rather than guessed at; the + caller is free to point --cargo at a subdirectory directly for a repo + where that is not the right answer. + """ + from graphify.detect import _is_noise_dir + + found: list[Path] = [] + for dirpath, dirnames, filenames in os.walk(root): + current = Path(dirpath) + depth = len(current.relative_to(root).parts) + if depth > 0 and "Cargo.toml" in filenames: + found.append(current / "Cargo.toml") + if depth >= _MAX_DISCOVERY_DEPTH: + dirnames[:] = [] + continue + dirnames[:] = [d for d in dirnames if not _is_noise_dir(d, current)] + if not found: + return None + return sorted(found)[0] + + def _member_manifest_paths(root: Path, root_data: dict[str, Any]) -> list[Path]: paths: list[Path] = [] if isinstance(root_data.get("package"), dict): @@ -48,9 +81,16 @@ def introspect_cargo(root: str | Path) -> dict[str, Any]: """Return crate nodes and internal dependency edges from Cargo manifests.""" root_path = Path(root).resolve() root_manifest = root_path / "Cargo.toml" + if not root_manifest.is_file(): + discovered = _discover_root_manifest(root_path) + if discovered is not None: + root_manifest = discovered root_data = _load_toml(root_manifest) - manifests = _member_manifest_paths(root_path, root_data) + # Workspace members are resolved relative to the manifest's OWN + # directory, not necessarily root_path (#3678: the manifest can live in + # a discovered subdirectory, e.g. src-tauri/). + manifests = _member_manifest_paths(root_manifest.parent, root_data) crates: dict[str, tuple[str, Path, dict[str, Any]]] = {} for manifest in manifests: