CMake helpers¶
Consume apiary from CMake via find_package(Apiary) (installed) or
add_subdirectory (vendored). Both provide:
apiary::apiary— the codegen executable.apiary::annotations— an INTERFACE target carrying theAPIARY_*macro header; link it instead of hardcoding the include path.
…plus the helper functions below (lifted from their in-source documentation).
An install has two components, which a package manager can ship apart.
ApiaryAnnotations holds the header, apiary::annotations and the package
config, and needs no Clang/LLVM; it is all a library that only annotates its
declarations, or a project that links one, needs.
ApiaryTool holds the executable and the helpers.
find_package(Apiary) succeeds with the annotations alone, so a project that
generates bindings asks for the tool:
find_package(Apiary REQUIRED COMPONENTS tool)
APIARY_TOOL_FOUND says whether apiary::apiary is present.
apiary_detect_toolchain¶
Find the include paths libtooling needs and cache them as:
APIARY_RESOURCE_DIR - clang -resource-dir (builtin headers): apiary's
own, else a borrowed Clang's (with a warning
when its major differs from apiary's libclang)
APIARY_EXTRA_ISYSTEM - include dirs named in CMAKE_CXX_FLAGS (a conda
compiler's environment include dir, say)
APIARY_CXX_INCLUDE_DIRS - the compiler's C++ stdlib search dirs
Without these even ``#include <string>`` fails when the build compiler is
gcc (libstdc++) rather than the clang that backs the apiary binary.
apiary_collect_usage_requirements¶
Recursively collect BUILD_INTERFACE include directories AND
INTERFACE_COMPILE_DEFINITIONS reachable from <target> via
INTERFACE_LINK_LIBRARIES. CMake has no built-in for this; the walk is
reliable since usage requirements are set explicitly via
target_link_libraries / target_include_directories.
apiary_collect_usage_requirements(<target> INCLUDES_OUT <v> DEFINES_OUT <v>)
Genex-wrapped entries are unwrapped for BUILD_INTERFACE and dropped
otherwise (config-conditional values can't be evaluated at configure time).
apiary_add_bindings¶
Emit the codegen custom command(s) that run apiary on one set of headers.
apiary_add_bindings(
HEADERS <abs header...> # parsed by apiary (positional)
SOURCE_INCLUDES <relative name...> # --source-include each
REGISTER_FUNCTION <name> # --register-function (binding TU)
MODULE <name> # --module: the Python module the stubs and
# docs JSON name (missing: a warning;
# required from 2.0)
DEPENDS_TARGETS <target...> # usage requirements -> -I / -D
OUTPUT_DIR <dir> # where generated files land
OUTPUT_NAME <stem> # base filename for outputs
CXX_STANDARD <n> # default 17
MAX_DEFS_PER_TU <n> # split binding into shard TUs of ~n .def's
NUM_TU <n> # split binding into exactly n shard TUs
EXTRA_FLAGS <flag...> # extra -I/-D/... after --
EXTRA_DEPENDS <file...> # extra DEPENDS (e.g. Defines.hpp)
BACKEND pybind11|nanobind # binding library the TU targets
# (apiary --target); default APIARY_BACKEND
BINDING # emit the binding TU (+ stub)
DOCS_JSON # emit the docs-json (Python surface)
CPP_DOCS_JSON # emit the docs-json (C++ surface)
OUT_BINDING <v> OUT_STUB <v> OUT_DOCS_JSON <v> OUT_CPP_DOCS_JSON <v>
)
The binding TU is ``<OUTPUT_NAME>_pybind.cpp`` under pybind11 and
``<OUTPUT_NAME>_nanobind.cpp`` under nanobind. Leaving BACKEND to the
build-wide APIARY_BACKEND (see ApiaryBackend.cmake) keeps it and the
apiary_aggregate_extension() that compiles the TU in step; a mismatch is
refused there, since it would otherwise surface as an undefined register
symbol at link time.
MAX_DEFS_PER_TU and NUM_TU both split the generated binding into several
smaller TUs (``<name>_pybind.shard<k>.cpp``) so a heavily instantiated module
doesn't compile as one TU large enough to exhaust memory. They differ in who
picks the shard count, which decides whether the tool has to run before the
OUTPUTs can be named:
MAX_DEFS_PER_TU <n> sizes each shard at ~n binding statements and lets the
count follow the headers. The count is discovered at configure time by
running ``apiary --plan``, so the binary must ALREADY EXIST then — true
under find_package(Apiary); in add_subdirectory mode the tool isn't
built yet, so sharding is skipped (with a status message) and a single
TU is emitted.
NUM_TU <n> fixes the count at n and balances the units across it. The
filenames follow from n alone, so nothing runs at configure time and
this works in add_subdirectory mode. n <= 1 means a single TU.
Set at most one. NUM_TU is the one to reach for when apiary is built as part
of the same build; MAX_DEFS_PER_TU auto-sizes but costs a configure-time run
of the tool over every header.
Computes flags = APIARY_SYSTEM_FLAGS + usage-requirements(DEPENDS_TARGETS) +
EXTRA_FLAGS + -std, and sets the requested OUT_* variables in the caller's
scope. Requires apiary_detect_toolchain() to have run.
apiary_add_python_docs¶
Emit a docs-JSON fragment for a package's hand-written pure-Python layer, via
the static Python frontend (apiary_py_extract.py — stdlib ``ast`` only, no
import). The fragment is in the same schema as apiary_add_bindings' DOCS_JSON,
so it folds into the same merge: pass it to apiary_aggregate_extension as
PY_DOCS_JSON alongside the C++ DOCS_JSON.
apiary_add_python_docs(
PACKAGE <name> # top package import name (-> document "module")
PACKAGE_DIR <dir> # the package directory (contains __init__.py)
SOURCE_ROOT <dir> # optional: record location.file relative to this
# (e.g. the repo root, for clean source links)
OUTPUT <file> # fragment path (default <bindir>/<package>.py.docs.json)
OUT_PY_DOCS_JSON <var> # set to the fragment path in the caller's scope
)
apiary_aggregate_extension¶
Assemble per-module bindings into one Python extension.
apiary_aggregate_extension(
NAME <target> # extension module target to create
MAIN <main.cpp> # consumer's module-definition source
BACKEND pybind11|nanobind # default APIARY_BACKEND; must match apiary_add_bindings
NOMINSIZE # keep the build type's optimization level (nanobind
# otherwise builds for size)
ADD_MODULE_OPTIONS <opt...> # forwarded to pybind11_add_module / nanobind_add_module
BINDINGS <generated tu...> # per-module binding TUs (from apiary_add_bindings)
MODULES <name...> # modules to register
REGISTER_PREFIX <p> # default apiary_register_
MODULES_HEADER <path> # generated header declaring <prefix>all()
MODULES_INCLUDE_DIR <dir> # added to the target so MAIN finds the header
# optional .pyi aggregation:
STUBS <pyi...> STUBS_TARGET <name> FRAG_DIR <d> PKG_DIR <d>
PY_HELPERS_DIR <d> PY_HELPER_DEPENDS <file...>
# optional consumer-provided overlay for runtime-patched members the
# codegen can't see (content + target-class regex are the caller's):
STUB_OVERLAY <file.pyi> STUB_OVERLAY_CLASS_REGEX <regex>
# optional docs render (built on demand, not part of ALL):
DOCS_JSON <json...> # C++-frontend docs-JSON fragments
PY_DOCS_JSON <json...> # static-Python-frontend fragments (apiary_py_extract.py)
DOCS_TARGET <name> DOCS_OUTDIR <d>
DOCS_CONTENT_DIR <d> # optional authored Markdown: per-module
# curation (## Topics) + free-standing articles
)
The docs pipeline merges all fragments (DOCS_JSON + PY_DOCS_JSON) into one
canonical docs.json (apiary_merge_docs_json.py) before rendering, so C++- and
Python-origin symbols land on the same submodule pages.
The generated MODULES_HEADER declares the register functions, the inline
<prefix>all() aggregator, and APIARY_EXTENSION_MODULE(name, m): the backend's
own module macro (PYBIND11_MODULE or NB_MODULE). A MAIN written against that
macro builds unchanged under either BACKEND:
#include <mylib/Modules.hpp>
APIARY_EXTENSION_MODULE(_core, m) { apiary_register_all(m); }
The consumer calls apiary_find_backend() before this (see ApiaryBackend.cmake),
which finds the library APIARY_BACKEND names. The module is created as
apiary_add_extension_module creates one: APIARY_STABLE_ABI applies, and
APIARY_BACKEND_PYBIND11 or APIARY_BACKEND_NANOBIND is defined for MAIN and
anything it includes, <apiary/Python.hpp> among them.
Generates the register header, creates the extension module (the consumer
configures output name / linkage / packaging afterward), and — when the
optional groups are given — wires the stub-aggregation (ALL) and docs-render
(on-demand) targets using Apiary's bundled scripts (APIARY_SCRIPTS_DIR).