CMake (C / C++)

find_package(llvm-dsdl) provides dsdlc_generate(), which queries dsdlc for the exact input and output lists at configure time and produces a static library to link. Four lines of build file, and adding a definition needs no edit to any of them.

Language C
Build system CMake
Idiom B -- your project owns the manifest
Regeneration Configure-time query (--list-outputs)

Notes

Requires llvm-dsdl to have been installed, not merely built -- the package config and the function ship in the bin install component.

Editing a definition regenerates; adding or removing one re-runs configure first, so the file list stays honest. Removing one also deletes its generated header, because the function hands dsdlc a --prune-manifest -- a type you deleted stops existing instead of lingering on the include path.

One call is the simple case. A project with more than one namespace needs several, and they must not overlap: two calls defaulting to SUPPORT as-needed into one output directory both emit the runtime header, which Ninja refuses to build. Split with SUPPORT only / SUPPORT never, OMIT_DEPENDENCIES, and a BUILTIN call owning the shared standard types. dsdlc_generate() checks for overlap itself and fails at configure time naming both claimants.

The obj variant. -l obj runs the C backend and then compiles it, handing back the headers, the objects, and a .a. Reach for it when the generated code should be built once -- for a target triple that is not this host, or by a party who ships the archive rather than the schema. The archive is self-contained and the headers are published beside it in the same layout the c backend uses, so one call gives a complete interface. dsdlc_generate(LANGUAGE obj) hands back a target that carries both.

Prerequisites

Tool Why
cmake configures and drives the build
cc compiles the generated C and the round-trip program

Commands

Run these from the recipe directory, with dsdl/ and src/ copied alongside it. This is the exact sequence CI runs.

# configure
cmake -S . -B build "-DCMAKE_PREFIX_PATH=$LLVM_DSDL_PREFIX"
# build
cmake --build build
# round-trip
./build/roundtrip
# round-trip (prebuilt archive)
./build/roundtrip_prebuilt

The types this builds

The whole lanyard namespace -- twenty-four definitions, browsable from the showroom overview, where each one is paired with its wire-layout facts and a declaration excerpt in every language.

The build files

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(lanyard_c_cmake C)

# find_package brings in the imported target llvm-dsdl::dsdlc and the dsdlc_generate() function.
# Point CMAKE_PREFIX_PATH at wherever llvm-dsdl was installed, or let it be found on the default
# search path if it went to /usr or /usr/local.
find_package(llvm-dsdl REQUIRED)

# One call. It asks dsdlc at configure time what the run would read and what it would produce, hands
# CMake the exact lists, and -- for the C and C++ backends -- builds a static library out of the
# generated sources with the output directory already on its interface include path.
#
# So there is no glob to keep in step, no stamp file, no hand-maintained list of generated files, and
# adding a definition to dsdl/lanyard/ is picked up on the next build with nothing edited here.
dsdlc_generate(lanyard
  LANGUAGE  c
  NAMESPACE "${CMAKE_CURRENT_SOURCE_DIR}/dsdl/lanyard")

add_executable(roundtrip src/c/roundtrip.c)
target_link_libraries(roundtrip PRIVATE lanyard)

# --------------------------------------------------------------------------------------------------
# The obj backend: the same types as a prebuilt archive rather than as sources.
#
# `-l obj` runs the C backend and then compiles it, handing back the headers, the objects, and a
# `.a`. That is what you want when the generated code should be built once -- for a target triple
# that is not this host, or by a party who ships the archive rather than the schema.
#
# One call, like the source build above: the headers are declared outputs published beside the
# archive, so linking the target brings its include path with it.
dsdlc_generate(lanyard_prebuilt
  LANGUAGE  obj
  NAMESPACE "${CMAKE_CURRENT_SOURCE_DIR}/dsdl/lanyard"
  # Required for obj. It selects the codegen strategy, not the wire format -- DSDL is little-endian
  # on every target either way. Add --target-triple here to build the archive for a different one,
  # after which it only links into a build using that same toolchain.
  OPTIONS   --target-endianness little)

add_executable(roundtrip_prebuilt src/c/roundtrip.c)
target_link_libraries(roundtrip_prebuilt PRIVATE lanyard_prebuilt)