Skip to content

Latest commit

 

History

History
71 lines (57 loc) · 3.57 KB

codegen.md

File metadata and controls

71 lines (57 loc) · 3.57 KB

Code generation in ports

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.

Implementing a codegen feature

Adding a new feature to a code generation port goes through three phases:

  1. Under loadstone_config/src/lib.rs, the Configuration struct is expanded with any number of new fields detailing the new feature. These fields can be at the top level of Configuration or inside any of its members. This Configuration struct is what ultimately gets serialized into the .ron file, so anything included in it will be available for the code generation engine.

  2. 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 the update function has access to a variable of type Configuration, 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 under loadstone_front/src/app/menus/mod.rs, in the configure_boot_metrics function.

  3. 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 the Configuration struct into source code that Loadstone can include. This is done using the quote! macro, which can be used to build arbitrary Rust source code. A good example of this process can be found in the generate_top_level_module function, which constructs the source for the top level autogenerated module.

Integrating with CI

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.