check_toposort
1 #!/usr/bin/env python3 2 # 3 # Helper script: makes sure that the crates as listed in the workspace Cargo.toml are 4 # topologically sorted from lowest-level to highest level. 5 # 6 # We depend on this property for publishing to crates.io. e.g. 7 # see 8 # https://blog.iany.me/2020/10/gotchas-to-publish-rust-crates-in-a-workspace/#cyclic-dependencies 9 10 import toml.decoder 11 import sys 12 import os.path 13 import os 14 15 TOPDIR = os.path.split(os.path.dirname(sys.argv[0]))[0] 16 CRATEDIR = os.path.join(TOPDIR, "crates") 17 WORKSPACE_TOML = os.path.join(TOPDIR, "Cargo.toml") 18 19 20 def crate_dirs(): 21 return set(name for name in os.listdir(CRATEDIR) if not name.startswith(".")) 22 23 24 def strip_prefix(s, prefix): 25 if s.startswith(prefix): 26 return s[len(prefix) :] 27 else: 28 return s 29 30 31 def crate_list(): 32 t = toml.decoder.load(WORKSPACE_TOML) 33 return list( 34 strip_prefix(name, "crates/") 35 for name in t["workspace"]["members"] 36 if not (name.startswith("examples/") or name.startswith("maint/")) 37 ) 38 39 40 CRATE_LIST = crate_list() 41 CRATE_DIRS = crate_dirs() 42 43 44 def check_disjoint(): 45 listed_crates = set(crate_list()) 46 if listed_crates != CRATE_DIRS: 47 print( 48 "The crates in the crates/ directory do not match the ones in Cargo.toml!" 49 ) 50 print("Problem crates", listed_crates ^ CRATE_DIRS) 51 return True 52 else: 53 return False 54 55 56 def get_path(dep): 57 try: 58 return dep["path"] 59 except (KeyError, TypeError): 60 return None 61 62 63 def get_dependencies(cratename): 64 fname = os.path.join(CRATEDIR, cratename, "Cargo.toml") 65 t = toml.decoder.load(fname) 66 deps = set() 67 # We need to look at dev-dependencies too, and disallow false cyclic 68 # dependencies through dev-dependencies. We might be able to relax this if 69 # crate publishing is updated to ignore dev-dependencies 70 # <https://github.com/rust-lang/cargo/issues/4242>. 71 for secname in ["dependencies", "dev-dependencies"]: 72 sec = t.get(secname) 73 if not sec: 74 continue 75 for key, val in sec.items(): 76 path = get_path(val) 77 if path: 78 d, p = os.path.split(val["path"]) 79 if d == "..": 80 assert p in CRATE_DIRS 81 deps.add(p) 82 return deps 83 84 85 def get_dependency_graph(): 86 all_deps = {} 87 for crate in CRATE_DIRS: 88 all_deps[crate] = get_dependencies(crate) 89 return all_deps 90 91 92 GRAPH = get_dependency_graph() 93 94 95 def check_consistency(order, graph): 96 """Make sure that `order` is topologically sorted from bottom to 97 top, according to `graph`. 98 """ 99 seen_so_far = set() 100 problems = False 101 for crate in order: 102 for dependent in graph[crate]: 103 if dependent not in seen_so_far: 104 print( 105 f"{crate} dependency on {dependent} is not reflected in Cargo.toml" 106 ) 107 problems = True 108 seen_so_far.add(crate) 109 110 return problems 111 112 113 if __name__ == "__main__": 114 if check_disjoint(): 115 sys.exit(1) 116 if check_consistency(CRATE_LIST, GRAPH): 117 sys.exit(1) 118 print("Everything seems okay")