Skip to content

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

#if FF_my_app__logging
    log("This is logged.");
#endif

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
#if FF_cob__log_level >= FF_cob__log_level__info
    // verbose logging compiled in
#endif

int — emitted as the literal value, usable directly in #if and as a constexpr:

#define FF_cob__flush_threshold 1048576

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:

#define FF_cob__build_id "dev-build"

Validation (reported at configure time):

  • enum requires a non-empty values: list, and both the default: and any CLI override must be one of those values.
  • int requires the default: (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>:

catalyst build -f no-logging -f log_level=debug -f flush_threshold=2097152

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:

# tc_my-project.yaml
toolchain:
  compiler:
    cxx:
      flags: "-DENABLE_EXPERIMENTAL"