Files
create/docs/CI_CD.md
forbes bfb2728f8d docs: update OVERVIEW.md and CI_CD.md to match current repo state
- Fix stale submodule URLs (gitea.kindred.internal -> git.kindred-systems.com)
- Update silo submodule name and commit, add note about repo split
- Document actual release mechanism (curl API, not release-action)
- Mark macOS/Windows builds as disabled in platform matrix
- Update ccache docs: date-based key rotation, rattler-build env forwarding
- Document disk cleanup steps and runner cleanup daemon
- Update appimagetool extraction note (FUSE unavailable in containers)
2026-02-07 09:34:41 -06:00

309 lines
12 KiB
Markdown

# CI/CD
Kindred Create uses Gitea Actions for continuous integration and release builds. Workflows are defined in `.gitea/workflows/`.
## Overview
| Workflow | Trigger | Purpose | Artifacts |
|----------|---------|---------|-----------|
| `build.yml` | Push to `main`, pull requests | Build + test | Linux tarball |
| `release.yml` | Tags matching `v*` or `latest` | Release build | AppImage, .deb |
All builds run on public runners in dockerized mode. No host-mode or internal infrastructure is required.
---
## Build workflow (`build.yml`)
Runs on every push to `main` and on pull requests. Builds the project in an Ubuntu 24.04 container and runs the test suite.
### Steps
1. Install system prerequisites (GL, X11, fontconfig headers)
2. Checkout with recursive submodules
3. Install pixi
4. Restore ccache from prior builds
5. Configure via `pixi run cmake --preset conda-linux-release`
6. Build with `pixi run cmake --build build/release -j$(nproc)`
7. Run C++ unit tests under xvfb (Assembly_tests excluded due to discovery timeouts)
8. Install to `build/release/install`
9. Run Python CLI tests (`FreeCADCmd -t 0`)
10. Run GUI tests headless (`xvfb-run FreeCAD -t 0`)
11. Package as `.tar.xz` with SHA256 checksum
12. Upload artifact (14-day retention)
### Caching
ccache is persisted between builds using `actions/cache`. Cache keys use a date suffix so entries rotate daily (one save per day per branch). Saves are skipped when the exact key already exists, preventing duplicate entries that fill runner storage.
```
Key: ccache-build-{branch}-{YYYYMMDD}
Fallback: ccache-build-{branch}-
Fallback: ccache-build-main-
```
Release builds use a separate key namespace (`ccache-release-linux-{YYYYMMDD}`) because they compile with different optimization flags (`-O3`). The rattler-build script (`build.sh`) explicitly sets `CCACHE_DIR` and `CCACHE_BASEDIR` since rattler-build does not forward environment variables from the parent process.
ccache configuration: 4 GB max, zlib compression level 6, sloppy mode for include timestamps and PCH.
---
## Release workflow (`release.yml`)
Triggered by pushing a tag matching `v*` (e.g., `v0.1.0`, `v1.0.0-rc1`). Builds release packages for all platforms in parallel, then creates a Gitea release with all artifacts.
### Triggering a release
```bash
git tag v0.1.0
git push origin v0.1.0
```
Or manually via `workflow_dispatch` with a tag input.
Tags containing `rc`, `beta`, or `alpha` are marked as pre-releases.
### Platform matrix
| Job | Runner | Container | Preset | Output | Status |
|-----|--------|-----------|--------|--------|--------|
| `build-linux` | `ubuntu-latest` | `ubuntu:24.04` | `conda-linux-release` | AppImage, .deb | Active |
| `build-macos` (Intel) | `macos-13` | native | `conda-macos-release` | .dmg (x86_64) | Disabled |
| `build-macos` (Apple Silicon) | `macos-14` | native | `conda-macos-release` | .dmg (arm64) | Disabled |
| `build-windows` | `windows-latest` | native | `conda-windows-release` | .exe (NSIS), .7z | Disabled |
Only the Linux build is currently active. macOS and Windows jobs are defined but commented out pending runner availability or cross-compilation support. After `build-linux` succeeds, `publish-release` collects artifacts and creates the Gitea release.
### Linux build
Both workflows start with a disk cleanup step that removes pre-installed bloat (dotnet, Android SDK, etc.) to free space for the build.
Uses the rattler-build packaging pipeline:
1. `pixi install` in `package/rattler-build/`
2. `pixi run -e package create_bundle` -- invokes `linux/create_bundle.sh`
3. The bundle script:
- Copies the pixi conda environment to an AppDir
- Strips unnecessary files (includes, static libs, cmake files, `__pycache__`)
- Downloads `appimagetool`, extracts it with `--appimage-extract` (FUSE unavailable in containers), and runs via `squashfs-root/AppRun`
- Creates a squashfs AppImage (zstd compressed) with SHA256 checksums
4. Intermediate build files are cleaned up to free space for the .deb step
5. `package/debian/build-deb.sh` builds a .deb from the AppDir
- Installs to `/opt/kindred-create/` with wrapper scripts in `/usr/bin/`
- Sets up LD_LIBRARY_PATH, QT_PLUGIN_PATH, PYTHONPATH in wrappers
- Creates desktop entry, MIME types, AppStream metainfo
### macOS build
Builds natively on macOS runners (Intel via `macos-13`, Apple Silicon via `macos-14`):
1. `pixi run -e package create_bundle` invokes `osx/create_bundle.sh`
2. The bundle script:
- Creates `FreeCAD.app` bundle with conda environment in `Contents/Resources/`
- Runs `fix_macos_lib_paths.py` to convert absolute rpaths to `@loader_path`
- Builds native macOS launcher from `launcher/CMakeLists.txt`
- Patches `Info.plist` with version info
- Creates DMG via `dmgbuild`
- If `SIGN_RELEASE=true`: signs and notarizes via `macos_sign_and_notarize.zsh`
### Windows build
Builds natively on Windows runner:
1. `pixi run -e package create_bundle` invokes `windows/create_bundle.sh` (bash via Git for Windows)
2. The bundle script:
- Copies conda environment DLLs, Python, and FreeCAD binaries to `FreeCAD_Windows/`
- Applies SSL certificate patch (`ssl-patch.py`)
- Creates `qt6.conf` for Qt6 plugin paths
- Creates `.exe` wrapper shims via Chocolatey `shimgen`
- Compresses to `.7z` (compression level 9)
- If `MAKE_INSTALLER=true` and NSIS available: builds NSIS installer
3. NSIS installer (`package/WindowsInstaller/`):
- Multi-language support (28 languages)
- File associations for `.FCStd`
- Start menu and desktop shortcuts
- LZMA compression
### Publish step
`publish-release` runs after all platform builds succeed:
1. Downloads all artifacts from completed build jobs
2. Collects release files (AppImage, .deb, checksums) into a `release/` directory
3. Deletes any existing Gitea release for the same tag (allows re-running)
4. Creates a new Gitea release via the REST API (`/api/v1/repos/{owner}/{repo}/releases`)
5. Uploads each artifact as a release attachment via the API
The release payload (tag name, body, prerelease flag) is built entirely in Python to avoid shell/Python type mismatches. Tags containing `rc`, `beta`, or `alpha` are automatically marked as pre-releases.
Requires `RELEASE_TOKEN` secret with repository write permissions.
---
## Runner configuration
### Public runner setup
Workflows target public Gitea-compatible runners. The Linux build job runs inside a Docker container (`ubuntu:24.04`) for isolation and reproducibility. macOS and Windows jobs run directly on hosted runners.
**Required runner labels:**
- `ubuntu-latest` -- Linux builds (dockerized)
- `macos-13` -- macOS Intel builds
- `macos-14` -- macOS Apple Silicon builds
- `windows-latest` -- Windows builds
### Registering a self-hosted runner
If using self-hosted runners instead of hosted:
```bash
# Download the Gitea runner binary
curl -fsSL -o act_runner https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64
chmod +x act_runner
# Register with your Gitea instance
./act_runner register \
--instance https://git.kindred-systems.com \
--token <RUNNER_REGISTRATION_TOKEN> \
--labels ubuntu-latest:docker://ubuntu:24.04
# Start the runner
./act_runner daemon
```
For dockerized mode, the runner executes jobs inside containers. Ensure Docker is installed on the runner host.
The runner config file (`config.yaml`) should set:
```yaml
runner:
labels:
- "ubuntu-latest:docker://ubuntu:24.04"
container:
privileged: false
network: bridge
```
### Runner cleanup daemon
A cleanup script at `.gitea/runner/cleanup.sh` prevents disk exhaustion on self-hosted runners. It uses a tiered approach based on disk usage thresholds:
| Threshold | Action |
|-----------|--------|
| 70% | Docker cleanup (stopped containers, dangling images, build cache) |
| 80% | Purge act_runner cache entries older than 7 days, clean inactive workspaces |
| 90% | System cleanup (apt cache, old logs, journal vacuum to 100 MB) |
| 95% | Emergency: remove all act_runner cache entries and Docker images |
Install via the provided systemd units (`.gitea/runner/cleanup.service` and `.gitea/runner/cleanup.timer`) to run every 30 minutes:
```bash
sudo cp .gitea/runner/cleanup.sh /usr/local/bin/runner-cleanup.sh
sudo cp .gitea/runner/cleanup.service /etc/systemd/system/
sudo cp .gitea/runner/cleanup.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now cleanup.timer
```
---
## Secrets
| Secret | Used by | Purpose |
|--------|---------|---------|
| `RELEASE_TOKEN` | `release.yml` (publish) | Gitea API token for creating releases |
| `SIGNING_KEY_ID` | macOS build (optional) | Apple Developer signing identity |
---
## Build tools
### pixi
[pixi](https://pixi.sh) manages all build dependencies via conda-forge. It ensures consistent toolchains (clang, cmake, Qt6, OpenCASCADE, etc.) across all platforms without relying on system packages.
Key pixi tasks (defined in root `pixi.toml`):
| Task | Description |
|------|-------------|
| `pixi run configure` | CMake configure (defaults to debug) |
| `pixi run build` | CMake build (defaults to debug) |
| `pixi run install` | CMake install |
| `pixi run test` | Run ctest |
| `pixi run freecad` | Launch built FreeCAD |
| `pixi run build-release` | CMake build (release) |
### CMake presets
Defined in `CMakePresets.json`. Release builds use:
| Platform | Preset | Compiler | Linker |
|----------|--------|----------|--------|
| Linux | `conda-linux-release` | clang/clang++ | mold |
| macOS | `conda-macos-release` | clang/clang++ | default |
| Windows | `conda-windows-release` | MSVC | default |
### ccache
Compiler cache is used across all builds to speed up incremental compilation. Cache is persisted between CI runs via `actions/cache` with date-based key rotation. Configuration:
- Max size: 4 GB
- Compression: zlib level 6
- Sloppy mode: include timestamps, PCH defines, time macros
- `CCACHE_BASEDIR`: set to workspace root (build workflow) or `$SRC_DIR` (rattler-build) for path normalization across runs
---
## Adding a new platform or package format
1. Create a bundle script at `package/rattler-build/<platform>/create_bundle.sh`
2. Add the platform to `package/rattler-build/pixi.toml` if needed
3. Add a new job to `release.yml` following the existing pattern
4. Add the new artifact pattern to the `publish-release` job's `find` command
5. Update the release body template with the new download entry
---
## Troubleshooting
### Build fails with missing system libraries
The Docker container installs only minimal dependencies. If a new dependency is needed, add it to the `apt-get install` step in the workflow. Check the pixi environment first -- most dependencies should come from conda-forge, not system packages.
### ccache misses are high
ccache misses spike when:
- The compiler version changes (pixi update)
- CMake presets change configuration flags
- First build of the day (date-based key rotates daily)
- New branch without a prior cache (falls back to `main` cache)
For release builds, ensure `build.sh` is correctly setting `CCACHE_DIR=/tmp/ccache-kindred-create` -- rattler-build does not forward environment variables from the workflow, so ccache config must be set inside the script.
Check `pixi run ccache -s` output (printed in the "Show ccache statistics" step) for hit/miss ratios. The "Prepare ccache" step also prints the full ccache configuration via `ccache -p`.
### Submodule checkout fails
Submodules pointing to internal Gitea instances require either:
- Public mirrors of the submodule repositories
- Runner network access to the Gitea instance
- Submodule URLs updated to public-facing addresses in `.gitmodules`
### macOS signing
Code signing and notarization require:
- `SIGNING_KEY_ID` secret set to the Apple Developer identity
- `SIGN_RELEASE=true` environment variable
- Valid Apple Developer account credentials in the runner keychain
Without signing, the DMG is created unsigned. Users will see Gatekeeper warnings.
### Windows NSIS installer not created
The NSIS installer requires:
- NSIS 3.x installed in the runner environment (available via conda or Chocolatey)
- `MAKE_INSTALLER=true` environment variable
- Chocolatey `shimgen.exe` for wrapper executables
If NSIS is unavailable, only the `.7z` portable archive is produced.