# Build Scripts Unified build system for Slopsmith Desktop supporting Linux (via Docker), macOS, and Windows. ## Quick Start ```bash ./scripts/build-release.sh ``` ## Call Hierarchy ``` ┌─────────────────┐ │ GitHub Actions │ │ build.yml │ └────────┬────────┘ │ │ (calls the same script everywhere) │ ▼ ┌──────────────────┐ │ build-release.sh │ Platform dispatcher └────────┬─────────┘ │ │ Detects host OS: │ - Linux → build-linux-docker.sh │ - macOS → build-macos.sh │ - Windows → build-windows.sh │ ▼ ┌──────────────────────────────────────┐ │ For Linux: Docker wrapper │ │ build-linux-docker.sh → │ │ Docker container: │ │ ./build-linux-ubuntu.sh │ └──────────────────────────────────────┘ │ │ Sources: │ ▼ ┌─────────────────┐ │ build-common.sh │ Shared build logic (~250 lines) └─────────────────┘ ▲ ▲ ▲ │ │ │ │ │ ├─── Platform-specific implementations: │ │ install_system_deps() │ │ bundle_python_impl() │ │ bundle_binaries_impl() │ │ │ └─ build-macos.sh │ build-windows.sh │ build-linux-ubuntu.sh │ └─ platform: mac / win / linux ``` ## How It Works 1. **build-release.sh** - Platform dispatcher. Detects OS and routes to the right build script. 2. **Linux builds** - Always Docker-based for reproducibility: - `build-linux-docker.sh` → Docker container → `build-linux-ubuntu.sh` → packages 3. **Native builds** - macOS and Windows run directly on host: - `build-macos.sh` - Uses Homebrew dependencies - `build-windows.sh` - Uses Git Bash, downloads binaries 4. **build-common.sh** - Shared logic sourced by platform scripts: - Validates environment (Node.js, Python, .NET) - Runs npm install, builds C++ engine, bundles resources - Calls platform-specific functions for: dependency installation, Python bundling, binary bundling ## Platform-Specific Scripts ### Files | Script | Purpose | Requirements | Output | |--------|---------|--------------|--------| | `build-linux-docker.sh` | Reproducible Docker build | Docker, adjacent slopsmith repo | `.AppImage`, `.deb` | | `build-linux-ubuntu.sh` | Native Ubuntu build | Ubuntu/Debian + apt | `.AppImage`, `.deb` | | `build-macos.sh` | Native macOS build | Homebrew, Xcode CLI | `.dmg`, `.zip` | | `build-windows.sh` | Native Windows build | Git Bash, Node.js, Python, .NET | `.exe` installer | ### Two-Layer Ubuntu Builds Most Linux distributions don't have identical package versions. Using Docker ensures the build is reproducible: - **Direct use**: `./scripts/build-linux-docker.sh` - **Inside container**: Runs `./scripts/build-linux-ubuntu.sh` - **Why**: Guarantees identical builds across different Linux distros ### Platform-Specific Functions Each platform script implements four functions that `build-common.sh` calls: ```bash install_system_deps() { # Platform-specific: apt install, brew install, choco install, or downloads } bundle_python_impl() { # Linux: copy system Python # macOS: download python-build-standalone # Windows: download embeddable Python zip } bundle_binaries_impl() { # Linux: copy existing + patchelf # macOS: copy existing + dylibbundler + sign # Windows: download binaries (ffmpeg, vgmstream, fluidsynth) } get_expected_artifacts() { # Globs verify_artifacts checks at the end of the build, e.g. # printf "%s\n" "$PROJECT_DIR/release/*.dmg" "$PROJECT_DIR/release/*.zip" } ``` ## Requirements | Platform | Requirements | |----------|--------------| | **Linux (Docker)** | Docker, adjacent slopsmith repo | | **Linux (native)** | Ubuntu/Debian, sudo, Node.js 22+, Python 3.12+, .NET 10+, apt dependencies | | **macOS** | macOS 11+, Homebrew, Xcode CLI, Node.js 22+, Python 3.12+, .NET 10+ | | **Windows** | Windows 10/11, Git for Windows + Bash, Node.js 22+, Python 3.12+, .NET 10+ | **Windows Note:** These scripts must run in Git Bash (MSYS), not `cmd.exe` or PowerShell. They rely on MSYS-style paths such as `/tmp`, which work fine inside Git Bash but won't resolve correctly from a native Windows shell — so for local development outside GitHub Actions, run the scripts from a Git Bash terminal. ## GitHub Actions The CI workflow is extremely simple - just calls the same script: ```yaml # .github/workflows/build.yml steps: # Install platform-specific dependencies (apt, brew, or choco) - name: Install dependencies run: ... # Build using the same script developers use locally - name: Build shell: bash run: ./scripts/build-release.sh ``` Result: - Local builds and CI use identical code paths - Build failures can be reproduced and debugged locally - Workflow is "dumb" - all logic lives in versioned scripts ## macOS Code Signing & Notarization The macOS build signs every bundled native binary (fluidsynth, ffmpeg, vgmstream-cli, embedded Python interpreter + dylibs + extension `.so`s) with a Developer ID Application certificate, then electron-builder signs the `.app` and submits it to Apple's notary service. With signing in place, users get no Gatekeeper "app is damaged" warning on first launch. ### Required GitHub secrets | Secret | Purpose | |---|---| | `APPLE_CERTIFICATE_P12_BASE64` | Developer ID Application cert exported as `.p12`, then `base64 -i cert.p12` | | `APPLE_CERTIFICATE_PASSWORD` | The `.p12` export password | | `APPLE_SIGNING_IDENTITY` | Full identity, e.g. `Developer ID Application: Your Name (TEAMID)` | | `APPLE_ID` | Apple ID email | | `APPLE_APP_SPECIFIC_PASSWORD` | App-specific password from appleid.apple.com (not the regular Apple ID password) | | `APPLE_TEAM_ID` | 10-char team ID from developer.apple.com → Membership | | `KEYCHAIN_PASSWORD` | Any random string — used for the temporary CI keychain | When `APPLE_CERTIFICATE_P12_BASE64` is unset (forks, contributor PRs without secret access), the certificate-import step is skipped and `sign-macos-binaries.sh` exits early. The build still completes — it just produces an unsigned `.app` that will trigger Gatekeeper on macOS. ### Local macOS builds Local builds without `APPLE_SIGNING_IDENTITY` set produce an unsigned `.app` (same as before signing was added). To produce a signed local build for testing, ensure your Developer ID Application certificate is in your login keychain and run: ```bash APPLE_SIGNING_IDENTITY="Developer ID Application: Your Name (TEAMID)" \ ./scripts/build-release.sh ``` This signs the bundled binaries but does **not** notarize — notarization requires `APPLE_ID` + `APPLE_APP_SPECIFIC_PASSWORD` + `APPLE_TEAM_ID` env vars and is run by electron-builder when those are present. ### Local cmake-js cache `build-windows.sh` only force-clears `$HOME/.cmake-js` when `$CI` is set (or `CLEAN_CMAKE_JS=1` is exported). Local Windows builds reuse the cache by default; set `CLEAN_CMAKE_JS=1` if you need a fully fresh build to mirror CI behaviour.