#!/usr/bin/env python3 """Write a dependency recipe of a Cargo workspace (backlog F6b2). The recipe is the workspace with its sources stubbed out: every local package's Cargo.toml (and the workspace roots its path dependencies inherit from), the lock file, real build scripts, and an empty file for each other target. Building it compiles exactly the dependencies the real workspace uses, with the same features, so the build image can hold them prebuilt: jobs copy that target directory in and compile only their own crates. recipe.py WORKSPACE OUT """ import json import os import shutil import subprocess import sys def main(): ws, out = os.path.abspath(sys.argv[1]), os.path.abspath(sys.argv[2]) meta = json.loads( subprocess.run( ["cargo", "metadata", "--format-version", "1", "--locked", "--manifest-path", os.path.join(ws, "Cargo.toml")], check=True, capture_output=True, ).stdout ) root = meta["workspace_root"] if os.path.exists(out): shutil.rmtree(out) def put(path, data=None): rel = os.path.relpath(path, root) if rel.startswith(".."): sys.exit(f"{path} is outside the workspace; a recipe holds only paths under it") dest = os.path.join(out, rel) os.makedirs(os.path.dirname(dest), exist_ok=True) if data is None: shutil.copy2(path, dest) else: with open(dest, "w") as f: f.write(data) for name in ("Cargo.toml", "Cargo.lock"): put(os.path.join(root, name)) for p in meta["packages"]: if p["source"] is not None: continue manifest = p["manifest_path"] put(manifest) # Workspace roots above a path dependency, for `workspace = true`. d = os.path.dirname(os.path.dirname(manifest)) while d.startswith(root) and d != root: top = os.path.join(d, "Cargo.toml") if os.path.exists(top): put(top) d = os.path.dirname(d) for t in p["targets"]: kind = t["kind"][0] src = t["src_path"] if kind == "custom-build": put(src) elif kind in ("lib", "rlib", "proc-macro"): put(src, "") else: put(src, "fn main() {}\n") if __name__ == "__main__": main()