dsdlc

dsdlc is the primary compiler/codegen driver. Run dsdlc --help for languages, target syntax, and the full option set.

This page covers behaviour that switch descriptions do not carry.

Support code

Support code is everything a backend emits that is not derived from a definition. It is rendered from content compiled into dsdlc, and --generate-support selects when it is written.

Backend Support artifacts
c dsdl_runtime.h
cpp dsdl_runtime.h, dsdl_runtime.hpp (per profile)
rust Cargo.toml, src/dsdl_runtime.rs, src/dsdl_runtime_semantic_wrappers.rs
go go.mod, dsdlruntime/dsdl_runtime.go
ts package.json, dsdl_runtime.ts
python pyproject.toml, _dsdl_runtime.py, _runtime_loader.py, py.typed

Under never, Python still writes the __init__.py chain its generated modules import through.

Dependency files

-MD writes a make-style .d beside each generated output listing the .dsdl files it was built from: its own definition plus the transitive closure of the composite types it references.

Some outputs have no .dsdl source to name: definitions from the embedded uavcan catalog and all support code are compiled into the binary. Those rules name the dsdlc executable as their prerequisite instead — upgrading the compiler rebuilds what it produced. An output mixing local and embedded definitions lists its real inputs and the executable.

Every path emitted in a depfile or by --list-inputs exists on disk, so both can be fed to a build system verbatim.

Pruning stale output

dsdlc writes what it is asked for; without --prune-manifest it does not remove what it wrote last time. Delete a definition and its generated header stays in --outdir, still on the include path, so code that names a type nobody defines any more keeps compiling.

--prune-manifest <file> closes that. The run records its outputs in <file> and, on the next run, deletes the outputs the previous manifest listed that it no longer produces. Directories emptied by pruning are removed too, so a deleted namespace leaves no shape behind.

One manifest per invocation, not per output directory. Generation decomposes — a build may split one namespace across several runs for support, the embedded catalog, and definitions — and a run owns only the files it emits. A run that swept --outdir would delete the files its siblings had just written, so each tranche is given its own manifest and prunes only what it owns:

dsdlc -l c --outdir gen --generate-support only  --prune-manifest .dsdlc/support
dsdlc -l c --outdir gen --generate-support never --prune-manifest .dsdlc/builtin +uavcan.node
dsdlc -l c --outdir gen --generate-support never --omit-dependencies \
      --prune-manifest .dsdlc/types dsdl/myns

Removals are confined to --outdir: a manifest naming anything outside it is a hard error rather than a deletion, since a manifest is an input and an input that can name any path is a way to turn a stale file into an arbitrary rm. A manifest in an unrecognised format is treated as absent — a format change should cost one stale file, not every configured tree.

The flag is ignored under --dry-run and the --list-* modes, which imply it. A dry run that deleted files while reporting that it wrote none would be worse than either.

Embedded uavcan catalog

For the standard uavcan.* namespace, dsdlc ships an embedded catalog used by the mlir and codegen targets. Types referencing core uavcan definitions resolve without external uavcan source roots.

The catalog is consulted automatically during dependency resolution, and can be named directly as a target with the + sigil.

+ targets behave as explicit targets: their dependency closure is generated too, and --omit-dependencies restricts output to what was named. They mix freely with filesystem targets, and a local definition sharing a type key shadows the embedded one.

Namespace matching is anchored at a dot boundary, so +uavcan.n selects nothing rather than standing in for +uavcan.node. A selector matching nothing is an error with a did-you-mean; an unavailable version reports the versions the catalog carries.

Deprecation

A definition marked @deprecated generates a Deprecated: … notice in its documentation comment and an IS_DEPRECATED metadata constant (DSDL_IS_DEPRECATED in TypeScript and Python, <TYPE>_IS_DEPRECATED_ in C), in every language. Go recognises the Deprecated: doc paragraph, and TypeScript is additionally given a /** @deprecated … */ JSDoc block.

C, C++, and Rust additionally get a language-native attribute — __attribute__((deprecated)), [[deprecated]], and #[deprecated] respectively — so naming the type produces a compiler diagnostic. This is on by default.

Each generated file suppresses deprecation diagnostics across its own body (#pragma GCC diagnostic ignored "-Wdeprecated-declarations", or #![allow(deprecated)] in Rust). The suppression is scoped to the generated file: including generated headers is clean under -Werror, and only your own code naming a deprecated type is diagnosed.

The obj backend never emits these attributes.