Wholesale copy of all Kindred Create additions that don't conflict with upstream FreeCAD code: - kindred-icons/ (1444 Catppuccin Mocha SVG icon overrides) - src/Mod/Create/ (Kindred Create workbench) - src/Gui/ Kindred source files (FileOrigin, OriginManager, OriginSelectorWidget, CommandOrigin, BreadcrumbToolBar, EditingContext) - src/Gui/Icons/ (Kindred branding and silo icons) - src/Gui/PreferencePacks/KindredCreate/ - src/Gui/Stylesheets/ (KindredCreate.qss, images_dark-light/) - package/ (rattler-build recipe) - docs/ (architecture, guides, specifications) - .gitea/ (CI workflows, issue templates) - mods/silo, mods/ztools submodules - .gitmodules (Kindred submodule URLs) - resources/ (kindred-create.desktop, kindred-create.xml) - banner-logo-light.png, CONTRIBUTING.md
12 KiB
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
- Install system prerequisites (GL, X11, fontconfig headers)
- Checkout with recursive submodules
- Install pixi
- Restore ccache from prior builds
- Configure via
pixi run cmake --preset conda-linux-release - Build with
pixi run cmake --build build/release -j$(nproc) - Run C++ unit tests under xvfb (Assembly_tests excluded due to discovery timeouts)
- Install to
build/release/install - Run Python CLI tests (
FreeCADCmd -t 0) - Run GUI tests headless (
xvfb-run FreeCAD -t 0) - Package as
.tar.xzwith SHA256 checksum - 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
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:
pixi installinpackage/rattler-build/pixi run -e package create_bundle-- invokeslinux/create_bundle.sh- 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 viasquashfs-root/AppRun - Creates a squashfs AppImage (zstd compressed) with SHA256 checksums
- Intermediate build files are cleaned up to free space for the .deb step
package/debian/build-deb.shbuilds 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
- Installs to
macOS build
Builds natively on macOS runners (Intel via macos-13, Apple Silicon via macos-14):
pixi run -e package create_bundleinvokesosx/create_bundle.sh- The bundle script:
- Creates
FreeCAD.appbundle with conda environment inContents/Resources/ - Runs
fix_macos_lib_paths.pyto convert absolute rpaths to@loader_path - Builds native macOS launcher from
launcher/CMakeLists.txt - Patches
Info.plistwith version info - Creates DMG via
dmgbuild - If
SIGN_RELEASE=true: signs and notarizes viamacos_sign_and_notarize.zsh
- Creates
Windows build
Builds natively on Windows runner:
pixi run -e package create_bundleinvokeswindows/create_bundle.sh(bash via Git for Windows)- The bundle script:
- Copies conda environment DLLs, Python, and FreeCAD binaries to
FreeCAD_Windows/ - Applies SSL certificate patch (
ssl-patch.py) - Creates
qt6.conffor Qt6 plugin paths - Creates
.exewrapper shims via Chocolateyshimgen - Compresses to
.7z(compression level 9) - If
MAKE_INSTALLER=trueand NSIS available: builds NSIS installer
- Copies conda environment DLLs, Python, and FreeCAD binaries to
- 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:
- Downloads all artifacts from completed build jobs
- Collects release files (AppImage, .deb, checksums) into a
release/directory - Deletes any existing Gitea release for the same tag (allows re-running)
- Creates a new Gitea release via the REST API (
/api/v1/repos/{owner}/{repo}/releases) - 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 buildsmacos-14-- macOS Apple Silicon buildswindows-latest-- Windows builds
Registering a self-hosted runner
If using self-hosted runners instead of hosted:
# 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:
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:
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 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
- Create a bundle script at
package/rattler-build/<platform>/create_bundle.sh - Add the platform to
package/rattler-build/pixi.tomlif needed - Add a new job to
release.ymlfollowing the existing pattern - Add the new artifact pattern to the
publish-releasejob'sfindcommand - 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
maincache)
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_IDsecret set to the Apple Developer identitySIGN_RELEASE=trueenvironment 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=trueenvironment variable- Chocolatey
shimgen.exefor wrapper executables
If NSIS is unavailable, only the .7z portable archive is produced.