Initial commit of dump-interfaces
authorBen Gamari <ben@smart-cactus.org>
Mon, 1 Jul 2019 15:52:47 +0000 (11:52 -0400)
committerBen Gamari <ben@smart-cactus.org>
Mon, 1 Jul 2019 16:05:07 +0000 (12:05 -0400)
This is the initial commit of a utility that I have been using to
compare the user-facing interfaces exposed by GHC across releases

utils/dump-interfaces.py [new file with mode: 0644]

diff --git a/utils/dump-interfaces.py b/utils/dump-interfaces.py
new file mode 100644 (file)
index 0000000..c5888ea
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+"""
+This is a handy utility for comparing the interfaces exposed by the core
+library included in GHC's global package database. Given a compiler and a set
+of packages it will produce a directory containing dumps of the :browse output
+from each of the packages' exposed modules. These directories can be compared
+with, e.g., meld with a reasonable number of false differences.
+"""
+
+from pathlib import Path
+import subprocess
+from typing import TextIO, Set
+import re
+
+CORE_PACKAGES = [
+    "base",
+    "ghc-prim",
+    "template-haskell",
+    "ghc-boot",
+    "ghc-boot-th"
+]
+
+exposed_modules_re = re.compile('exposed-modules:\s*((?:(?:[A-Z][A-Za-z0-9_]*\.)*(?:[A-Z][A-Za-z0-9_]*)\s*)*)')
+
+def dump_module(out: TextIO, ghc: Path, mod: str):
+    print(f'  Dumping {mod}...')
+    subprocess.run([ghc, '--interactive', '-dppr-cols=9999', '-v0'],
+                   input=f':bro {mod}',
+                   stdout=out,
+                   encoding='UTF-8',
+                   check=True)
+
+def dump_package(out_dir: Path, ghc: Path, pkg: str):
+    pkg_out = out_dir / pkg
+    pkg_out.mkdir(exist_ok=True, parents=True)
+
+    modules = get_modules(ghc, pkg)
+    print(f'Dumping {len(modules)} exposed modules from {pkg}...')
+    for mod in modules:
+        mod_out = pkg_out / f"{mod}.txt"
+        dump_module(mod_out.open('w'), ghc, mod)
+
+def get_modules(ghc: Path, pkg: str) -> Set[str]:
+    ghc_pkg = ghc.parent / "ghc-pkg"
+    out = subprocess.check_output([ghc_pkg, 'describe', pkg], encoding='UTF-8')
+    m = exposed_modules_re.search(out)
+    return set(m.group(1).split())
+
+def main() -> None:
+    import argparse
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-c', '--compiler', type=Path, required=True,
+                        help='GHC executable')
+    parser.add_argument('-o', '--output', type=Path, default=Path('interfaces'),
+                        help='Output directory')
+    parser.add_argument('package', nargs='*', help='Packages to dump')
+    args = parser.parse_args()
+
+    packages = args.package
+    if packages == []:
+        packages = CORE_PACKAGES
+
+    for pkg in packages:
+        dump_package(args.output, args.compiler, pkg)
+
+if __name__ == "__main__":
+    main()