tools/c_api_gen/README.md
This directory contains a prototype for semi-automating parts of
RocksDB's C API based on the existing C++ public API and the current C API
conventions in include/rocksdb/c.h / db/c.cc.
The intent is not to automatically wrap arbitrary C++ APIs. Instead, the goal is to reduce maintenance burden for the parts of the C API that are already highly mechanical today.
RocksDB's C API follows stable conventions that downstream users rely on:
rocksdb_t*, rocksdb_transaction_t*Status translated to char** errptrSlice translated to const char* + size_tmalloc()-owned buffersunsigned charrocksdb_<object>_<verb>[_cf][_with_ts] namingThese conventions are valuable and should remain the source of truth for the generated code.
At the same time, the C++ public API includes features that are poor fits for fully automatic wrapper generation:
Comparator, MergeOperator, etc.)MultiGet familyFor those cases, hand-designed wrappers remain the right tool.
The spec-driven generator is still needed for wrappers whose C shape cannot be reliably inferred from the C++ declaration alone.
Yes, this means spec.json is manually maintained. That is intentional. The
goal is not to eliminate human decisions; the goal is to stop hand-writing the
same declaration/definition boilerplate once those decisions have been made.
For many wrappers, someone still has to decide:
Status becomes char** errptrReadOptions()Those are API design choices, not mechanical translations. The spec-driven path keeps those choices explicit while still generating consistent boilerplate.
Backward compatibility with the existing hand-written C API is one input, but it is not the primary reason this layer exists. Even for brand-new wrappers, we still need an explicit policy layer for non-trivial C bindings.
The generator currently targets representative examples from the easiest and most repetitive wrapper families:
Simple scalar field setters/getters
rocksdb_options_set_create_if_missingrocksdb_options_get_create_if_missingrocksdb_readoptions_set_fill_cacherocksdb_writeoptions_set_syncrocksdb_block_based_options_set_block_sizerocksdb_cuckoo_options_set_hash_ratioSimple forwarding wrappers with no Status
rocksdb_writebatch_clearrocksdb_writebatch_putrocksdb_writebatch_put_cfrocksdb_writebatch_deleterocksdb_writebatch_put_log_datarocksdb_writebatch_set_save_pointrocksdb_transaction_set_savepointSimple forwarding wrappers with Status -> char** errptr
rocksdb_put, rocksdb_put_cfrocksdb_delete, rocksdb_delete_cfrocksdb_merge, rocksdb_merge_cfrocksdb_writerocksdb_writebatch_rollback_to_save_pointrocksdb_writebatch_pop_save_pointrocksdb_transaction_put, rocksdb_transaction_put_cfrocksdb_transaction_merge, rocksdb_transaction_merge_cfrocksdb_transaction_delete, rocksdb_transaction_delete_cfrocksdb_transaction_commitrocksdb_transaction_rollbackrocksdb_transaction_rollback_to_savepointSimple metadata accessors
rocksdb_flushjobinfo_cf_namerocksdb_flushjobinfo_largest_seqnoThis is intentionally a constrained subset. The point is to demonstrate a maintainable pattern and a spec format that can be extended gradually.
These families should stay hand-written unless a future design adds a much richer generator:
MultiGet / batched MultiGet familiesstd::shared_ptr / std::unique_ptr ownership transferThe code generator should not invent a new style. It should emit code that looks like today's hand-written wrappers:
SaveError(errptr, ...)Slice(key, keylen)For a small set of mechanically mappable public structs, the generator can discover missing bindings directly from the C++ headers and emit the missing C wrappers automatically. Existing handwritten wrappers still win when they already exist.
Today this strict auto-discovery path is used for:
ReadOptions scalar / enum fieldsFor those managed families, unsupported or intentionally deferred fields must
be explicitly recorded in auto_simple_bindings_blocklist.json rather than
being skipped silently. Anything outside those families remains hand-written.
The spec-driven generator remains the right tool for method-style wrappers and other APIs whose C surface requires an explicit design decision. In practice:
spec.json owns wrappers that are still manual in design but repetitive in
implementationLonger term, Clang/libTooling can help validate that a wrapper spec still matches the underlying C++ method signature. But the C naming, ownership, and error-handling policy should remain explicit in the spec.
Source templates (hand-written; NOT user-includable and NOT compiled directly):
c_base.h
include/rocksdb/c.h; the generated .h.inc
fragments are inlined into it to produce the self-contained public headerc_base.cc
db/c.cc; the generated .cc.inc fragments are
inlined into it to produce the compiled implementationGenerators and inputs:
spec.json
generate_c_api.py
auto_simple_bindings.py
.inc files for themauto_simple_bindings_blocklist.json
abi_type_overrides.json
regen_all.py
c.h / c.ccVerification (run by make check-c-api-gen and in CI):
check_api_completeness.py
check_api_compatibility.py + api_compatibility_allowlist.json
verify_generated_up_to_date.py
validate_generated_equivalence.py + equivalence_allowlist.json
check_api_compatibility.py.Regeneration needs two external tools:
clang++ (libclang) — used to dump the C++ struct ASTs that drive
auto-discovery. Resolution order is $CXX (if it names a clang), then bare
clang++, then versioned fallbacks (clang++-21 … clang++-13).clang-format — used to canonicalize the generated .inc fragments (and
therefore the inlined c.h / c.cc).The checked-in generated output is byte-reproducible only for a pinned
clang-format version. CI uses clang-format-21 (see
.github/workflows/pr-jobs.yml); pin the same version locally so your
regeneration matches CI:
python3 tools/c_api_gen/regen_all.py --clang-format clang-format-21
# or, when running the staleness check via make:
make check-c-api-gen CLANG_FORMAT_BINARY=clang-format-21
If clang++ is not installed, make check skips the C API staleness check
with a message instead of failing, so it still works without the codegen
toolchain. CI is the authoritative gate.
The dedicated build-linux-clang-21-no_test_run CI job runs this target with
CHECK_C_API_GEN_STRICT=1, which turns every such "skip" (missing clang++ or
clang-format, or an unresolvable compat baseline ref) into a hard error, so the
core CI coverage cannot silently regress to a no-op:
make check-c-api-gen CHECK_C_API_GEN_STRICT=1 CLANG_FORMAT_BINARY=clang-format-21
From the repo root, to regenerate all checked-in fragments and the inlined
c.h / c.cc:
python3 tools/c_api_gen/regen_all.py
This regenerates:
spec.jsonauto_simple_bindings.pygen_roundtrip_tests.pyTo preview only the spec-driven output for one section without touching the
checked-in fragments, run generate_c_api.py directly with --header-out /
--source-out pointing at a scratch path (e.g. under /tmp).
To verify the auto-discovered checked-in output is up to date:
python3 tools/c_api_gen/auto_simple_bindings.py --check
To verify the full checked-in generated set is stable without relying on Git worktree metadata:
python3 tools/c_api_gen/verify_generated_up_to_date.py
When a new public field is added to an auto-managed family, use this decision process:
include/rocksdb/c.h or db/c.cc. Run python3 tools/c_api_gen/regen_all.py
and let auto_simple_bindings.py generate the wrapper.tools/c_api_gen/auto_simple_bindings_blocklist.json with
"policy": "manual"."policy": "deferred" and a concrete reason. Include tracking_issue when
you have one.auto_simple_bindings.py, add a spec-driven wrapper, or add a hand-written
wrapper and then update the blocklist policy accordingly.The blocklist is intentionally strict:
manual or deferredExample deferred entry:
{
"name": "ReadOptions",
"entries": [
{
"field": "future_slice_option",
"policy": "deferred",
"reason": "Needs an explicit C buffer ownership design before exposing it.",
"tracking_issue": "T123456789"
}
]
}
The blocklist only applies to the auto-managed families handled by
auto_simple_bindings.py. It is not a substitute for spec.json, which still
owns non-trivial method-style wrappers. It is also unrelated to
equivalence_allowlist.json, which only suppresses intentional diff mismatches
in the equivalence checker.
To refresh the full checked-in generated set and confirm nothing changes:
python3 tools/c_api_gen/verify_generated_up_to_date.py
To verify active generated wrappers are equivalent to the handwritten wrappers from a reference revision:
python3 tools/c_api_gen/validate_generated_equivalence.py --ref HEAD
This checks the generated fragments currently included by
include/rocksdb/c.h and db/c.cc against HEAD:include/rocksdb/c.h and
HEAD:db/c.cc. Known historical inconsistencies can be documented in
equivalence_allowlist.json with a reason instead of being silently ignored.
This incremental approach minimizes downstream impact while still reducing maintenance burden over time.