From 3cd0135f63676d8e272652303f6f74bde0aead55 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 17 Sep 2026 09:19:27 +0900 Subject: [PATCH] fix: mark virtual workspace Cargo.toml manifests as skipped, not zero-node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Cargo.toml with only [workspace] (no [package]) legitimately declares no package node of its own. extract_package_manifest already returned an empty result for it, but without a "skipped" marker, extract.py's #1666 empty-source detector could not tell that apart from an unexplained extraction failure, so every run printed a persistent "produced zero nodes" warning for a manifest that was never supposed to emit one. _parse_cargo now reports workspace_only=True when [workspace] is present with no [package], and extract_package_manifest returns skipped=True for that case only — a manifest missing a name for any other reason still warns. Fixes downstream report at ContextualWisdomLab/fast-mlsirm#1833. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01BXYRNTJhYLB2bYveuaFe74 --- graphify/manifest_ingest.py | 10 ++++++++++ tests/test_manifest_ingest.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/graphify/manifest_ingest.py b/graphify/manifest_ingest.py index 313465334f..5c470e7f27 100644 --- a/graphify/manifest_ingest.py +++ b/graphify/manifest_ingest.py @@ -92,6 +92,14 @@ def extract_package_manifest(path: Path) -> dict[str, Any]: except Exception as exc: # noqa: BLE001 — a malformed manifest must not abort extraction return {"nodes": [], "edges": [], "error": f"manifest parse error: {exc}"} if not info or not info.get("name"): + # A virtual workspace root (Cargo `[workspace]`, no `[package]`) is a + # legitimate zero-node result, not a failure (#1833): mark it + # "skipped" so extract.py's #1666 empty-source detector doesn't print + # a persistent "produced zero nodes" warning for a manifest that was + # never supposed to emit a package node. A manifest missing a name + # for any other reason keeps warning, since that IS unexplained. + if info and info.get("workspace_only"): + return {"nodes": [], "edges": [], "skipped": True} return {"nodes": [], "edges": []} name = info["name"] @@ -237,6 +245,8 @@ def _parse_cargo(text: str) -> dict | None: # package of its own — emit nothing rather than a fabricated node. ``name`` is # never workspace-inheritable in Cargo, but guard on the type anyway. if not isinstance(name, str) or not name: + if isinstance(data.get("workspace"), dict): + return {"name": None, "workspace_only": True, "deps": []} return None # ``version`` may be workspace-inherited (``version.workspace = true``), which # parses to a table; keep only a concrete string version. diff --git a/tests/test_manifest_ingest.py b/tests/test_manifest_ingest.py index 9f01675c4f..92ca9f1c29 100644 --- a/tests/test_manifest_ingest.py +++ b/tests/test_manifest_ingest.py @@ -144,6 +144,21 @@ def test_cargo_virtual_workspace_manifest_emits_no_package(tmp_path): assert _pkg_nodes(r) == [] +def test_cargo_virtual_workspace_manifest_is_marked_skipped(tmp_path): + # #1833: a virtual workspace root (no [package]) legitimately emits zero + # nodes by design. Without an explicit "skipped" marker, extract.py's + # #1666 empty-source detector treats this as an unexplained failure and + # prints a persistent "produced zero nodes" warning on every run. A + # manifest with no [workspace] table either (i.e. genuinely malformed, + # missing a name) must NOT be marked skipped — that would hide a real + # problem. + ws = _write(tmp_path / "ws" / "Cargo.toml", '[workspace]\nmembers = ["a"]\n') + assert extract_package_manifest(ws).get("skipped") is True + + broken = _write(tmp_path / "broken" / "Cargo.toml", '[dependencies]\nserde = "1"\n') + assert extract_package_manifest(broken).get("skipped") is not True + + def test_cargo_target_conditional_deps_are_collected(tmp_path): # Platform-gated deps under [target.'cfg(...)'.dependencies] are common in # real crates and must not be dropped just because they are conditional.