Preprocessor Variables & Features¶
Catalyst integrates tightly with the C++ preprocessor to pass build information and feature flags into your code.
Catalyst-Defined Macros¶
These macros are automatically defined by Catalyst during compilation.
| Macro | Description |
|---|---|
CATALYST_BUILD_SYS |
Always defined. Indicates the code is being built by Catalyst. |
CATALYST_PROJ_NAME |
The project name (from manifest.name). |
CATALYST_PROJ_VER |
The project version (from manifest.version). |
Feature Flags¶
Tip
Read the build subcommand docs for how to override feature flags from
the command line (-f/--features).
Features defined in the features: block of your manifest map directly to preprocessor macros named
FF_<project>__<feature>, where <project> is manifest.name.
Every declared flag is always defined: a disabled boolean is emitted as 0, not left undefined.
This means a typo in #if FF_my_app__loging is a hard always-false rather than a silent one,
and compiling with -Wundef will flag the misspelling.
Boolean Flags¶
The simplest flag is an on/off boolean. Write it as a bare value, or as a map with a default: and an optional
list of source files: that are compiled only when the flag is enabled.
manifest:
name: my_app
features:
logging: true
gui: false
predictive_execution:
default: true
files: ["src/predictive_execution.cpp"]
Generated macros:
- FF_my_app__logging → 1
- FF_my_app__gui → 0
- FF_my_app__predictive_execution → 1
Tip
catalyst build tracks the composed configuration and feature overrides, regenerating when they change.
This includes files: source gating and removing a previously supplied CLI override. Unchanged commands
remain eligible for incremental compilation.
Valued Flags (enum / int / string)¶
Not every flag is naturally a boolean. A flag can carry a value by adding a type:.
Valued flags require an explicit default:.
manifest:
name: cob
features:
log_level:
type: enum
values: [off, error, info, debug] # ordered; the index is the emitted int
default: error
flush_threshold:
type: int
default: 1048576 # 1 MiB
build_id:
type: string
default: "dev-build" # runtime-only; cannot drive #if
enum — the selected member is emitted as its index into values:. One named-constant macro is also
emitted per member so comparisons read well:
#define FF_cob__log_level 1 // = error (the active value)
#define FF_cob__log_level__off 0
#define FF_cob__log_level__error 1
#define FF_cob__log_level__info 2
#define FF_cob__log_level__debug 3
int — emitted as the literal value, usable directly in #if and as a constexpr:
string — emitted as a quoted string literal. A string flag cannot drive #if (the preprocessor cannot compare strings); it is meant for runtime use such as version strings or paths:
Validation (reported at configure time):
enumrequires a non-emptyvalues:list, and both thedefault:and any CLI override must be one of those values.intrequires thedefault:(and any CLI override) to be a valid integer.- Every valued flag requires a
default:.
A bare feature: true|false remains valid and behaves as before — it is sugar for an on/off flag.
Overriding from the CLI¶
Defaults can be overridden per build with -f/--features.
Booleans use -f <name> / -f no-<name>; valued flags use -f <name>=<value>:
See the build subcommand for the full grammar and validation rules.
Features exported by local dependencies¶
Local Catalyst dependencies export their resolved features to both C and C++ consumers, including through
transitive local dependencies and INTERFACE libraries. Macros use the dependency's manifest.name, not its
alias in the consumer's dependency list, and are formatted using the consumer's toolchain.
dependencies:
- name: ripc
source: local
path: ../../ripc
profiles: [common, release]
using: [max_payload=2048, max_capacity=128]
using accepts the same overrides as --features. Catalyst passes them to the dependency build and exports
those same resolved values, so the example supplies FF_ripc__max_payload=2048 and
FF_ripc__max_capacity=128 to the library and its consumers. Without using, the selected profiles' defaults
are exported. Source gating remains local to the project declaring the feature.
Every build checks local dependencies incrementally, even after the initial fetch. Changes to their composed manifests or feature overrides invalidate the consumer's generated configuration; unchanged builds do not regenerate. Identical macro definitions are emitted once. Conflicting values across dependency paths, cycles, and feature-resolution errors fail generation instead of producing ambiguous or incomplete compiler flags.
Custom Flags¶
You can always define arbitrary macros via the compiler flags in your toolchain file: