Library / SDK
intel/compile-time-init-build avatar
intel/compile-time-init-build

cib: building embedded firmware at compile time instead of at startup

C++ library for composing modular firmware at compile-time.

656 stars64 forksC++BSL-1.0

At a glance

What is it?
cib is Intel's header-only C++ library for assembling embedded firmware from components, services and features entirely inside constexpr or consteval code. It removes runtime registration and callback wiring from startup paths, at the cost of a template-heavy dependency stack and a compiler support matrix that narrows as you move to newer language features.
Who is it for?
cib belongs in firmware where startup cost and the size of the initialization path matter, and where the team is already committed to a recent GCC or Clang and to Intel's surrounding C++ libraries. It does not fit projects that need runtime plugin loading, that must build with an older toolchain, or that would rather avoid a header-only dependency graph fetched over the network.
Can I use it commercially?
Yes. BSL-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem cib addresses: firmware that wires itself up at runtime

The README states the pattern in one line: instead of initializing components and registering callbacks at runtime, this process is executed at compile-time using constexpr or consteval functions. That sentence describes a real and common shape of embedded code. A firmware image contains several modules, each of which wants to expose an interface to the others. The conventional way to connect them is a global registry, a constructor that runs before main, or a table of function pointers filled in during startup. All of that costs code size, costs startup time, and leaves the actual dependency structure invisible in the source.

cib inverts that. It is a header-only C++ library, so nothing is linked in; the composition happens inside the compiler's constant evaluation, and what reaches the target binary is the finished structure rather than the machinery that built it. The library's own description calls it a C++ library for building embedded firmware with reusable components, and the accompanying talk on the constexpr init()/build() pattern frames the goal as composing modular firmware with minimal runtime cost.

The audience is embedded engineers writing firmware as a set of reusable pieces. The repository topics are embedded and firmware, nothing else, and the dependency list pulls in Intel's bare metal concurrency and standard extension libraries rather than anything from the hosted application world. The design also suits organisations that ship the same firmware across many boards, where the same component set is recombined per product and the wiring code would otherwise be duplicated and hand-edited for each variant.

Components, services and features: the three nouns the library is built on

The README defines the vocabulary precisely. Firmware using cib is implemented as a collection of components. Each component provides services and features to the build. Component features extend and provide new functionality to services. Three terms, and a relationship that runs in one direction: features extend services, services are provided by components, and components are gathered together at the end.

That direction matters more than it first appears. A service is an interface, defined once by whoever owns it, and a feature is a fragment of behaviour that plugs into that interface without the service owner knowing in advance who will plug in. The result is a dependency inversion at compile time: the service header does not include the feature headers, and the feature headers do not include each other. The only place where all of them appear together is the composition point.

The examples given in the README show the pattern at three levels of complexity. The Callback service, whose header lives at include/nexus/callback.hpp, handles simple callbacks. Flow, in include/flow, composes sequences of dependent operations drawn from multiple components. Message, in include/msg, matches incoming data against message formats and dispatches callbacks. Each of these is a separate service that a component can export and another component can extend.

Because the mechanism is templates and constexpr evaluation rather than a runtime registry, the joining happens in the type system. A component exposes a service through cib::exports, another declares an extension through cib::extend, and a project lists the components it wants through cib::components. The compiler checks that the pieces fit; a mismatch is a compile error rather than a link error or a runtime surprise.

Adding cib to a project with CPM or as a git submodule

The recommended way to use cib is with CMake and CPM. The README gives the exact two lines to add to your CMakeLists.txt:

cmake
CPMAddPackage("gh:intel/compile-time-init-build#047aab6")
target_link_libraries(your_target PRIVATE cib)

The hash in that string, 047aab6, is the git hash, tag or branch you want to use, so you replace it with the revision that matches your project. Pinning a specific revision is the point of the exercise: the same hash across your firmware and its tooling is what makes builds reproducible.

The other route is to vendor the library as a git submodule and add its directory to your CMakeLists.txt:

cmake
add_subdirectory(extern/compile-time-init-build)
target_link_libraries(your_target PRIVATE cib)

Either way, the README's closing instruction for the quick start is identical: with either method, include cib/cib.hpp in your code to use cib. Nothing else is required at the build-system level, which is what makes the library attractive for projects that do not want a framework-level dependency.

