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 the APIARY_* 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).