#!/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 member'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. A third-party crate patched in from vendor/ is copied whole: registry crates with real code depend on it. 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)) members = set(meta["workspace_members"]) for p in meta["packages"]: if p["source"] is not None: continue manifest = p["manifest_path"] src_dir = os.path.dirname(manifest) rel = os.path.relpath(src_dir, root) if p["id"] not in members and "vendor" in rel.split(os.sep): # A third-party crate patched in from a vendor/ directory (the # workspace's own, or a pinned subrepo's, as oxygenhub patches # from deps/…/oxygen/vendor/): registry crates # with real code depend on it, so it goes in whole. (A pinned # subrepo's crates under deps/ are stubbed like the members: # only stubbed code depends on them.) shutil.copytree(src_dir, os.path.join(out, rel), dirs_exist_ok=True) continue 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()