One operational detail deserves attention. The README notes that cib uses CPM.cmake to fetch its dependencies, and that when first running cmake, the dependencies will be downloaded. To avoid re-downloading dependencies when reconfiguring, it recommends designating a cache directory and setting the CPM_SOURCE_CACHE environment variable. On a build machine without outbound network access, that variable is not a convenience but a requirement, and it is the first thing to set on a hermetic CI runner.

Hello world as compile-time composition, not as runtime registration

The README is candid that a simple example takes a few more lines than a typical hello world, because the point being demonstrated is composition rather than printing. The example defines a service, exports it from one component, extends it from a second component, composes both into a project, and instantiates that project as a nexus:

c++
#include <cib/cib.hpp>
#include <iostream>

struct say_message : public callback::service<>{};

// the 'core' component exposes the 'say_message' service for others to extend
struct core {
    constexpr static auto config = cib::exports<say_message>;
};

// the 'say_hello_world' component extends 'say_message' with its own functionality
struct say_hello_world {
    constexpr static auto config =
        cib::extend<say_message>([](){
            std::cout << "Hello, world!" << std::endl;
        });
};

// the 'hello_world' project composes 'core' and 'say_hello_world'
struct hello_world {
    constexpr static auto config =
        cib::components<core, say_hello_world>;
};

// the nexus instantiates the project
cib::nexus<hello_world> nexus{};

int main() {
    // the fully extended and built services are ready to be used
    nexus.service<say_message>();
    return 0;
}

Four mechanisms are on display. A service is declared by inheriting from callback::service. A component exposes it with cib::exports. A second component attaches behaviour with cib::extend, passing a callable that is evaluated at compile time. The project lists its components with cib::components, and cib::nexus<hello_world> turns that list into a built object. Asking for nexus.service<say_message>() returns the service with every extension already folded in, so main contains no wiring at all.

A larger example is kept in the repository at examples/nexus/hello_world, and the README also points to a Compiler Explorer link that runs the example live, noting that it comes from an earlier version of the library.

The trade-off sits in plain sight. Nothing in this design is dynamic. The set of components is fixed when the type is written, and the callbacks are compile-time constants. That is exactly what makes the runtime cheap, and it is also what makes runtime extension impossible.

Sub-libraries and the dependency chain they pull in

cib is an umbrella project with several sub-libraries under include. The cib target is the omnibus library that includes all the rest. Around it sit focused ones: cib_flow, a flow that is a sequence of dependent operations composed at compile time; cib_seq, a sequence that is like a flow but allows stepping and reversal; cib_interrupt for defining and handling interrupts; cib_lookup for selecting efficient lookup strategies at compile time; cib_match for arbitrary match criteria; cib_msg for field and message layouts, message construction and matching callbacks; and cib_nexus, the place where services and components are composed at compile time. The logging family splits three ways: cib_log holds basics with implementation-independent definitions, cib_log_binary implements it in the MIPI Sys-T binary format, and cib_log_fmt implements it with fmt. The README points to a library dependency chart for the full graph.

The external dependencies are listed explicitly. Boost.MP11 is required. Intel's C++ Bare Metal Concurrency, C++ Standard Extensions and C++ Bare Metal Senders and Receivers libraries are also required, the last of those used in cib_msg only. fmt is used in cib_log_fmt only. Test-only extras include Intel's Generic Register Operation Optimizer for cib_interrupt tests, and mph and Frozen for cib_lookup benchmark tests.

Two things follow from that list. First, adopting cib means adopting a family of Intel-maintained libraries that move together, which is a supply-chain decision as much as a technical one. Second, the umbrella cib target pulls in everything, so a project that only needs logging can link cib_log_fmt instead and avoid the concurrency stack. The README also notes that building cib depends on Intel's CICD Repo Infrastructure.

The Python side of the repository is smaller and mostly tooling. The pyproject.toml defines a package named intel-compile-time-init-build, requires Python 3.12 or newer, and exposes two console scripts, cib_gen_str_catalog and cib_log_decode, with the code living in a python directory and the tests under python/test.

Compiler and standard support, and the branch structure behind it

The README lays out support by branch, and this is where adopters should start reading. For the C++ standard: the main branch is C++23 and is where active development happens, the cpp20 branch is C++20 and is supported, and the cpp17 branch is C++17 and is marked end-of-life. The compiler table that follows gives the versions each branch is tested against. On main, GCC 12 through 14 and Clang 18 through 22. On cpp20, GCC 12 through 14 and Clang 14 through 21. On cpp17, GCC 9 through 12 and Clang 9 through 15.

