summaryrefslogtreecommitdiff
path: root/pkgs/build-support/rust/replace-workspace-values.py
blob: 64fd7151d46c57ab4a750f40f34107ff69e719a6 (plain)
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()