diff --git a/parser/parser.py b/parser/parser.py index 2fbb59d..0f79a6a 100644 --- a/parser/parser.py +++ b/parser/parser.py @@ -109,12 +109,15 @@ def merge_meta(idl: dict, meta_path: Path) -> dict: ) -def parse_meos(entry: Path, include_dir: Path) -> dict: +def parse_meos(entry: Path, include_dir: Path, + extra_headers: tuple[Path, ...] = ()) -> dict: index = clang.cindex.Index.create() + extra_dirs = sorted({h.parent.resolve() for h in extra_headers}) tu = index.parse(str(entry), args=[ "-x", "c", "-std=c11", f"-I{include_dir}", + *(f"-I{d}" for d in extra_dirs), "-DMEOS", # Define the MEOS ``UNUSED`` attribute macro on the command line so it is # always in scope: the amalgamated entry point may parse a header that @@ -131,6 +134,7 @@ def parse_meos(entry: Path, include_dir: Path) -> dict: # Collect all .h files belonging to the project own_files = {str(p.resolve()) for p in include_dir.glob("**/*.h")} + own_files.update(str(h.resolve()) for h in extra_headers) # First pass: build a mapping "anonymous struct location -> typedef name" typedef_map: dict[str, str] = {} @@ -199,21 +203,25 @@ def _dedup(items: list) -> list: return resolve_idl_types(idl, mappings_path) -def build_entry_point(headers_dir: Path) -> str: +def build_entry_point(headers_dir: Path, + extra_headers: tuple[Path, ...] = ()) -> str: lines = [] for h in sorted(headers_dir.glob("**/*.h")): lines.append(f'#include "{h.resolve()}"') + for h in extra_headers: + lines.append(f'#include "{h.resolve()}"') return "\n".join(lines) -def parse_all_headers(headers_dir: Path) -> dict: - entry_src = build_entry_point(headers_dir) +def parse_all_headers(headers_dir: Path, + extra_headers: tuple[Path, ...] = ()) -> dict: + entry_src = build_entry_point(headers_dir, extra_headers) with tempfile.NamedTemporaryFile(suffix=".h", mode="w", delete=False) as f: f.write(entry_src) tmp_path = f.name try: - return parse_meos(Path(tmp_path), headers_dir) + return parse_meos(Path(tmp_path), headers_dir, extra_headers) finally: os.unlink(tmp_path) \ No newline at end of file diff --git a/run.py b/run.py index 15c88a3..93b370c 100644 --- a/run.py +++ b/run.py @@ -60,12 +60,39 @@ def _source_commit(): return None +def _public_pgtypes_headers() -> tuple[Path, ...]: + """MobilityDB's vendored PostgreSQL base-type headers, when they sit outside HEADERS_DIR. + + MobilityDB keeps the PostgreSQL 18 base types in a `pgtypes/` library at the repo root and + installs `pgtypes.h` and the `pg_*.h` set into the include prefix, so they are as public as + `meos.h`. Reading an INSTALLED prefix therefore already covers them and this adds nothing; + reading the source tree points at `meos/include`, which does not contain them, and without + this the catalog silently loses the whole base-type surface (`interval_make`, the base I/O + functions, …) — the same ref then yields two different catalogs depending on the header root. + """ + root = Path(os.environ.get("MDB_SRC_ROOT", "./_mobilitydb")) / "pgtypes" + if not root.is_dir(): + return () + seen = {p.name for p in HEADERS_DIR.glob("**/*.h")} + candidates = sorted(root.glob("pg_*.h")) + [root / "pgtypes.h"] + headers = [h for h in candidates + # pg_config*.h carry PostgreSQL's build configuration, not API; the + # install set leaves them behind and so does the catalog. + if h.is_file() and not h.name.startswith("pg_config") + and h.name not in seen] + return tuple(headers) + + def main(): OUTPUT_DIR.mkdir(parents=True, exist_ok=True) # 1. Parse C headers print(f"[1/4] Parsing {HEADERS_DIR}...", file=sys.stderr) - idl = parse_all_headers(HEADERS_DIR) + pgtypes = _public_pgtypes_headers() + if pgtypes: + print(f" + {len(pgtypes)} vendored pgtypes headers from " + f"{pgtypes[0].parent}", file=sys.stderr) + idl = parse_all_headers(HEADERS_DIR, pgtypes) # 1b. Recover PG-vendored C types the preprocessor collapsed to int # (bool / int64 / Timestamp(Tz) / H3Index) from the header text.