OXYGENHUB
◆ push-uses-login oxygenflow / images / rust / recipe.py
3.1 KiB · Python · last changed Build image: coverage tools; recipe copies any vendor/ 1 month ago source blame history raw
eba589f8 Andy McCall ↶ 1
#!/usr/bin/env python3
2
"""Write a dependency recipe of a Cargo workspace (backlog F6b2).
3
87eb0f97 Andy McCall ↶ 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
9c8820a5 Andy McCall ↶ 7
target. A third-party crate patched in from vendor/ is copied whole:
8
registry crates with real code depend on it. Building it compiles exactly the dependencies the real
eba589f8 Andy McCall ↶ 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))
87eb0f97 Andy McCall ↶ 50
    members = set(meta["workspace_members"])
eba589f8 Andy McCall ↶ 51
    for p in meta["packages"]:
52
        if p["source"] is not None:
53
            continue
54
        manifest = p["manifest_path"]
9c8820a5 Andy McCall ↶ 55
        src_dir = os.path.dirname(manifest)
56
        rel = os.path.relpath(src_dir, root)
fad61521 Andy McCall ↶ 57
        if p["id"] not in members and "vendor" in rel.split(os.sep):
58
            # A third-party crate patched in from a vendor/ directory (the
59
            # workspace's own, or a pinned subrepo's, as oxygenhub patches
60
            # from deps/…/oxygen/vendor/): registry crates
9c8820a5 Andy McCall ↶ 61
            # with real code depend on it, so it goes in whole. (A pinned
62
            # subrepo's crates under deps/ are stubbed like the members:
63
            # only stubbed code depends on them.)
87eb0f97 Andy McCall ↶ 64
            shutil.copytree(src_dir, os.path.join(out, rel), dirs_exist_ok=True)
65
            continue
eba589f8 Andy McCall ↶ 66
        put(manifest)
67
        # Workspace roots above a path dependency, for `workspace = true`.
68
        d = os.path.dirname(os.path.dirname(manifest))
69
        while d.startswith(root) and d != root:
70
            top = os.path.join(d, "Cargo.toml")
71
            if os.path.exists(top):
72
                put(top)
73
            d = os.path.dirname(d)
74
        for t in p["targets"]:
75
            kind = t["kind"][0]
76
            src = t["src_path"]
77
            if kind == "custom-build":
78
                put(src)
79
            elif kind in ("lib", "rlib", "proc-macro"):
80
                put(src, "")
81
            else:
82
                put(src, "fn main() {}\n")
83
84
85
if __name__ == "__main__":
86
    main()