#!/usr/bin/env python3 import re import sys import copy from pathlib import Path from collections import defaultdict crate_deps = dict() crate_deps_reverse = defaultdict(set) CHECK_DEV_DEPS = sys.argv[1] == '--dev' if len(sys.argv) > 1 else False LOCAL_DEP_RE = re.compile(r'{.*path\s*=\s*"([^"]*)"') TOP_DIR = Path('.').resolve() members = [] with Path('Cargo.toml').open() as top_cargo: parsing_members = False for line in top_cargo.readlines(): if parsing_members: if line.strip() == ']': break if line.strip().startswith('"'): members.append(Path(line.split('"')[1])) elif line.strip().startswith("'"): members.append(Path(line.split("'")[1])) elif [w.strip() for w in line.split('=')] == ['members', '[']: parsing_members = True if len(members) == 0: print("Failed to parse members from ./Cargo.toml", file=sys.stderr) sys.exit(127) for crate_dir in members: crate_deps[crate_dir] = set() with (crate_dir / 'Cargo.toml').open() as f: for line in f.readlines(): if line.strip() == '[dev-dependencies]'.strip() and not CHECK_DEV_DEPS: break match = LOCAL_DEP_RE.search(line) if match is not None: dep_dir = (crate_dir / match.group(1)).resolve().relative_to(TOP_DIR) crate_deps[crate_dir].add(dep_dir) crate_deps_reverse[dep_dir].add(crate_dir) has_missing_members = False for dep in set(dep for deps in crate_deps.values() for dep in deps): if dep not in crate_deps: has_missing_members = True print("Member {} is missing in ./Cargo.toml".format(dep), file=sys.stderr) if has_missing_members: sys.exit(127) remember_crate_deps = copy.deepcopy(crate_deps) while len(crate_deps) > 0: available = [crate for (crate, deps) in crate_deps.items() if len(deps) == 0] if len(available) == 0: print("Loop dependencies detected:\n", file=sys.stderr) for (k, v) in sorted(crate_deps.items(), key=lambda t: len(t[1])): print("|- ", k, file=sys.stderr) for e in v: print("| |- ", e, file=sys.stderr) sys.exit(127) for crate in available: print(crate) for crate_user in crate_deps_reverse[crate]: crate_deps[crate_user].remove(crate) del crate_deps[crate] crate_deps = remember_crate_deps for crate in members: if len(crate_deps[crate]) > 0: print("Workspace members are not sorted by dependencies, see the topological sorted printed above", file=sys.stderr) print("{} depends on following members which come after it:".format(crate), file=sys.stderr) for e in crate_deps[crate]: print(" |- ", e, file=sys.stderr) sys.exit(127) for crate_user in crate_deps_reverse[crate]: crate_deps[crate_user].remove(crate)