#!/usr/bin/env python3 # # Helper script: makes sure that the crates as listed in the workspace Cargo.toml are # topologically sorted from lowest-level to highest level. # # We depend on this property for publishing to crates.io. e.g. # see # https://blog.iany.me/2020/10/gotchas-to-publish-rust-crates-in-a-workspace/#cyclic-dependencies import toml.decoder import sys import os.path import os TOPDIR = os.path.split(os.path.dirname(sys.argv[0]))[0] CRATEDIR = os.path.join(TOPDIR, "crates") WORKSPACE_TOML = os.path.join(TOPDIR, "Cargo.toml") def crate_dirs(): return set(name for name in os.listdir(CRATEDIR) if not name.startswith(".")) def strip_prefix(s, prefix): if s.startswith(prefix): return s[len(prefix) :] else: return s def crate_list(): t = toml.decoder.load(WORKSPACE_TOML) return list( strip_prefix(name, "crates/") for name in t["workspace"]["members"] if not (name.startswith("examples/") or name.startswith("maint/")) ) CRATE_LIST = crate_list() CRATE_DIRS = crate_dirs() def check_disjoint(): listed_crates = set(crate_list()) if listed_crates != CRATE_DIRS: print( "The crates in the crates/ directory do not match the ones in Cargo.toml!" ) print("Problem crates", listed_crates ^ CRATE_DIRS) return True else: return False def get_path(dep): try: return dep["path"] except (KeyError, TypeError): return None def get_dependencies(cratename): fname = os.path.join(CRATEDIR, cratename, "Cargo.toml") t = toml.decoder.load(fname) deps = set() # We need to look at dev-dependencies too, and disallow false cyclic # dependencies through dev-dependencies. We might be able to relax this if # crate publishing is updated to ignore dev-dependencies # . for secname in ["dependencies", "dev-dependencies"]: sec = t.get(secname) if not sec: continue for key, val in sec.items(): path = get_path(val) if path: d, p = os.path.split(val["path"]) if d == "..": assert p in CRATE_DIRS deps.add(p) return deps def get_dependency_graph(): all_deps = {} for crate in CRATE_DIRS: all_deps[crate] = get_dependencies(crate) return all_deps GRAPH = get_dependency_graph() def check_consistency(order, graph): """Make sure that `order` is topologically sorted from bottom to top, according to `graph`. """ seen_so_far = set() problems = False for crate in order: for dependent in graph[crate]: if dependent not in seen_so_far: print( f"{crate} dependency on {dependent} is not reflected in Cargo.toml" ) problems = True seen_so_far.add(crate) return problems if __name__ == "__main__": if check_disjoint(): sys.exit(1) if check_consistency(CRATE_LIST, GRAPH): sys.exit(1) print("Everything seems okay")