mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 23:49:14 +00:00
fix(cargo): resolve inherited workspace dependency identity
(cherry picked from commit 4d8fa2e93c)
This commit is contained in:
1 parent
9b42507014
commit
7a508a71bd
2 files changed
+133
No files matched your search
@@ -86,6 +86,10 @@ def introspect_cargo(root: str | Path) -> dict[str, Any]:
|
||||
if discovered is not None:
|
||||
root_manifest = discovered
|
||||
root_data = _load_toml(root_manifest)
|
||||
workspace = root_data.get("workspace")
|
||||
workspace_dependencies = workspace.get("dependencies", {}) if isinstance(workspace, dict) else {}
|
||||
if not isinstance(workspace_dependencies, dict):
|
||||
workspace_dependencies = {}
|
||||
|
||||
# Workspace members are resolved relative to the manifest's OWN
|
||||
# directory, not necessarily root_path (#3678: the manifest can live in
|
||||
@@ -119,6 +123,13 @@ def introspect_cargo(root: str | Path) -> dict[str, Any]:
|
||||
continue
|
||||
source_file = manifest.relative_to(root_path).as_posix()
|
||||
for dep_key, spec in sorted(dependencies.items()):
|
||||
inherited = isinstance(spec, dict) and "workspace" in spec
|
||||
if inherited:
|
||||
if spec["workspace"] is not True:
|
||||
continue
|
||||
spec = workspace_dependencies.get(dep_key)
|
||||
if not isinstance(spec, dict) or not isinstance(spec.get("path"), str):
|
||||
continue
|
||||
# Cargo lets a dep table entry rename the crate via `package = "..."`:
|
||||
# db = { path = "../storage", package = "internal-storage" }
|
||||
# The key `db` is the name used in `use db::…;`; the actual crate
|
||||
@@ -133,6 +144,12 @@ def introspect_cargo(root: str | Path) -> dict[str, Any]:
|
||||
target = crates.get(real_name)
|
||||
if target is None:
|
||||
continue
|
||||
if inherited:
|
||||
# Shared paths are relative to the workspace root, not the member.
|
||||
# A matching name alone cannot prove a registry/git dep is local.
|
||||
inherited_manifest = (root_path / spec["path"] / "Cargo.toml").resolve()
|
||||
if inherited_manifest != target[1].resolve():
|
||||
continue
|
||||
edges.append(
|
||||
{
|
||||
"source": source_id,
|
||||
|
||||
@@ -565,3 +565,119 @@ edition = "2021"
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
introspect_cargo(tmp_path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("root_package", [False, True], ids=["virtual", "root-package"])
|
||||
def test_cargo_inherited_rename_uses_workspace_dependency_identity(tmp_path, root_package):
|
||||
root_header = '[package]\nname = "root-app"\nversion = "0.1.0"\n' if root_package else ""
|
||||
root_dependency = '[dependencies]\ndb.workspace = true\n' if root_package else ""
|
||||
_write_manifest(
|
||||
tmp_path / "Cargo.toml",
|
||||
root_header
|
||||
+ '[workspace]\nmembers = ["app", "storage"]\n'
|
||||
+ '[workspace.dependencies]\n'
|
||||
+ 'db = { path = "storage", package = "internal-storage" }\n'
|
||||
+ root_dependency,
|
||||
)
|
||||
app = tmp_path / "app"
|
||||
storage = tmp_path / "storage"
|
||||
app.mkdir()
|
||||
storage.mkdir()
|
||||
_write_manifest(
|
||||
app / "Cargo.toml",
|
||||
'[package]\nname = "app"\nversion = "0.1.0"\n'
|
||||
'[dependencies]\ndb = { workspace = true, features = ["fast"] }\n',
|
||||
)
|
||||
_write_manifest(
|
||||
storage / "Cargo.toml",
|
||||
'[package]\nname = "internal-storage"\nversion = "0.1.0"\n'
|
||||
'[features]\nfast = []\n',
|
||||
)
|
||||
|
||||
result = introspect_cargo(tmp_path)
|
||||
expected_sources = {"crate:app"}
|
||||
if root_package:
|
||||
expected_sources.add("crate:root-app")
|
||||
assert {node["id"] for node in result["nodes"]} == expected_sources | {"crate:internal-storage"}
|
||||
assert len(result["edges"]) == len(expected_sources)
|
||||
assert {(edge["source"], edge["target"]) for edge in result["edges"]} == {
|
||||
(source, "crate:internal-storage") for source in expected_sources
|
||||
}
|
||||
for edge in result["edges"]:
|
||||
assert edge["source_file"] == ("Cargo.toml" if edge["source"] == "crate:root-app" else "app/Cargo.toml")
|
||||
assert edge["relation"] == "crate_depends_on"
|
||||
assert edge["context"] == "cargo_dependency"
|
||||
assert edge["confidence"] == "EXTRACTED"
|
||||
assert introspect_cargo(tmp_path) == result
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("root_declaration", "member_declaration", "expected"),
|
||||
[
|
||||
('db = { path = "db" }', 'db.workspace = true', True),
|
||||
('db = "1"', 'db.workspace = true', False),
|
||||
('db = { version = "1" }', 'db.workspace = true', False),
|
||||
('db = { git = "https://example.com/db" }', 'db.workspace = true', False),
|
||||
('db = { path = "missing" }', 'db.workspace = true', False),
|
||||
('db = { path = "app" }', 'db.workspace = true', False),
|
||||
('db = { path = 42 }', 'db.workspace = true', False),
|
||||
('db = false', 'db.workspace = true', False),
|
||||
('', 'db.workspace = true', False),
|
||||
('db = { path = "db" }', 'db.workspace = false', False),
|
||||
('db = { path = "db" }', 'db.workspace = "true"', False),
|
||||
('db = { path = "missing" }', 'db = { path = "../db" }', True),
|
||||
],
|
||||
)
|
||||
def test_cargo_inherited_dependency_requires_matching_local_path(
|
||||
tmp_path, root_declaration, member_declaration, expected
|
||||
):
|
||||
_write_manifest(
|
||||
tmp_path / "Cargo.toml",
|
||||
'[workspace]\nmembers = ["app", "db"]\n'
|
||||
'[workspace.dependencies]\n' + root_declaration + '\n',
|
||||
)
|
||||
for name in ("app", "db"):
|
||||
crate = tmp_path / name
|
||||
crate.mkdir()
|
||||
_write_manifest(
|
||||
crate / "Cargo.toml",
|
||||
f'[package]\nname = "{name}"\nversion = "0.1.0"\n'
|
||||
+ (f'[dependencies]\n{member_declaration}\n' if name == "app" else ""),
|
||||
)
|
||||
|
||||
result = introspect_cargo(tmp_path)
|
||||
assert {node["id"] for node in result["nodes"]} == {"crate:app", "crate:db"}
|
||||
assert [(edge["source"], edge["target"]) for edge in result["edges"]] == (
|
||||
[("crate:app", "crate:db")] if expected else []
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("root_path", [".", ""])
|
||||
def test_cargo_inherited_dependency_can_target_root_package(tmp_path, root_path):
|
||||
_write_manifest(
|
||||
tmp_path / "Cargo.toml",
|
||||
'[package]\nname = "db"\nversion = "0.1.0"\n'
|
||||
'[workspace]\nmembers = ["app"]\n'
|
||||
f'[workspace.dependencies]\ndb = {{ path = "{root_path}" }}\n',
|
||||
)
|
||||
app = tmp_path / "app"
|
||||
app.mkdir()
|
||||
_write_manifest(
|
||||
app / "Cargo.toml",
|
||||
'[package]\nname = "app"\nversion = "0.1.0"\n'
|
||||
'[dependencies]\ndb.workspace = true\n',
|
||||
)
|
||||
result = introspect_cargo(tmp_path)
|
||||
assert [(edge["source"], edge["target"]) for edge in result["edges"]] == [
|
||||
("crate:app", "crate:db")
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("workspace", ["", "workspace = false\n", "[workspace]\n", "[workspace]\ndependencies = false\n"])
|
||||
def test_cargo_missing_or_malformed_workspace_dependencies_are_ignored(tmp_path, workspace):
|
||||
_write_manifest(
|
||||
tmp_path / "Cargo.toml",
|
||||
workspace + '[package]\nname = "db"\nversion = "0.1.0"\n'
|
||||
'[dependencies]\ndb.workspace = true\n',
|
||||
)
|
||||
assert introspect_cargo(tmp_path)["edges"] == []
|
||||
Reference in new issue
Block a user