Files
rocksdb/tools/c_api_gen/check_api_completeness.py
T
zaidoon 1cec28d82d Finish C API code generation (continues #14572) (#14868)
Summary:
This continues and finishes **https://github.com/facebook/rocksdb/issues/14572** ("Add semi-automated code generation for RocksDB C API bindings") by xingbowang. The original author is unavailable to finish it, so I've taken it over. **All 13 of the original commits are preserved** (this branch was created from the PR head and builds on top of it — `git log` shows the original `Xingbo Wang` authorship intact); my follow-up work is in the commits prefixed `C API codegen:`.

The underlying design is unchanged and is the original author's: hand-written source templates (`tools/c_api_gen/c_base.h` / `c_base.cc`) plus two generators (auto-discovery from the C++ headers + a spec-driven generator) are inlined into a single, self-contained, `generated` `include/rocksdb/c.h` and `db/c.cc`. This grows the public C API by **668 functions** while keeping `c.h` a single includable header with no `-I` requirement (so `bindgen` and other FFI tools keep working unchanged).

This branch reconciles the PR with ~4 months of `main` and addresses the outstanding review feedback (clang-tidy bot, the automated code review, the `c.h` self-containedness discussion, and pdillinger's points about `include/rocksdb` hygiene and `generated` marking).

## What changed on top of the original PR

### Reconciled with current `main`
- Merged current `main` (conflicts were confined to the generated/test files) and regenerated. Reconciled the 14 C API functions `main` added since the merge-base (e.g. `rocksdb_set_db_options`, the backup-engine rate limiters, `memtable_batch_lookup_optimization`, `optimize_multiget_for_io`, …) and restored 5 enum constants that upstream had added by hand (`rocksdb_txndb_write_policy_*`, `..._index_block_search_type_auto`, `rocksdb_blob_cache_read_byte`).

### Maintainer feedback (pdillinger)
- **No non-user-includable files in `include/rocksdb`.** Moved the hand-written templates out of `include/rocksdb/` and `db/` to `tools/c_api_gen/c_base.{h,cc}`. They were `#include`-ing generated fragments, which broke `make check-headers` and was shipped by `make install`. `include/rocksdb/` now contains only the user-facing, self-contained, `generated` `c.h`.
- `c.h` / `c.cc` carry the `// generated` marker.

### Backward compatibility (zero ABI break)
- The generator derived each wrapper's C type purely from the C++ field, which had silently changed **5 already-shipped signatures** (e.g. `rocksdb_writeoptions_disable_WAL` `int` → `unsigned char`). Added an ABI type-pinning layer (`tools/c_api_gen/abi_type_overrides.json`) so already-shipped functions keep their historical C signature (the body still casts to the real field type). A repo-wide diff against the merge-base now reports **0 ABI drift**.
- New `check_api_compatibility.py` gate (wired into CI + `make`) fails on any removed/changed public function **or** removed enum/typedef symbol, vs a reference revision. Intentional changes go in an allowlist with a reason.

### Correctness (from the automated review)
- Restored 5 option setters that were declared in `c.h` but **defined nowhere** (link failure for downstream bindings such as `rust-rocksdb`). Added `check_api_completeness.py` (dependency-free; runs in CI + `make`) asserting every declared function has exactly one definition — this is the gate that would have caught it.
- `CopyStringVector` now null-checks `malloc`; the WAL filter `std::move`s the `WriteBatch`; the backup exclude-files callback captures by value instead of the wrapper pointer.

### Build / CI robustness
- Removed the dead `C_API_CODEGEN_STAMP` Makefile prerequisite (it was a silent no-op).
- The `make check` staleness check is now opt-out-able (behind `SKIP_FORMAT_BUCK_CHECKS`) and skips gracefully when `clang++` is unavailable, so `make check` works without the codegen toolchain. CI remains the authoritative gate.
- Pinned `clang-format` consistently through `regen_all.py` / `verify_generated_up_to_date.py` (CI uses clang-format-21) so regeneration is byte-reproducible across environments.
- Cleared all 20 `clang-tidy` warnings the bot reported on `db/c.cc` changed lines (fixed in the `c_base.cc` template, not the generated output).
- Updated the internal Buck `c_test_bin` wrapper to expose generated `c_api_gen/*.inc` fragments as headers, so sandboxed Buck builds can compile `db/c_test.c` after the generated round-trip tests are included.

### Test coverage
- Added `gen_roundtrip_tests.py`, which derives **462 set→get→assert round-trip checks across 25 option objects** from the same generated fragments and wires them into `db/c_test.c`. Coverage now tracks the generated surface automatically.

### Docs
- Added the `unreleased_history/public_api_changes` note and fixed `claude_md/add_public_api.md`, which still told contributors to hand-edit the now-`generated` `c.h`/`c.cc`.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14868

Test Plan:
- `make c_test && ./c_test` — **passes**, including the 462 generated round-trip assertions (a successful link also confirms the API is complete).
- `python3 tools/c_api_gen/check_api_completeness.py` — all 1737 declared functions defined exactly once.
- `python3 tools/c_api_gen/check_api_compatibility.py --ref <release>` — 1070 reference functions + 229 enum/typedef symbols preserved, 0 removed/changed.
- `python3 tools/c_api_gen/verify_generated_up_to_date.py` — generated output is stable.
- `include/rocksdb/c.h` confirmed self-contained (only `<stdbool.h>`, `<stddef.h>`, `<stdint.h>`).

cc xingbowang

- `buck2 build --flagfile fbcode//mode/dev fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.
- `buck2 build --flagfile fbcode//mode/dev --config fbcode.arch=aarch64 fbcode//internal_repo_rocksdb/repo:c_test_bin` — passes.

Reviewed By: pdillinger

Differential Revision: D109149150

Pulled By: xingbowang

fbshipit-source-id: 3417375345f360a4c78bdfe27e9850b89d0a226a
2026-06-24 10:45:42 -07:00

118 lines
4.1 KiB
Python

# Copyright (c) Meta Platforms, Inc. and affiliates.
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
#!/usr/bin/env python3
"""Check that the public C API is link-complete and free of duplicate symbols.
Every function declared in ``include/rocksdb/c.h`` with
``extern ROCKSDB_LIBRARY_API`` must have exactly one definition in ``db/c.cc``.
This is a cheap, dependency-free safety net (no clang/libclang required) that
guards against a whole class of code-generation migration bugs: a wrapper whose
declaration is kept while its hand-written implementation is removed and never
regenerated. Such a function compiles fine into ``librocksdb`` (the missing
symbol is simply absent from the archive) and is invisible to RocksDB's own C
test unless that test happens to call it -- but it breaks every downstream
language binding (Rust, Go, Python, ...) that links against the symbol.
It also flags duplicate definitions, which would otherwise fail only at link
time with a less obvious error.
Run:
python3 tools/c_api_gen/check_api_completeness.py
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
C_HEADER = ROOT / "include/rocksdb/c.h"
C_SOURCE = ROOT / "db/c.cc"
# A public C API declaration looks like:
# extern ROCKSDB_LIBRARY_API <return type> rocksdb_foo(...);
# The return type may be on the same line or the following one, so anchor on
# the export macro and capture the first rocksdb_* identifier that is
# immediately followed by '('.
DECL_RE = re.compile(
r"extern\s+ROCKSDB_LIBRARY_API\b[\s\S]*?\b(rocksdb_[A-Za-z0-9_]+)\s*\(",
)
# A definition in c.cc is a top-level (column 0) function whose name is a
# rocksdb_* identifier immediately followed by '('. Return types/pointers may
# precede the name on the same line (e.g. "rocksdb_t* rocksdb_open(").
DEF_RE = re.compile(
r"^(?:[A-Za-z_][\w:<>,\s\*&]*?\s[\*&]*)?(rocksdb_[A-Za-z0-9_]+)\s*\(",
re.MULTILINE,
)
# Function-pointer typedefs etc. are not function definitions; the patterns
# above already exclude lines containing "(*name)" because the identifier must
# be directly followed by '('.
def declared_functions(header_text: str) -> set[str]:
return set(DECL_RE.findall(header_text))
def defined_functions(source_text: str) -> dict[str, int]:
counts: dict[str, int] = {}
for name in DEF_RE.findall(source_text):
counts[name] = counts.get(name, 0) + 1
return counts
def main() -> int:
if not C_HEADER.is_file() or not C_SOURCE.is_file():
sys.stderr.write(
f"error: expected {C_HEADER} and {C_SOURCE} to exist\n"
)
return 2
declared = declared_functions(C_HEADER.read_text())
defined = defined_functions(C_SOURCE.read_text())
missing = sorted(name for name in declared if name not in defined)
duplicated = sorted(name for name, n in defined.items() if n > 1)
status = 0
if missing:
status = 1
sys.stderr.write(
f"error: {len(missing)} C API function(s) are declared in "
f"{C_HEADER.relative_to(ROOT)} but have no definition in "
f"{C_SOURCE.relative_to(ROOT)}:\n"
)
for name in missing:
sys.stderr.write(f" - {name}\n")
sys.stderr.write(
"\nEach such function breaks downstream bindings at link time.\n"
"Add the implementation to tools/c_api_gen/c_base.cc (or via the\n"
"generators) and rerun python3 tools/c_api_gen/regen_all.py.\n"
)
if duplicated:
status = 1
sys.stderr.write(
f"error: {len(duplicated)} C API function(s) are defined more than "
f"once in {C_SOURCE.relative_to(ROOT)}:\n"
)
for name in duplicated:
sys.stderr.write(f" - {name} ({defined[name]} definitions)\n")
if status == 0:
print(
f"C API is link-complete: all {len(declared)} declared functions "
"have exactly one definition."
)
return status
if __name__ == "__main__":
raise SystemExit(main())