Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions parser/enrich.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,12 @@ def classify_category(fn: dict) -> str:
return "transformation"


# MEOS splits its API: the public *user* surface (meos.h + the public type
# headers) and the *internal* programmer surface. The latter is type-erased
# (``Datum``-generic), undocumented for end users, and must not be projected
# onto a network service — it is policy-excluded, like lifecycle/index.
_INTERNAL_FILES = {"meos_internal.h", "meos_internal_geo.h"}
# MEOS splits its API into a public *user* surface and an *internal* programmer
# surface. The single authored signal for that split is the doxygen `@ingroup`:
# a function is public iff it carries a group that is not `meos_internal_*`; a
# function with no group, or a `meos_internal_*` group, is internal. This ties
# the binding/network surface to the reference manual — the two derive from one
# human-authored tag and cannot drift.


def _outparam(fn: dict, enums: set, type_encodings: dict):
Expand Down Expand Up @@ -517,7 +518,9 @@ def enrich_idl(idl: dict) -> dict:
type_encodings = build_type_encodings(functions, opaque_names)

for fn in functions:
fn["api"] = ("internal" if fn.get("file") in _INTERNAL_FILES
group = fn.get("group")
fn["api"] = ("internal"
if (not group or group.startswith("meos_internal_"))
else "public")
fn["category"] = classify_category(fn)
network, wire = assess(fn, type_encodings, enum_names)
Expand Down
21 changes: 11 additions & 10 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,17 @@ def main():
for fn, pn, reason in out_drift:
print(f" {fn}({pn}) — {reason}", file=sys.stderr)

# 1e. Derive service-projection metadata (category / encodings / network).
# 1e. Attach the doxygen @ingroup groups BEFORE enrich: the catalog `api`
# field derives from the group (public unless the group is
# `meos_internal_*` or absent), so the group must already be attached when
# enrich computes api and the api-dependent network projection.
_grp_root = Path(os.environ.get("MDB_SRC_ROOT", "./_mobilitydb"))
if (_grp_root / "meos" / "src").exists():
idl, ngrp = attach_groups(idl, _grp_root / "meos" / "src",
_grp_root / "pgtypes")
print(f" attached {ngrp} doxygen @ingroup groups", file=sys.stderr)

# 1f. Derive service-projection metadata (category / encodings / network).
# Runs before the merge so manual annotations override the heuristics.
idl = enrich_idl(idl)

Expand Down Expand Up @@ -206,15 +216,6 @@ def main():
for fn, pn, reason in ba_drift:
print(f" {fn}({pn}) — {reason}", file=sys.stderr)

# 5. Attach the doxygen module group (@ingroup) from the vendored source, so
# bindings organize their generated surface like the reference manual. The
# exported base-type functions (text_out, date_in, timestamp_cmp_internal,
# …) live in the vendored `pgtypes/` tree at the repo root and carry
# `@ingroup meos_base_*` there, so scan it alongside meos/src.
PGTYPES_SRC = SRC_ROOT / "pgtypes"
if MEOS_SRC.exists():
idl, ngrp = attach_groups(idl, MEOS_SRC, PGTYPES_SRC)
print(f"[5/5] Attached {ngrp} doxygen @ingroup groups", file=sys.stderr)

# Surface any forward-declared external ABI struct pointer in the API, so a
# new one is classified explicitly instead of diverging per binding.
Expand Down
20 changes: 17 additions & 3 deletions tests/test_enrich.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def fn(name, ret, *params):
return {
"name": name,
"file": "meos.h",
# A public doxygen group: api derives from @ingroup, so a fixture
# function is public unless given a meos_internal_* group (or none).
"group": "meos_temporal",
"returnType": {"c": ret, "canonical": ret},
"params": [{"name": n, "cType": t, "canonical": t} for t, n in params],
}
Expand Down Expand Up @@ -59,10 +62,10 @@ def fn(name, ret, *params):
("const struct Box *", "box"), ("int", "maxdd")),
fn("weird_in", "struct Weird *",
("const char *", "str"), ("int", "basetype")),
# An otherwise-exposable function living in the internal header: it must
# be policy-excluded (api=internal), like the programmer Datum API.
# An otherwise-exposable function carrying an internal doxygen group: it
# must be policy-excluded (api=internal), like the programmer Datum API.
dict(fn("internal_op", "struct Temporal *", (T, "temp")),
file="meos_internal.h"),
file="meos_internal.h", group="meos_internal_temporal"),
# Scalar out-parameter accessor: bool f(.., int *result) — the value is
# returned through the trailing out-param, the bool is a presence flag.
fn("setspan_value_n", "int",
Expand Down Expand Up @@ -227,6 +230,17 @@ def test_public_default(self):
self.assertEqual(self.fns["temporal_eq"]["api"], "public")
self.assertTrue(self.fns["temporal_eq"]["network"]["exposable"])

def test_no_group_is_internal(self):
# A function with no doxygen @ingroup is internal (not documented,
# not part of the public surface), even in a public header.
idl = enrich_idl({
"functions": [{k: v for k, v in fn("ungrouped_op", "int",
(T, "temp")).items()
if k != "group"}],
"structs": [dict(s) for s in STRUCTS], "enums": [],
})
self.assertEqual(idl["functions"][0]["api"], "internal")

def test_scalar_outparam_projected_as_result(self):
f = self.fns["setspan_value_n"]
self.assertTrue(f["network"]["exposable"])
Expand Down
Loading