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