1# This script implements the workspace inheritance mechanism described
2# here: https://doc.rust-lang.org/cargo/reference/workspaces.html#the-package-table
3#
4# Please run `mypy --strict`, `black`, and `isort --profile black` on this after editing, thanks!
5
6import sys
7from typing import Any
8
9import tomli
10import tomli_w
11
12
13def load_file(path: str) -> dict[str, Any]:
14 with open(path, "rb") as f:
15 return tomli.load(f)
16
17
18# This replicates the dependency merging logic from Cargo.
19# See `inner_dependency_inherit_with`:
20# https://github.com/rust-lang/cargo/blob/4de0094ac78743d2c8ff682489e35c8a7cafe8e4/src/cargo/util/toml/mod.rs#L982
21def replace_key(
22 workspace_manifest: dict[str, Any], table: dict[str, Any], section: str, key: str
23) -> bool:
24 if (
25 isinstance(table[key], dict)
26 and "workspace" in table[key]
27 and table[key]["workspace"] is True
28 ):
29 print("replacing " + key)
30
31 local_dep = table[key]
32 del local_dep["workspace"]
33
34 workspace_dep = workspace_manifest[section][key]
35
36 if section == "dependencies":
37 if isinstance(workspace_dep, str):
38 workspace_dep = {"version": workspace_dep}
39
40 final: dict[str, Any] = workspace_dep.copy()
41
42 merged_features = local_dep.pop("features", []) + workspace_dep.get("features", [])
43 if merged_features:
44 final["features"] = merged_features
45
46 local_default_features = local_dep.pop(
47 "default-features",
48 local_dep.pop("default_features", None)
49 )
50 workspace_default_features = workspace_dep.get(
51 "default-features",
52 workspace_dep.get("default_features")
53 )
54
55 if not workspace_default_features and local_default_features:
56 final["default-features"] = True
57
58 optional = local_dep.pop("optional", False)
59 if optional:
60 final["optional"] = True
61
62 if "package" in local_dep:
63 final["package"] = local_dep.pop("package")
64
65 if local_dep:
66 raise Exception(f"Unhandled keys in inherited dependency {key}: {local_dep}")
67
68 table[key] = final
69 elif section == "package":
70 table[key] = workspace_dep
71
72 return True
73
74 return False
75
76
77def replace_dependencies(
78 workspace_manifest: dict[str, Any], root: dict[str, Any]
79) -> bool:
80 changed = False
81
82 for key in ["dependencies", "dev-dependencies", "build-dependencies"]:
83 if key in root:
84 for k in root[key].keys():
85 changed |= replace_key(workspace_manifest, root[key], "dependencies", k)
86
87 return changed
88
89
90def main() -> None:
91 top_cargo_toml = load_file(sys.argv[2])
92
93 if "workspace" not in top_cargo_toml:
94 # If top_cargo_toml is not a workspace manifest, then this script was probably
95 # ran on something that does not actually use workspace dependencies
96 print(f"{sys.argv[2]} is not a workspace manifest, doing nothing.")
97 return
98
99 crate_manifest = load_file(sys.argv[1])
100 workspace_manifest = top_cargo_toml["workspace"]
101
102 if "workspace" in crate_manifest:
103 return
104
105 changed = False
106
107 for key in crate_manifest["package"].keys():
108 changed |= replace_key(
109 workspace_manifest, crate_manifest["package"], "package", key
110 )
111
112 changed |= replace_dependencies(workspace_manifest, crate_manifest)
113
114 if "target" in crate_manifest:
115 for key in crate_manifest["target"].keys():
116 changed |= replace_dependencies(
117 workspace_manifest, crate_manifest["target"][key]
118 )
119
120 if (
121 "lints" in crate_manifest
122 and "workspace" in crate_manifest["lints"]
123 and crate_manifest["lints"]["workspace"] is True
124 ):
125 crate_manifest["lints"] = workspace_manifest["lints"]
126 changed = True
127
128 if not changed:
129 return
130
131 with open(sys.argv[1], "wb") as f:
132 tomli_w.dump(crate_manifest, f)
133
134
135if __name__ == "__main__":
136 main()