The pattern is clear and worth stating plainly. Newer language features mean a newer compiler floor, so the C++23 branch drops GCC versions below 12 and Clang versions below 18. If your build environment is pinned to an older toolchain, the branch you can use is determined by your compiler, not by your preference.

The branch layout also gives you a migration path that many embedded projects will recognise: stay on the branch that matches your toolchain while the C++23 work proceeds on main, then move up when the compiler in your build farm supports it. The repository contains a cpp20 and a cpp17 directory reference in the branch links themselves, and the release tags follow the same versioning scheme.

One note about the project's own history. The releases listed for the repository are v0.2.1 and v0.2.2 in April 2022 and v1.0.0 in July 2022. The last push to the repository was on 2026-10-21, and it is not archived, so development continues on main while the published tags stop in 2022. In practice you consume a branch or a commit rather than a release tarball, which is exactly what the CPM hash in the quick start is for.

Limitations: compile-time cost, dependency fetching, and a closed ecosystem

The first limitation is that compile-time work is not free. Every service, feature and component is folded into a template instantiation graph evaluated by the compiler, and the README's own framing of the pattern as minimal runtime cost says nothing about build time. On a large firmware with many components, compile times and peak memory during the build are the costs you accept in exchange. The README documents no knobs for controlling that, so if build time is a problem in your pipeline, measure it on your own component set before committing.

The second is the dependency fetch. CPM downloads dependencies the first time cmake runs, and the mitigation is a cache directory pointed at by CPM_SOURCE_CACHE. That works well for a team with shared infrastructure and badly on an isolated build machine, and it means your build environment needs either network access or a pre-populated cache.

The third is ecosystem breadth. The dependency list is dominated by Intel-maintained libraries, and the whole set has to move together, since Boost.MP11 and the three bare metal libraries are all required rather than optional. The library is also header-only, which means compile time and binary size scale with how much of the umbrella you include, and the individual sub-libraries exist precisely so you can limit that.

The fourth is the design constraint discussed earlier. Because composition is expressed in types, there is no runtime registration path. A component cannot be added to a built nexus later, and a callback cannot be attached at runtime. For firmware whose module set is fixed at build time that is the point of the library; for a system expecting dynamic configuration it is disqualifying.

Editorial conclusion

cib belongs in firmware where startup cost and the size of the initialization path matter, and where the team is already committed to a recent GCC or Clang and to Intel's surrounding C++ libraries. It does not fit projects that need runtime plugin loading, that must build with an older toolchain, or that would rather avoid a header-only dependency graph fetched over the network. Verify first that your compiler appears in the support table for the branch you pick, that the CPM_SOURCE_CACHE variable is set on machines with restricted network access, and that the service you need exists as a sub-library rather than something you must write yourself.

Frequently asked questions

Which C++ standard and compilers does cib support?

Support is split by branch: the main branch targets C++23 with GCC 12 through 14 and Clang 18 through 22, the cpp20 branch targets C++20 with GCC 12 through 14 and Clang 14 through 21, and the cpp17 branch targets C++17 with GCC 9 through 12 and Clang 9 through 15. The cpp17 branch is marked end-of-life.

How do I add cib to a CMake project?

The README recommends CPM, adding CPMAddPackage("gh:intel/compile-time-init-build#047aab6") and then linking the cib target, with the hash replaced by the git hash, tag or branch you want. The alternative is to vendor the library as a git submodule and use add_subdirectory(extern/compile-time-init-build), and in both cases you include cib/cib.hpp.

Does building cib require network access?

The first cmake run downloads the dependencies, because cib uses CPM.cmake to fetch them. The README recommends designating a cache directory and setting the CPM_SOURCE_CACHE environment variable to avoid re-downloading dependencies when reconfiguring.

Which cib sub-library should I use for logging?

The project splits logging into cib_log, which holds basics with implementation-independent definitions, cib_log_binary, which implements it in the MIPI Sys-T binary format, and cib_log_fmt, which implements it with fmt. Linking the umbrella cib target pulls in all of them.

Can components be registered with cib at runtime?

No. The README states that initialization and build happen at compile time using constexpr or consteval functions rather than at runtime, and the composition model shown in the quick start fixes the component list in the type, so a nexus is built with everything already extended.

What licence does cib use?

The repository is licensed under BSL-1.0, and the Python package metadata in pyproject.toml declares the same licence for the supporting tools.

Official sources

  1. intel/compile-time-init-build on GitHub
  2. License: BSL-1.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/intel-compile-time-init-build.svg)](https://hysenlabs.com/projects/intel-compile-time-init-build)

Was this useful?