Files
feedBack-desktop/docs/BUILD_ARCHITECTURE.md
T
2026-06-16 18:48:12 +02:00

204 lines
11 KiB
Markdown

# Build system architecture
How Slopsmith Desktop is built — the flow of scripts, config, and
packaging across local dev and CI.
## Philosophy
1. **npm scripts are the API.** Every build step is callable via
`npm run <name>`. CI, local dev, and the DevContainer all use the
same entry points.
2. **Shell scripts implement.** Complex bundling lives in
`scripts/bundle-*.sh`; npm just invokes them.
3. **Configuration is centralized.** Tool versions + external pins
live in `.build-config.json`; per-OS system deps in `.packages/`.
4. **Reproducibility is pinned, not floating.** External dependencies
(FluidR3/GeneralUser soundfonts, fluidsynth Windows zip) are tied to
specific commits or SHA256s; CI and local see the same bytes.
## High-level flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ Build environments │
│ │
│ GitHub Actions DevContainer (Docker) Developer CLI │
│ │ │ │ │
│ └─────────────────────┼───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ npm scripts │ │
│ │ (package.json) │ ← single-source entry │
│ └─────────────────────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │build:native│ │ bundle │ │ build:ts │ │
│ │ │ │ │ │ │ │
│ │• build:audio│ │• bundle: │ │ (tsc) │ │
│ │ │ │ slopsmith │ │ │ │
│ └────────────┘ │• bundle: │ └────────────┘ │
│ │ python │ │
│ │• bundle: │ │
│ │ binaries │ │
│ │• bundle: │ │
│ │ soundfont │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ electron-builder │ │
│ │ (dist:linux etc.) │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Configuration files │
│ │
│ .build-config.json .packages/ │
│ ├── versions ├── apt.txt (Ubuntu/Debian) │
│ │ ├── node ├── brew.txt (macOS) │
│ │ ├── python ├── choco.txt (Windows) │
│ │ ├── electron └── README.md │
│ │ ├── cmake │
│ │ └── ubuntu │
│ └── external │
│ ├── fluidsynth_windows (version + URL) │
│ └── soundfont_general_user (URL + SHA256) │
└─────────────────────────────────────────────────────────────────────┘
```
## Detailed flow for `npm run dist:linux`
```
npm run dist:linux
└── npm run dist -- --linux
└── npm run bundle && npm run build:ts && electron-builder --linux
└── bundle
├── bundle-slopsmith.sh — server.py + lib + static + plugins
├── bundle-python.sh — portable Python 3.12 runtime + pip + app deps
├── bundle-binaries.sh — ffmpeg + vgmstream-cli + fluidsynth + its .so chain
└── bundle-soundfont.sh — GeneralUser-GS.sf2 (SHA256-verified)
└── build:ts
└── tsc
└── electron-builder
└── AppImage + .deb
```
For `build:native` (run separately, not part of `dist`):
```
npm run build:native
└── build:audio — cmake-js builds the JUCE C++ native addon
```
## Scripts directory
```
scripts/
├── bundle.sh — top-level Linux delegator (→ bundle-*.sh)
├── bundle-slopsmith.sh — copy server + plugins
├── bundle-python.sh — portable Python runtime (Linux)
├── bundle-binaries.sh — ffmpeg + vgmstream + fluidsynth + libs (Linux)
├── bundle-soundfont.sh — GeneralUser-GS.sf2 download + verify (cross-platform)
├── build-linux-release.sh — Docker wrapper for reproducible Linux AppImage
├── parse-build-config.py — JSON value extractor
└── setup-dev.sh — local prerequisite checker
```
### Why modular
- **Composability**: debug or re-run a single step (`npm run bundle:python`)
- **Readability**: each script does one thing and has a single reason to change
- **CI/local parity**: CI calls the same scripts where platform-independent; platform-specific inline bash stays in `build.yml` for macOS/Windows (dylibbundler, zip downloads) only because those tools don't fit the Linux shell-script model
### Platform scope per script
| Script | Linux | macOS | Windows |
|---|---|---|---|
| `bundle.sh` | yes | n/a | n/a |
| `bundle-slopsmith.sh` | yes | yes (untested) | yes (untested) |
| `bundle-python.sh` | yes | no (CI inline) | no (CI inline) |
| `bundle-binaries.sh` | yes | no (CI uses dylibbundler inline) | no (CI downloads zips) |
| `bundle-soundfont.sh` | yes | yes | yes |
## Configuration
### `.build-config.json`
Tool versions pinned once, read by the CI workflow (via `node -p`) and
by the DevContainer (via `parse-build-config.py`). External pins
(soundfont SHA256, fluidsynth Windows zip URL) live under `external.*`.
Update tool versions in one place; every consumer picks them up.
### `.packages/`
Platform system-dep lists. One package per line; `#` comments and
blank lines are filtered by consumers. See `.packages/README.md` for
manual install snippets.
### Electron version
One source of truth: `devDependencies.electron` in `package.json`.
`build:audio` derives `--runtime-version` via
`node -p "require('electron/package.json').version"` so cmake-js
rebuilds the native addon against the currently-installed Electron.
## CI vs local parity
| Aspect | GitHub Actions | DevContainer |
|---|---|---|
| Entry | `npm run dist:linux` (via checkout + setup-* + install) | `npm run dist:linux` (inside container) |
| Node / Python | From `.build-config.json` | From `.build-config.json` |
| System deps | `.packages/apt.txt` | `.packages/apt.txt` |
| Slopsmith source | Cloned fresh to `$RUNNER_TEMP` | Bind-mounted from `../slopsmith` |
| Python bundle | `scripts/bundle-python.sh` | `scripts/bundle-python.sh` |
| Binary bundle | Inline bash (ffmpeg / vgmstream / fluidsynth lib chain) | `scripts/bundle-binaries.sh` (invoked by `npm run bundle`) |
| Soundfont bundle | `scripts/bundle-soundfont.sh` | `scripts/bundle-soundfont.sh` |
| Smoke test | `fluidsynth --version` + `ffmpeg -version` + `vgmstream-cli --help` | (run manually) |
## Adding a new build step
1. Create a script in `scripts/` (e.g. `bundle-foo.sh`)
2. Register an npm script in `package.json` (e.g. `"bundle:foo": "bash scripts/bundle-foo.sh"`)
3. Wire into the appropriate chain (`bundle`, `build:native`, etc.)
4. Test locally: `npm run bundle:foo`
5. Open a PR — CI consumes the same npm scripts
## Troubleshooting
### Run individual steps
```bash
npm run bundle:python # rebuild just the Python runtime
npm run bundle:binaries # rebuild just the binary chain
npm run bundle:soundfont # re-verify the soundfont
```
### Read the current config
```bash
python3 scripts/parse-build-config.py .build-config.json
python3 scripts/parse-build-config.py .build-config.json .versions.electron
```
### Local vs CI divergence
If local and CI builds differ:
1. `.build-config.json` — pinned versions identical?
2. `.packages/apt.txt` — are you running in the DevContainer?
3. The CI workflow inline-bundles some things differently on
macOS/Windows (dylibbundler, zip downloads). Local linux dev won't
reproduce those.
## See also
- [`../.build-config.json`](../.build-config.json)
- [`../.packages/`](../.packages/)
- [`../.devcontainer/`](../.devcontainer/)
- [`../.github/workflows/build.yml`](../.github/workflows/build.yml)