Build Guide
This guide explains how to build the Wsh-Shell example application both on a PC and on embedded hardware.
Prerequisites
Ensure the required toolchain components are installed on your system:
For extra actions you should use python virtualenv with requirements.txt:
Or just run setup python env vscode task
Project Structure
The configuration file wsh_shell_cfg.h is not tracked in git. It is generated automatically in the example/ directory when building the example.
Building and Running Example
By default, the project is built in Debug mode. To build with the Release preset:
Generating Config Only
To copy the default config template into example/ without building:
This creates example/wsh_shell_cfg.h from src/wsh_shell_cfg_def.h.
Edit this file to customize the shell configuration for your project.
Cleaning the Build
⚠️ This does not delete
example/wsh_shell_cfg.h. Remove it manually if you want to regenerate it from the default template.
Measuring the Memory Footprint
Feature flags are only interesting if their cost is known, and toggling them by
hand in a real firmware project is slow and hard to compare. utils/measure-footprint.py
does it mechanically: for every configuration it renders a temporary
wsh_shell_cfg.h from the default template, rebuilds the library for the target
CPU, links it against a synthetic stub that touches every public entry point, and
sums the linker-map sections that came from the shell's own object files. libc,
startup code and the stub are never counted, so the numbers are comparable across
configurations and machines.
Useful options:
| Option | Meaning |
|---|---|
--cpu, --opt |
Target and optimization level (default cortex-m7, -O1) |
--cc, --nm |
Toolchain binaries (default arm-none-eabi-gcc/-nm) |
--markdown |
Markdown table, ready to paste into the README |
--json |
Raw numbers for CI or plotting |
Important
--mode drop is the number to trim flash by. It rebuilds the full configuration
with one feature removed, so it answers the only question that matters in practice —
"what do I actually save by switching this off?" Feature costs are not additive:
code is shared between features, and a flag that looks cheap on a bare build can be
the most expensive one in a full build (WSH_SHELL_SUBCOMMANDS: ~0.6 KB alone,
~6.4 KB once the default wsh command tree exists).
The script also reports sizeof(WshShell_t) for each configuration, read back
from a probe symbol in the linked ELF, so RAM cost is measured on the target's
word size rather than the host's.
Because every mode rebuilds the library from scratch for each configuration, the
script doubles as a feature-flag build matrix: it is the only thing here that
compiles the disabled-feature branches. A #else stub whose signature drifted from
its header breaks nobody's build until an integrator turns that feature off, so CI
runs all three modes (config_matrix job in .gitlab-ci.yml) and fails on the first
configuration that does not compile. The generated tables are kept as job artifacts.
Example on Hardware
It has been moved to separate repos:
- https://github.com/whoosh-bike/wsh-shell-blue-pill-example
- https://github.com/whoosh-bike/wsh-shell-black-pill-example
Changing the Compiler
By default gcc is used. To switch to clang:
Formatting
Both routes run the same script, so src/ and example/ are always formatted
identically. The generated example/wsh_shell_cfg.h is skipped on purpose — it must
stay a byte-identical copy of src/wsh_shell_cfg_def.h.
Static Analysis
Findings are printed and saved to the log file; the exit code is cppcheck's own, so
the target still fails the build on findings (--error-exitcode=1). The log path is
covered by .gitignore.
The target depends on gen-config and analyses src/ with example/wsh_shell_cfg.h
on the include path. Without a resolvable config cppcheck cannot evaluate the feature
macros: it drowns the report in missingInclude noise, checks far more #ifdef
combinations (slow), and still misses real findings that only appear once the buffer
sizes are known.
Class-wide suppressions live in .cppcheck-suppressions, each with the reason it is
there — mostly artefacts of analysing a library without its callers (unusedFunction,
staticFunction). One-off exceptions are inline // cppcheck-suppress <id> comments
next to the code they excuse (--inline-suppr).
Additional Notes
- You can adjust build flags in
example/Makefile(optimization level, debug symbols, etc.) - If using Windows, please use a WSL environment