OXYGENHUB
# 87eb0f97c52d oxygenflow / images / rust / recipe.py
3.0 KiB · Python · last changed Build image recipe: a patched path dependency goes in whole 1 month ago source blame history raw
1
#!/usr/bin/env python3
2
"""Write a dependency recipe of a Cargo workspace (backlog F6b2).
3
4
The recipe is the workspace with its sources stubbed out: every member's
5
Cargo.toml (and the workspace roots its path dependencies inherit from),
6
the lock file, real build scripts, and an empty file for each other
7
target. A path dependency that is not a member (a patched third-party
8
crate under vendor/) is copied whole: it is built as a dependency. Building it compiles exactly the dependencies the real
9
workspace uses, with the same features, so the build image can hold them
10
prebuilt: jobs copy that target directory in and compile only their own
11
crates.
12
13
    recipe.py WORKSPACE OUT
14
"""
15
16
import json
17
import os
18
import shutil
19
import subprocess
20
import sys
21
22
23
def main():
24
    ws, out = os.path.abspath(sys.argv[1]), os.path.abspath(sys.argv[2])
25
    meta = json.loads(
26
        subprocess.run(
27
            ["cargo", "metadata", "--format-version", "1", "--locked", "--manifest-path", os.path.join(ws, "Cargo.toml")],
28
            check=True,
29
            capture_output=True,
30
        ).stdout
31
    )
32
    root = meta["workspace_root"]
33
    if os.path.exists(out):
34
        shutil.rmtree(out)
35
36
    def put(path, data=None):
37
        rel = os.path.relpath(path, root)
38
        if rel.startswith(".."):
39
            sys.exit(f"{path} is outside the workspace; a recipe holds only paths under it")
40
        dest = os.path.join(out, rel)
41
        os.makedirs(os.path.dirname(dest), exist_ok=True)
42
        if data is None:
43
            shutil.copy2(path, dest)
44
        else:
45
            with open(dest, "w") as f:
46
                f.write(data)
47
48
    for name in ("Cargo.toml", "Cargo.lock"):
49
        put(os.path.join(root, name))
50
    members = set(meta["workspace_members"])
51
    for p in meta["packages"]:
52
        if p["source"] is not None:
53
            continue
54
        manifest = p["manifest_path"]
55
        if p["id"] not in members:
56
            # A path dependency that is not a member: a patched third-party
57
            # crate under vendor/. It is a dependency, so it goes in whole.
58
            src_dir = os.path.dirname(manifest)
59
            rel = os.path.relpath(src_dir, root)
60
            if rel.startswith(".."):
61
                sys.exit(f"{src_dir} is outside the workspace; a recipe holds only paths under it")
62
            shutil.copytree(src_dir, os.path.join(out, rel), dirs_exist_ok=True)
63
            continue
64
        put(manifest)
65
        # Workspace roots above a path dependency, for `workspace = true`.
66
        d = os.path.dirname(os.path.dirname(manifest))
67
        while d.startswith(root) and d != root:
68
            top = os.path.join(d, "Cargo.toml")
69
            if os.path.exists(top):
70
                put(top)
71
            d = os.path.dirname(d)
72
        for t in p["targets"]:
73
            kind = t["kind"][0]
74
            src = t["src_path"]
75
            if kind == "custom-build":
76
                put(src)
77
            elif kind in ("lib", "rlib", "proc-macro"):
78
                put(src, "")
79
            else:
80
                put(src, "fn main() {}\n")
81
82
83
if __name__ == "__main__":
84
    main()