Loadstone is roughly divided in two layers. An abstract layer under
src/devices/
, where you can find behaviour common to all targets, and a
target-specific layer under src/ports/
, composed of code that only applies to a
particular microprocessor or family.
The main role of the ports
module is to define exactly how a bootloader is
built. This involves many decisions, such as what pins are assigned to what
functionality (serial, flash, etc), which features to enable, and how to
distribute the image banks in the memory map.
Since those decisions must be made at compile time, this would require a lot of
source code to be modified for each particular application, which isn't a clean
way to work. It would likely result in dozens of folders under ports
containing particular permutations of pins and banks used for different
projects.
To combat this, ports can make use of code generation. There's a secondary
crate under loadstone_front
, which can be ran natively or as a WASM web app,
and whose sole purpose is to offer an intuitive interface to define all the
port-specific bootloader options. These options are then encoded as a .ron file
(a format similar to Json), which can be read by Loadstone at build time, in
order to generate source code that conforms to these options.
$ LOADSTONE_CONFIG=`cat loadstone_config.ron` cargo b loadstone
The command above will attempt to build Loadstone based on the provided config. If any feature flags are missing, the resulting compiler error will provide a list of them.
Note that it's not mandatory for the ports to make use of this feature. It's possible
to define a manual port that makes no use of code generation at all, in which
case the LOADSTONE_CONFIG
environment variable can be assigned an empty
string.
Adding a new feature to a code generation port goes through three phases:
-
Under
loadstone_config/src/lib.rs
, theConfiguration
struct is expanded with any number of new fields detailing the new feature. These fields can be at the top level ofConfiguration
or inside any of its members. ThisConfiguration
struct is what ultimately gets serialized into the.ron
file, so anything included in it will be available for the code generation engine. -
Under
loadstone_front/src/app/mod.rs
, the GUI code is expanded with any necessary widgets, labels, etc. required to configure the new feature. Note how theupdate
function has access to a variable of typeConfiguration
, which is the struct that was expanded in step one. A simple example to follow in order to understand how to offer a widget interface over a feature can be found underloadstone_front/src/app/menus/mod.rs
, in theconfigure_boot_metrics
function. -
Under
loadstone_config/src/codegen/mod.rs
or any of its submodules, the code generation functions are expanded with the logic necessary to transform the feature defined in theConfiguration
struct into source code that Loadstone can include. This is done using thequote!
macro, which can be used to build arbitrary Rust source code. A good example of this process can be found in thegenerate_top_level_module
function, which constructs the source for the top levelautogenerated
module.
Adding a new code generation feature requires updating the CI scripts to be
aware of it. .github/workflows/actions.yml
contains a few embedded .ron
samples so that CI can verify a variety of feature permutations. If your feature
adds a new field to the Configuration
struct, these new samples must be
updated with the new field.