Skip to content

Repository files navigation

Scaffold

Generic component scaffolding tool for firmware projects. Generates files from Jinja2 templates and supports fully interactive or pre-filled CLI usage. Primarily focused on Zephyr RTOS but not exclusive to it.

Setup

pip install -e .

This enables both python -m scaffold and the scaffold CLI entry point.

Usage

# Fully interactive — prompts for everything
python -m scaffold
# Component type selected, rest interactive
python -m scaffold driver
# Pre-fill fields, prompt for the rest
python -m scaffold driver --name my_drv --path drivers/
# Pre-fill everything, accept all defaults silently
python -m scaffold driver --name my_drv --path drivers/ --skip-defaults
# Load external component definitions from a directory
python -m scaffold --components-dir my-components/widget

Options

OptionDescription
[component]Component type to scaffold. Omit to select interactively.
--name, -nComponent name in snake_case.
--path, -pOutput path relative to the current directory.
--skip-defaults, -SAccept all defaults without prompting.
--components-dir, -CDirectory to load extra component modules from.
--hide-defaultsSuppress built-in components; show only external ones.

Configuration

Scaffold loads configuration from up to two files before applying CLI flags. This lets you persist settings like components-dir per-user or per-project without passing them on every invocation.

Resolution order (lowest → highest priority)

PrioritySource
1$HOME/.config/scaffold/config.yml — user-level
2<cwd>/scaffold.yml — project-level (git-trackable)
3CLI flags

Keys from lower-priority sources are preserved when a higher-priority source does not set them.

Config file format

# scaffold.ymlcomponents-dir: path/to/componentshide_defaults: true # suppress built-in components; show only external ones

components-dir is always resolved relative to the file that declares it: scaffold.yml paths resolve against <cwd>, user-config paths resolve against ~/.config/scaffold/, and --components-dir on the CLI resolves against the current working directory.

Schema

Valid keys are defined in scaffold/config_schema.json. Any config file that contains an unknown key, a wrong value type, or malformed YAML is rejected with a clear error message — scaffold will not start.

Example: committing a project-level config

# scaffold.yml (committed to your repo)components-dir: tools/scaffold-components

All team members get the same components-dir automatically after cloning, with no manual setup required.

Built-in components

ComponentDefault pathGenerated files
driverdrivers/CMakeLists.txt, Kconfig, README.md, {name}.h, {name}.c
serviceservices/CMakeLists.txt, Kconfig, README.md, include/service/{name}_access.h, src/{name}.c, src/{name}_internal.h
liblib/CMakeLists.txt, Kconfig, README.md, {name}.h, {name}.c
samplesamples/CMakeLists.txt, README.md, sample.yaml, prj.conf, src/main.c
testtests/CMakeLists.txt, README.md, testcase.yaml, prj.conf, src/main.c

External components

Component definitions can live outside this repository. Point --components-dir at any directory containing .py files that define a COMPONENT constant. Each component declares its own template directory, making external component sets fully self-contained.

my-components/
├── widget/
│ ├── __init__.py
│ ├── widget.py # COMPONENT definition
│ └── templates/ # Jinja2 templates for widget
│ └── widget.c.j2
└── sensor/
├── __init__.py
├── sensor.py
└── templates/
└── sensor.c.j2

A minimal external component:

frompathlibimportPathfromscaffold.descriptorsimportComponent, Field, TemplateFilefromscaffold.validatorsimportcheck_path_conflict, validate_snake_case_HERE=Path(__file__).parentCOMPONENT=Component(
name="my_type",
default_path="src/",
fields=[
Field(name="name", prompt="Name (snake_case)", validator=validate_snake_case),
Field(name="path", prompt="Output path", default="src/"),
],
validators=[check_path_conflict],
templates=[
TemplateFile("main.c.j2", "{name}.c"),
],
templates_dir=_HERE/"templates",
)

Load the component set at runtime:

scaffold --components-dir my-components/widget widget --name my_widget

External components with the same name as a built-in will override it. Use --hide-defaults (or hide_defaults: true in scaffold.yml) to suppress built-ins entirely and show only external components.

Adding a built-in component type

  1. Create a subdirectory example_components/<type>/.
  2. Add __init__.py (empty) and <type>.py with a COMPONENT constant. Set templates_dir=Path(__file__).parent / "templates".
  3. Add Jinja2 templates under example_components/<type>/templates/.
  4. Auto-discovery picks it up — nothing else needs to change.

See example_components/driver/driver.py as a reference.

West integration

Add scaffold as a module in your west manifest:

# west.ymlmanifest:
projects:
- name: scaffoldurl: https://github.com/edgebr/scaffoldrevision: mainpath: tools/scaffold

Once added, run west update and use west scaffold with the same arguments as the CLI:

west scaffold driver --name my_drv --path drivers/ --skip-defaults
west scaffold --components-dir my-components/ --hide-defaults widget --name my_widget

About

A component generation tool for helping develop in zephyr

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages