# CI/CD Pipeline ## Infrastructure | Component | URL | Notes | |-----------|-----|-------| | GitHub | `https://github.com/msicie/apollo_nxt-trcaa` | Git server and CI/CD platform | | GitHub Actions | Native to GitHub | Hosted runners (`ubuntu-latest`, `macos-latest`, `macos-13`) | | ghcr.io | `ghcr.io/msicie/` | GitHub Container Registry for pre-baked CI images | --- ## Pre-baked Builder Images CI build and test jobs use pre-baked Docker images pushed to `ghcr.io/msicie/`. These images bake in all system dependencies (Tauri libs, Node.js, Rust toolchain, cross-compilers) so that CI jobs skip package installation entirely. | Image | Used by jobs | Contents | |-------|-------------|----------| | `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` | `rust-test`, `build-linux-amd64` | Rust 1.88 + rustfmt + clippy + Tauri amd64 libs + Node.js 22 | | `ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22` | `build-windows-amd64` | Rust 1.88 + mingw-w64 + NSIS + Node.js 22 | | `ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22` | `build-linux-arm64` | Rust 1.88 + aarch64 cross-toolchain + arm64 multiarch libs + Node.js 22 | **Rebuild triggers:** Rust toolchain version bump, webkit2gtk/gtk major version change, Node.js major version change. **How to rebuild images:** 1. Trigger `build-images.yml` via Actions → Build CI Docker Images → Run workflow 2. Confirm all 3 images appear in `ghcr.io/msicie/` 3. Only then merge workflow changes that depend on the new image contents --- ## Cargo and npm Caching All Rust and build jobs use `actions/cache@v4` to cache downloaded package artifacts. **Cargo cache** (Rust jobs): ```yaml - name: Cache cargo registry uses: actions/cache@v4 with: path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo- ``` **npm cache** (frontend and build jobs): ```yaml - name: Cache npm uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- ``` Cache keys for cross-compile jobs use a suffix to avoid collisions: - Windows build: `${{ runner.os }}-cargo-windows-${{ hashFiles('**/Cargo.lock') }}` - arm64 build: `${{ runner.os }}-cargo-arm64-${{ hashFiles('**/Cargo.lock') }}` --- ## Test Pipeline (`.github/workflows/test.yml`) **Triggers:** Every push to `main`, `feature/**`, `bug/**`, `fix/**` branches; pull requests targeting `main`. ``` Jobs (run in parallel): rust-test → cargo fmt --check → cargo clippy -- -D warnings → cargo test (64 tests) frontend-test → npx tsc --noEmit → npm run test:run (Vitest tests) ``` **Docker image used:** - `ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22` — both Rust and frontend steps Job names `rust-test` and `frontend-test` must match exactly — these are the required status check names in branch protection on `main`. --- ## Release Pipeline (`.github/workflows/release.yml`) **Triggers:** Push to `main` (auto-tag job), then release build/upload jobs run after `autotag` completes on `v*` tags. Auto-tags are created by the `autotag` job using `git tag` + `git push`. Release jobs depend on `autotag` and run in parallel. ``` Jobs (run in parallel after autotag): build-linux-amd64 → image: ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22 → cargo tauri build (x86_64-unknown-linux-gnu) → {.deb, .rpm, .AppImage} uploaded to GitHub Release → fails fast if no Linux artifacts are produced build-windows-amd64 → image: ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22 → cargo tauri build (x86_64-pc-windows-gnu) via mingw-w64 → {.exe, .msi} uploaded to GitHub Release → fails fast if no Windows artifacts are produced build-linux-arm64 → image: ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22 → cargo tauri build (aarch64-unknown-linux-gnu) → {.deb, .rpm, .AppImage} uploaded to GitHub Release → fails fast if no Linux artifacts are produced build-macos-arm64 → runs-on: macos-latest (Apple Silicon) → cargo tauri build (aarch64-apple-darwin) natively → {.dmg} uploaded to GitHub Release → existing same-name assets are deleted before upload (rerun-safe) → unsigned; after install run: xattr -cr /Applications/TRCAA.app build-macos-intel → runs-on: macos-13 (Intel x86_64) → cargo tauri build (x86_64-apple-darwin) natively → {.dmg} uploaded to GitHub Release ``` **Windows cross-compile environment:** ```yaml env: TARGET: x86_64-pc-windows-gnu CC_x86_64_pc_windows_gnu: x86_64-w64-mingw32-gcc CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc OPENSSL_NO_VENDOR: "0" OPENSSL_STATIC: "1" ``` **Artifacts per platform:** - Linux amd64: `.deb`, `.rpm`, `.AppImage` - Windows amd64: `.exe` (NSIS installer), `.msi` - Linux arm64: `.deb`, `.rpm`, `.AppImage` - macOS ARM64: `.dmg` - macOS Intel: `.dmg` --- ## Build Images Workflow (`.github/workflows/build-images.yml`) **Triggers:** Changes to `.docker/**` on `main`; manual `workflow_dispatch`. Builds and pushes all three pre-baked CI images to `ghcr.io/msicie/`: ``` Jobs (all run on ubuntu-latest): build-linux-amd64-image → .docker/Dockerfile.linux-amd64 → ghcr.io/msicie/trcaa-linux-amd64:rust1.88-node22 build-windows-cross-image → .docker/Dockerfile.windows-cross → ghcr.io/msicie/trcaa-windows-cross:rust1.88-node22 build-linux-arm64-image → .docker/Dockerfile.linux-arm64 → ghcr.io/msicie/trcaa-linux-arm64:rust1.88-node22 ``` Authentication: `docker login ghcr.io -u ${{ github.actor }} --password-stdin <<< "${{ secrets.GITHUB_TOKEN }}"`. Requires `packages: write` permission. --- ## Branch Protection `main` branch requires: - All changes via PR - 1 approving review - CODEOWNER review (`@Shaun-Arman-VFK387_moto` and `@github-copilot`) - Required status checks: `rust-test`, `frontend-test` - `enforce_admins: false` — owner and admins can bypass with `gh pr merge --admin` --- ## Changelog Generation Changelogs are generated automatically by **git-cliff** on every release. Configuration lives in `cliff.toml` at the repo root. ### How it works A `changelog` job in `release.yml` runs in parallel with the build jobs, immediately after `autotag` completes: 1. Clones the full repo history with all tags (`--depth=2147483647` — git-cliff needs every tag to compute version boundaries). 2. Downloads the git-cliff v2.7.0 static musl binary (~5 MB, no image change needed). 3. Runs `git-cliff --output CHANGELOG.md` to regenerate the full cumulative changelog. 4. Runs `git-cliff --latest --strip all` to produce release notes for the new tag only. 5. Updates the GitHub Release body with those notes via `gh release edit`. 6. Commits `CHANGELOG.md` to `main` with `[skip ci]` appended to the message. The `[skip ci]` token prevents `release.yml` from re-triggering on the CHANGELOG commit. 7. Uploads `CHANGELOG.md` as a release asset (replaces any previous version). ### cliff.toml reference | Setting | Value | |---------|-------| | `tag_pattern` | `v[0-9].*` | | `ignore_tags` | `rc\|alpha\|beta` | | `filter_unconventional` | `true` — non-conventional commits are dropped | | Included types | `feat`, `fix`, `perf`, `docs`, `refactor` | | Excluded types | `ci`, `chore`, `build`, `test`, `style` | ### Loop prevention The `[skip ci]` suffix on the CHANGELOG commit message is recognised by GitHub Actions and causes the workflow to be skipped for that push. Without it, the CHANGELOG commit would trigger `release.yml` again, incrementing the patch version forever. --- ## Known Issues & Fixes ### Debian Multiarch Breaks arm64 Cross-Compile (`held broken packages`) When using `rust:1.88-slim` (Debian Bookworm) with `dpkg --add-architecture arm64`, apt resolves amd64 and arm64 simultaneously against the same mirror. The `binary-all` package index is duplicated and certain `-dev` package pairs cannot be co-installed because they don't declare `Multi-Arch: same`. This produces `E: Unable to correct problems, you have held broken packages` and cannot be fixed by tweaking `sources.list` entries. **Fix**: Use `ubuntu:22.04` as the container image (see `Dockerfile.linux-arm64`). Ubuntu routes arm64 through `ports.ubuntu.com/ubuntu-ports` — a separate mirror from `archive.ubuntu.com` (amd64). There are no cross-arch index overlaps and the dependency resolver succeeds. Rust must be installed manually via `rustup`. ### `CI=true` Required by Tauri CLI GitHub Actions sets `CI=true` by default — `cargo tauri build` accepts this without modification. Prefix the command explicitly if your runner environment is unusual: ```yaml - run: CI=true cargo tauri build --target $TARGET ``` ### Windows DLL Export Ordinal Too Large `/usr/bin/x86_64-w64-mingw32-ld: error: export ordinal too large: 106290` Fix: `src-tauri/.cargo/config.toml`: ```toml [target.x86_64-pc-windows-gnu] rustflags = ["-C", "link-arg=-Wl,--exclude-all-symbols"] ``` ### Release Artifacts Not Uploaded **Cause:** `GITHUB_TOKEN` needs `contents: write` permission in the workflow. Verify the `release.yml` permissions block includes: ```yaml permissions: contents: write ``` ### Runner Group Restrictions (Enterprise) GitHub Enterprise organizations may restrict which repositories can use GitHub-hosted runners. If jobs are stuck in queue, contact your org admin to add the repository to the allowed runner group, or temporarily make the repo public for the bootstrap run.