Development Guide
Note
These instructions were written with macOS in mind. It should work elsewhere, but we have not personally verified this.
We hang out over in the Grout Development Channel on the RomM Discord. Come join us!
Prerequisites
Local Development
- Go v1.24+
- Task (for running the handy build scripts)
- SDL2 Shared Libraries (can be installed via Homebrew)
Local Builds / Packaging
- Docker Desktop or equivalent
- We love OrbStack (not a sponsor)
Getting Started
- Clone the Grout repository.
- Run
task hooks-setupto install git hooks. - Make a copy of
.env.devand save it as.envin the root of the cloned repository. - Fill out the
.envfile. Here are descriptions of the various values you can set.ENVIRONMENT=DEV(mandatory), this will disable some Gabagool features behind the scenesWINDOW_WIDTH(optional)WINDOW_HEIGHT(optional)NITRATES[true | false] (optional) This is used for Gabagool development debuggingCFW[MUOS | KNULLI | SPRUCE | NEXTUI] (mandatory), this controls how Grout interacts with and places filesBASE_PATH(mandatory), this acts as the root path like you would have on a handheld (e.g./mmc/sdcardon muOS). Have the subdirectory structure of this path match the CFW you are working on.
- Run / Debug
app/grout.go, making sure to reference the.envfile in your run configuration.
Project Structure
The codebase is laid out fairly well. It attempts to keep everything grouped by feature / function / domain.
appcontains the glue, the main function, finite state machine for screen transitions, and the setup / cleanup code.bioshandles BIOS file operationscachecontains the logic for the SQLite database that powers the local cachecfwcontains all the logic for adapting Grout to the various CFWs that are supporteddocsfor the user guide and other repo housekeeping, including this document!internalthe college educated utils package. App-wide / stateless utilities live hereresourcesthe splash screen image and localization files live here, along with the go file that embeds themromma client library for the RomM API.- Why wasn't this generated with the OpenAPI spec? We tried a number of the codegen tools for OpenAPI and they weren't compatible with version 3 of the spec and hacking around this limitation produced frustrating to use code.
scriptscontains the scripts (and metadata) associated with creating a package for each CFWsynccontains the save sync functionalityuicontains the screens that the FSM references inapp/states.goupdatehandles the in-app updater functionality, excluding the UIversionexposes the version information that is injected at build time. Having it as its own package made the script cleaner.
Packaging
The taskfile.yml defines scripts for building and packaging Grout for various CFWs. All builds target ARM64 Linux
and use Docker for cross-compilation.
Quick Start
# Build and package for all platforms
task all
# Or build with a local gabagool workspace (for gabagool development)
task all-local
Build Process
The build happens in two stages:
-
Docker Build (
task build) - Cross-compiles the Go binary for ARM64 Linux inside a Docker container. This ensures consistent builds regardless of your host OS and handles SDL2 dependencies. -
Extract (
task extract) - Copies the compiled binary and required shared libraries (likelibSDL2_gfx) from the Docker container to the localbuild/directory.
Platform-Specific Packaging
After building, you can package for individual platforms:
| Task | Platform | Output Location |
|---|---|---|
task package-next |
NextUI (TrimUI) | build/Grout.pak/ |
task package-muos |
muOS | build/muOS/Grout/, build/Grout.muxapp |
task package-knulli |
Knulli | build/Knulli/Grout/ |
task package-spruce |
Spruce | build/Spruce/Grout/ |
Each packaging task copies the binary, launch scripts from scripts/<platform>/, shared libraries, and documentation
into the appropriate directory structure for that CFW.
Deployment via ADB
For rapid testing, you can deploy directly to a connected device, assuming that the device has ADB available:
# NextUI (TrimUI devices)
task adb-next
# muOS (SD card 1 or 2)
task adb-muos-sd1
task adb-muos-sd2
# Knulli
task adb-knulli
These tasks will remove any existing installation and push the freshly built package to the device.
Local Gabagool Development
When developing gabagool alongside Grout, use the -local variants:
task build-local # Build using local gabagool via go.work
task all-local # Build and package all platforms with local gabagool
This requires a go.work file in the parent directory that references both projects.
Output Structure
After running task all, the build/ directory will contain:
build/
├── grout # ARM64 Linux binary
├── lib/ # Shared libraries
│ └── libSDL2_gfx-1.0.so.0
├── Grout.pak/ # NextUI package
├── Grout.muxapp # muOS archive (ready to install)
├── muOS/Grout/ # muOS package (unpacked)
├── Knulli/Grout/ # Knulli package
└── Spruce/Grout/ # Spruce package
Helper Tools
The taskfile.yml includes several utility tasks for common development workflows.
Internationalization (i18n)
Grout uses go-i18n for localization. The workflow for updating translations:
# Extract new messages and find missing translations (recommended)
task i18n
# Or run steps individually:
task i18n:extract # Extract messages from source to active.en.toml
task i18n:merge # Compare against other locales, output missing to translations_todo/
The i18n task will:
- Scan the codebase for translatable strings and update
resources/locales/active.en.toml - Compare against each locale (es, fr, de, it, pt, ja, ru) to find missing translations
- Output any missing translations to
translations_todo/<lang>.toml - Simplify the locale files to a clean
key = "value"format
Code Quality
This runs go fmt, go vet, and staticcheck across the codebase.
Requires staticcheck to be installed (
go install honnef.co/go/tools/cmd/staticcheck@latest).
Media Conversion
# Convert MP4 video to animated WebP (interactive, prompts for paths)
task mp4-to-webp
# Resize all user guide screenshots to 1024px width
task resize-user-guide-images
The mp4-to-webp task is useful for creating animated preview images for documentation.
The resize-user-guide-images makes sure all the user guide screenshots are the same size.