kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

lib_reloc_defined_prefixes.py (3531B)


      1 #!/usr/bin/env python3
      2 """Print disallowed global definitions after a relocatable archive link.
      3 
      4 The check extracts every object from libkit.a, links them into one
      5 relocatable object, and reports any remaining externally visible definition
      6 whose C symbol name is not in libkit's public prefix set.
      7 """
      8 
      9 import argparse
     10 import os
     11 import pathlib
     12 import subprocess
     13 import sys
     14 import tempfile
     15 
     16 
     17 ALLOWED_PREFIXES = ("Kit", "kit_", "KIT")
     18 
     19 
     20 def run(args: list[str], **kwargs) -> subprocess.CompletedProcess[str]:
     21     return subprocess.run(args, check=True, text=True, **kwargs)
     22 
     23 
     24 def nm_lines(path: pathlib.Path, nm: str) -> list[str]:
     25     return run([nm, str(path)], capture_output=True).stdout.splitlines()
     26 
     27 
     28 def parse_nm_defined_globals(lines: list[str]) -> set[str]:
     29     syms: set[str] = set()
     30     for line in lines:
     31         line = line.rstrip()
     32         if not line or line.endswith(":"):
     33             continue
     34         parts = line.split(None, 2)
     35         if len(parts) != 3:
     36             continue
     37         _, type_, name = parts
     38         if type_ != "U" and type_.isupper():
     39             syms.add(name)
     40     return syms
     41 
     42 
     43 def public_c_name(sym: str) -> str:
     44     if sym.startswith("_"):
     45         stripped = sym[1:]
     46         if stripped.startswith(ALLOWED_PREFIXES):
     47             return stripped
     48     return sym
     49 
     50 
     51 def is_allowed(sym: str) -> bool:
     52     return public_c_name(sym).startswith(ALLOWED_PREFIXES)
     53 
     54 
     55 def link_relocatable(
     56     archive: pathlib.Path, output: pathlib.Path, ar: str, cc: str
     57 ) -> None:
     58     archive = archive.resolve()
     59     output = output.resolve()
     60     with tempfile.TemporaryDirectory(prefix="kit-lib-reloc.") as td:
     61         work = pathlib.Path(td)
     62         run([ar, "x", str(archive)], cwd=work)
     63         objects = sorted(work.glob("*.o"))
     64         if not objects:
     65             raise RuntimeError(f"{archive} did not contain any .o members")
     66 
     67         args = [cc, "-r", "-nostdlib", "-o", str(output)]
     68         args.extend(str(obj) for obj in objects)
     69         output.parent.mkdir(parents=True, exist_ok=True)
     70         run(args, capture_output=True)
     71 
     72 
     73 def disallowed_reloc_defs(
     74     archive: pathlib.Path, output: pathlib.Path, ar: str, cc: str, nm: str
     75 ) -> list[str]:
     76     link_relocatable(archive, output, ar, cc)
     77     return sorted(
     78         public_c_name(sym)
     79         for sym in parse_nm_defined_globals(nm_lines(output, nm))
     80         if not is_allowed(sym)
     81     )
     82 
     83 
     84 def main() -> int:
     85     ap = argparse.ArgumentParser(description=__doc__)
     86     ap.add_argument("archive", help="Path to libkit.a")
     87     ap.add_argument(
     88         "--output",
     89         default="build/libkit.reloc.o",
     90         help="Path for the temporary relocatable-link output",
     91     )
     92     ap.add_argument("--ar", default=os.environ.get("AR", "ar"), help="ar binary")
     93     ap.add_argument("--cc", default=os.environ.get("CC", "cc"), help="C compiler driver")
     94     ap.add_argument("--nm", default=os.environ.get("NM", "nm"), help="nm binary")
     95     args = ap.parse_args()
     96 
     97     try:
     98         bad = disallowed_reloc_defs(
     99             pathlib.Path(args.archive),
    100             pathlib.Path(args.output),
    101             args.ar,
    102             args.cc,
    103             args.nm,
    104         )
    105     except (FileNotFoundError, RuntimeError, subprocess.CalledProcessError) as e:
    106         print(f"libkit relocatable symbol check failed: {e}", file=sys.stderr)
    107         if isinstance(e, subprocess.CalledProcessError) and e.stderr:
    108             print(e.stderr, file=sys.stderr, end="")
    109         return 2
    110 
    111     for sym in bad:
    112         print(sym)
    113     return 0
    114 
    115 
    116 if __name__ == "__main__":
    117     sys.exit(main())