-
Notifications
You must be signed in to change notification settings - Fork 8
Distributor Guide Writing Distributions
This page explains how to create a new RTICX distribution for a target that is not already covered by the reference distributions.
This repository only maintains the core framework and a small set of reference distributions. New hardware distributions should be developed in their own crates and repositories. They are not merged into the core project.
A distribution consists of two crates:
-
The library crate — users depend on this. It re-exports the proc macro and exposes an
exportmodule with runtime helpers. -
The macro crate — defines the actual
#[<distro>::app]proc macro and implements the backend traits.
Example layout:
my-rticx/
├── Cargo.toml
├── src/
│ └── lib.rs # re-exports app macro and export module
└── my-rticx-macro/
├── Cargo.toml
└── src/
└── lib.rs # proc macro + backend impl
The macro crate implements rticx_core::CorePassBackend. This is the bulk of the target-specific work. Refer to the method table in Architecture for the full interface.
At minimum, you must implement:
-
generate_resource_proxy_lock_impl— how shared resources are locked. -
generate_global_definitions— any global constants or helper functions. -
wrap_task_execution— how a task body is wrapped in an interrupt handler. -
post_init— code after initialization. -
entry_name,entry_attrs— entry point naming and attributes. -
default_task_priority— fallback task priority. -
generate_interrupt_free_fn— the global critical-section function.
use proc_macro::TokenStream;
use rticx_core::RticMacroBuilder;
#[proc_macro_attribute]
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
let mut builder = RticMacroBuilder::new(MyBackend);
builder.bind_pre_core_pass(SoftwarePass::new(MySwBackend));
builder.bind_pre_core_pass(AutoAssignPass);
builder.build_rtic_macro(args, input)
}If your distribution uses software tasks, implement SwPassBackend:
impl SwPassBackend for MySwBackend {
fn generate_local_pend_fn(&self, empty_body_fn: ItemFn) -> ItemFn {
// Fill the local NVIC set-pending function
}
fn generate_cross_pend_fn(&self, empty_body_fn: ItemFn) -> Option<ItemFn> {
// Fill the cross-core pending function, or None for single-core
}
}The library crate re-exports the macro and provides the export module:
pub use my_rticx_macro::app;
pub mod export {
// Re-export target runtime helpers, e.g.:
// pub use cortex_m::peripheral::NVIC;
// pub use rticx_sw_pass::export::*;
}Users write:
use my_rticx::app;
#[app(device = ...)]
mod my_app { ... }You should expose a compilation pass as a Cargo feature when its syntax is optional for the distribution. This lets users opt into syntax extensions and keeps compile times, dependency trees, and generated code small when those extensions are not needed.
Feature-gating is appropriate when:
- The pass adds new syntax that not every application uses (e.g.,
#[sw_task],spawn,deadline = ...). - Omitting the pass significantly reduces compilation time, dependencies, or generated code.
- The distribution works correctly for a meaningful subset of applications without the pass.
Do not feature-gate a pass when:
- It is part of the distribution's core programming model and every application is expected to use it.
- It is required for the backend to produce correct code for the target.
The core compilation pass provided by rticx-core is always required and is not feature-gated.
[dependencies]
rticx-core = { path = "../rticx-core" }
rticx-sw-pass = { path = "../rticx-sw-pass", optional = true, features = ["proc-macro"] }
rticx-auto-assign = { path = "../rticx-auto-assign", optional = true }
[features]
swtasks = ["rticx-sw-pass"]
autoassign = ["rticx-auto-assign"]#[proc_macro_attribute]
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
let mut builder = RticMacroBuilder::new(MyBackend);
#[cfg(feature = "swtasks")]
builder.bind_pre_core_pass(rticx_sw_pass::SoftwarePass::new(MySwBackend));
#[cfg(feature = "autoassign")]
builder.bind_pre_core_pass(rticx_auto_assign::AutoAssignPass);
builder.build_rtic_macro(args, input)
}The user-facing library crate should mirror the macro crate features so users can enable them from their own Cargo.toml:
[dependencies]
rticx-macro = { path = "my-rticx-macro" }
[features]
swtasks = ["rticx-macro/swtasks"]
autoassign = ["rticx-macro/autoassign"]A pass is considered optional if the core RTICX syntax (#[app], #[init], #[idle], #[shared], #[task]) works correctly without it. Mandatory passes can be registered unconditionally.
- For single-core targets, implement only the core backend and ignore cross-core features.
- For multicore targets, you need to handle core entry points and cross-core dispatch.
Use the debug_expand feature of rticx-core to write the expanded macro output to examples/{binary_name}_expanded.rs:
[features]
debug_expand = ["rticx-core/debug_expand"]Study the existing reference distributions for concrete examples:
-
rticx-rp2040— dual-core Cortex-M0+ with software tasks. -
rticx-stm32-renode— multi-binary multicore build. -
rticx-hippo— single-core RISC-V with threshold-based locking. -
rticx-atalanta— single-core RISC-V.
- Writing Compilation Passes — if you need a new pass for your distribution.