| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- #!/usr/bin/env python3
- """gir_verify.py - check that every C identifier gir2def.py would emit
- actually exists in the installed shared library.
- It parses the .gir, collects the `c:identifier` of every constructor,
- method and function that would be generated, then builds the set of
- exported dynamic symbols from the libraries pkg-config links for the
- namespace and reports any identifier that is not there.
- Symbol versions (the `@@LIBADWAITA_1_0` suffixes) are stripped.
- Usage:
- tools/gir_verify.py Gtk-4.0
- tools/gir_verify.py Adw-1 --pkg libadwaita-1
- Exit status is non-zero if anything is missing.
- """
- import argparse
- import glob
- import os
- import subprocess
- import sys
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
- import gir2def # noqa: E402
- import xml.etree.ElementTree as ET # noqa: E402
- # GIR namespace -> pkg-config package that provides its libraries.
- PKG = {
- "Gtk-4.0": "gtk4",
- "Gdk-4.0": "gtk4",
- "Gsk-4.0": "gtk4",
- "GLib-2.0": "glib-2.0",
- "GObject-2.0": "gobject-2.0",
- "Gio-2.0": "gio-2.0",
- "Adw-1": "libadwaita-1",
- "Pango-1.0": "pango",
- "PangoCairo-1.0": "pangocairo",
- "GdkPixbuf-2.0": "gdk-pixbuf-2.0",
- "Graphene-1.0": "graphene-1.0",
- "cairo-1.0": "cairo",
- }
- def run(cmd):
- return subprocess.run(cmd, capture_output=True, text=True).stdout
- def lib_dirs(pkg):
- dirs = []
- for tok in run(["pkg-config", "--libs-only-L", pkg]).split():
- if tok.startswith("-L"):
- dirs.append(tok[2:])
- ld = run(["pkg-config", "--variable=libdir", pkg]).strip()
- if ld:
- dirs.append(ld)
- dirs += ["/usr/lib/x86_64-linux-gnu", "/usr/lib", "/lib/x86_64-linux-gnu",
- "/lib"]
- return dirs
- def find_lib(name, dirs):
- for d in dirs:
- for pat in ("lib%s.so", "lib%s.so.*"):
- hits = sorted(glob.glob(os.path.join(d, pat % name)))
- if hits:
- return hits[0]
- return None
- def libs_for(pkg):
- names = []
- for tok in run(["pkg-config", "--libs-only-l", pkg]).split():
- if tok.startswith("-l"):
- names.append(tok[2:])
- dirs = lib_dirs(pkg)
- found = []
- for n in names:
- p = find_lib(n, dirs)
- if p:
- found.append(p)
- return found
- def symbols(paths):
- syms = set()
- for p in paths:
- for line in run(["nm", "-D", "--defined-only", p]).splitlines():
- parts = line.split()
- if len(parts) >= 3 and parts[1] in ("T", "W", "i"):
- syms.add(parts[2].split("@")[0])
- return syms
- def main():
- ap = argparse.ArgumentParser()
- ap.add_argument("namespace")
- ap.add_argument("--gir-dir", default="/usr/share/gir-1.0")
- ap.add_argument("--pkg", help="override the pkg-config package")
- ap.add_argument("--allowlist",
- default=os.path.join(os.path.dirname(
- os.path.abspath(__file__)),
- "gir_verify_allowlist.txt"),
- help="known macro/deprecated identifiers to ignore")
- args = ap.parse_args()
- gir = os.path.join(args.gir_dir, args.namespace + ".gir")
- if not os.path.exists(gir):
- sys.stderr.write("gir_verify: not found: %s\n" % gir)
- return 2
- pkg = args.pkg or PKG.get(args.namespace)
- if not pkg:
- sys.stderr.write("gir_verify: no package mapping for %s; use --pkg\n"
- % args.namespace)
- return 2
- paths = libs_for(pkg)
- libs = paths
- if not libs:
- sys.stderr.write("gir_verify: no libraries found for %s\n" % pkg)
- return 2
- root = ET.parse(gir).getroot()
- ns = gir2def.Namespace(root)
- ids = gir2def.collect_identifiers(ns)
- have = symbols(libs)
- allowed = set()
- if os.path.exists(args.allowlist):
- with open(args.allowlist) as f:
- for line in f:
- line = line.split("#", 1)[0].strip()
- if line:
- allowed.add(line)
- missing = [i for i in ids if i not in have]
- known = [m for m in missing if m in allowed]
- unexpected = [m for m in missing if m not in allowed]
- print("gir_verify: %s: %d identifiers, %d known-macro, %d unexpected "
- "(libs: %s)" % (args.namespace, len(ids), len(known),
- len(unexpected),
- ", ".join(os.path.basename(l) for l in libs)))
- for m in known:
- print(" known %s" % m)
- for m in unexpected:
- print(" MISSING %s" % m)
- return 1 if unexpected else 0
- if __name__ == "__main__":
- sys.exit(main())
|