Skip to content

Build

Build #547

Workflow file for this run

name: Build
# Build + package (cpack) on each platform, uploaded as workflow
# artifacts. This does NOT install or run anything -- see test.yml, which
# runs the three regression suites.
#
# (This header used to say that installing and running the GUI apps needed
# "a lot more complexity than a build-and-package health check warrants".
# That turned out to be `apt install xvfb` plus letting the app wrapper
# start its own services, so test.yml does it now. The parser and dialog
# suites need neither a display nor services and run on every push in
# about three minutes.)
# Packaging itself needs neither: cpack just archives already-built
# binaries, no display involved. The goal is early, continuous signal on
# "does this even compile (and package) here", especially for platforms
# nobody is routinely building on locally.
#
# RPM packages built here are real .rpm artifacts, but incomplete ones --
# CPACK_RPM_PACKAGE_REQUIRES (the RPM equivalent of the existing
# CPACK_DEBIAN_PACKAGE_DEPENDS) isn't set yet, so the package won't
# auto-pull its runtime deps (apache2 equivalent, mosquitto, wx python
# bindings, ...) the way the .deb does. Worth fixing before treating
# these as real release artifacts, not before treating them as "does
# cpack's RPM generator work at all here" signal.
#
# Target platforms and why:
# - Debian: what the primary maintainer actually develops on.
# - Ubuntu: most popular desktop/CI Linux distro.
# - RHEL family: what a lot of institutional/cluster environments run.
# GitHub Actions has no real RHEL runner (needs a subscription), so
# this uses Rocky Linux -- a bug-for-bug free rebuild of RHEL from
# the same sources, the standard stand-in for "does this work on
# RHEL" in CI that doesn't have a Red Hat subscription to spend.
# - WSL2: NOT a separate job. WSL2 runs a real Linux kernel with a real
# distro userland (Ubuntu by default) on top of it -- there's no
# separate "WSL2 build" to test, the Ubuntu/Debian jobs below already
# cover it directly. The one WSL2-relevant wrinkle, filename
# case-collisions (#40), is actually a non-issue for WSL2 specifically
# (its ext4-backed filesystem is case-sensitive like any other Linux
# disk) -- it only bites on a native-Windows checkout or a default
# case-insensitive macOS volume.
# - macOS: tracks GitHub #3 (currently expected to fail -- see the job
# below for why).
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
only:
description: "Run just this job (debian, vendored-inventor, ubuntu, rocky, fedora, macos); empty runs all"
required: false
default: ""
# One run per branch. Without this every push starts a full run and none
# of them cancel the older ones, so a run of quick pushes leaves a dozen
# stale runs competing for the same runners -- which is exactly what
# happened on 2026-09-23: nine pushes in a day, eleven jobs each, and CI
# stopped completing anything for six hours. A superseded run tells you
# nothing a newer one will not.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
debian:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'debian' }}
name: Debian (apt, containerized)
runs-on: ubuntu-latest
# Bounded so a hung job cannot hold a runner slot for GitHub's
# 6-hour default; see the concurrency note above.
timeout-minutes: 90
container: debian:trixie
steps:
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential gfortran cmake ninja-build \
libwxgtk3.2-dev libxerces-c-dev libgl-dev libglu1-mesa-dev \
libgtk-3-dev libx11-dev libice-dev libxt-dev libjpeg-dev \
libmosquitto-dev mosquitto-dev libaprutil1-dev mosquitto git dpkg-dev file libssh-dev \
python3 libcoin-dev libegl-dev
- uses: actions/checkout@v4
- name: Configure
run: cmake -G Ninja -B build-cmake -DECCE_REQUIRE_LIBSSH=ON
- name: Build
# -k 0: report every failing file, not only the first few.
run: cmake --build build-cmake -- -k 0
- name: Package
working-directory: build-cmake
run: cpack -G DEB
- uses: actions/upload-artifact@v4
with:
name: ecce-debian-trixie-deb
path: build-cmake/*.deb
# Build only, no packaging: keeps the vendored Inventor core
# (-DECCE_USE_COIN=OFF, #166) compiling for the one release it stays a
# fallback.
vendored-inventor:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'vendored-inventor' }}
name: Debian, vendored Inventor viewer (build only)
runs-on: ubuntu-latest
timeout-minutes: 90
container: debian:trixie
steps:
- name: Install build dependencies
run: |
apt-get update
apt-get install -y \
build-essential gfortran cmake ninja-build python3 \
libwxgtk3.2-dev libxerces-c-dev libgl-dev libglu1-mesa-dev \
libgtk-3-dev libx11-dev libice-dev libxt-dev libjpeg-dev \
libmosquitto-dev mosquitto-dev libaprutil1-dev mosquitto git dpkg-dev file libssh-dev
- uses: actions/checkout@v4
- name: Configure
run: cmake -G Ninja -B build-oiv -DECCE_USE_COIN=OFF -DECCE_REQUIRE_LIBSSH=ON
- name: Build
run: cmake --build build-oiv
ubuntu:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'ubuntu' }}
name: Ubuntu (apt)
runs-on: ubuntu-latest
# Bounded so a hung job cannot hold a runner slot for GitHub's
# 6-hour default; see the concurrency note above.
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential gfortran cmake ninja-build \
libwxgtk3.2-dev libxerces-c-dev libgl-dev libglu1-mesa-dev \
libgtk-3-dev libx11-dev libice-dev libxt-dev libjpeg-dev \
libmosquitto-dev mosquitto-dev libaprutil1-dev mosquitto git dpkg-dev libssh-dev \
python3 libcoin-dev libegl-dev
- name: Configure
run: cmake -G Ninja -B build-cmake -DECCE_REQUIRE_LIBSSH=ON
- name: Build
# -k 0: report every failing file, not only the first few.
run: cmake --build build-cmake -- -k 0
- name: Package
working-directory: build-cmake
run: cpack -G DEB
- uses: actions/upload-artifact@v4
with:
name: ecce-ubuntu-latest-deb
path: build-cmake/*.deb
rocky:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'rocky' }}
name: Rocky Linux (dnf, RHEL family, containerized)
runs-on: ubuntu-latest
# Bounded so a hung job cannot hold a runner slot for GitHub's
# 6-hour default; see the concurrency note above.
timeout-minutes: 90
container: rockylinux:9
steps:
- name: Install build dependencies
run: |
dnf install -y epel-release 'dnf-command(config-manager)'
# ninja-build and a few -devel packages live in the CodeReady
# Builder repo, not EPEL itself -- EPEL's own release package
# prints a reminder about this ("Many EPEL packages require the
# CRB repository... /usr/bin/crb enable") but doesn't enable it
# automatically.
dnf config-manager --set-enabled crb
dnf install -y \
gcc gcc-c++ gcc-gfortran make cmake ninja-build git rpm-build \
wxGTK-devel xerces-c-devel \
mesa-libGL-devel mesa-libGLU-devel \
gtk3-devel libX11-devel libICE-devel libXt-devel libjpeg-turbo-devel \
mosquitto-devel apr-util-devel mosquitto libssh-devel \
Coin4-devel mesa-libEGL-devel python3
# actions/checkout doesn't work reliably inside an arbitrary
# container image (no git-installed-as-a-prereq guarantee, and the
# container user often isn't the checkout action's expected uid) --
# install git above first, then check out manually instead.
- name: Check out source
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git clone --depth 1 --branch "${GITHUB_REF_NAME}" \
"https://github.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE" \
|| git clone --depth 1 "https://github.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE"
- name: Configure
working-directory: ${{ github.workspace }}
run: cmake -G Ninja -B build-cmake -DECCE_REQUIRE_LIBSSH=ON
- name: Build
working-directory: ${{ github.workspace }}
# -k 0: report every failing file, not only the first few.
run: cmake --build build-cmake -- -k 0
- name: Package
working-directory: ${{ github.workspace }}/build-cmake
run: cpack -G RPM
- uses: actions/upload-artifact@v4
with:
name: ecce-rockylinux9-rpm
path: build-cmake/*.rpm
fedora:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'fedora' }}
name: Fedora (dnf, containerized)
runs-on: ubuntu-latest
# Bounded so a hung job cannot hold a runner slot for GitHub's
# 6-hour default; see the concurrency note above.
timeout-minutes: 90
container: fedora:latest
steps:
- name: Install build dependencies
run: |
dnf install -y \
gcc gcc-c++ gcc-gfortran make cmake ninja-build git rpm-build \
wxGTK-devel xerces-c-devel \
mesa-libGL-devel mesa-libGLU-devel \
gtk3-devel libX11-devel libICE-devel libXt-devel libjpeg-turbo-devel \
mosquitto-devel apr-util-devel mosquitto libssh-devel \
Coin4-devel mesa-libEGL-devel python3
- name: Check out source
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git clone --depth 1 --branch "${GITHUB_REF_NAME}" \
"https://github.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE" \
|| git clone --depth 1 "https://github.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE"
- name: Configure
working-directory: ${{ github.workspace }}
run: cmake -G Ninja -B build-cmake -DECCE_REQUIRE_LIBSSH=ON
- name: Build
working-directory: ${{ github.workspace }}
# -k 0: report every failing file, not only the first few.
run: cmake --build build-cmake -- -k 0
- name: Package
working-directory: ${{ github.workspace }}/build-cmake
run: cpack -G RPM
- uses: actions/upload-artifact@v4
with:
name: ecce-fedora-latest-rpm
path: build-cmake/*.rpm
macos:
if: ${{ github.event_name != 'workflow_dispatch' || inputs.only == '' || inputs.only == 'macos' }}
name: macOS (${{ matrix.arch }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { os: macos-latest, arch: arm64, target: "11.0", experimental: false }
# Catalina: the oldest macOS many 2012-2014 Intel Macs can run.
- { os: macos-15-intel, arch: x86_64, target: "10.15", experimental: true }
env:
# Oldest macOS the app is built for (#133). Our code and every
# bundled library are built to this; the minimum-macOS step fails
# the job on any Mach-O above it.
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.target }}
ECCE_DEPS: /Users/runner/ecce-deps
# Bounded so a hung job cannot hold a runner slot for GitHub's
# 6-hour default; see the concurrency note above.
timeout-minutes: 90
# Known-unsupported today (GitHub #3) -- ECCE's offscreen-rendering
# path (src/inv/dbso/SoOffscreenRenderer.inc) calls raw Xlib/GLX
# (XOpenDisplay/glXCreateContext), which has no macOS-native
# equivalent (Cocoa/NSOpenGL, or GLX only via XQuartz). This job is
# here to track the gap with a real, current compile log instead of
# nothing -- it's expected to fail until #3 gets real porting work,
# not a sign the workflow itself is broken.
# x86_64 may fail until it is proven; arm64 must be green.
continue-on-error: ${{ matrix.experimental }}
steps:
- uses: actions/checkout@v4
- name: Install build tools
# Only tools: the libraries come from build-deps.sh, because
# Homebrew bottles are built for the runner's own macOS.
run: brew install cmake ninja pkgconf gcc bash
- name: Cache libraries built for macOS ${{ env.MACOSX_DEPLOYMENT_TARGET }}
id: deps
uses: actions/cache/restore@v4
with:
path: ${{ env.ECCE_DEPS }}
key: macos-deps-${{ matrix.arch }}-${{ env.MACOSX_DEPLOYMENT_TARGET }}-${{ hashFiles('packaging/macos/build-deps.sh') }}
- name: Build libraries (wxWidgets, Coin, Xerces, Mosquitto, libssh, OpenSSL)
if: steps.deps.outputs.cache-hit != 'true'
run: packaging/macos/build-deps.sh "$ECCE_DEPS"
- name: Save the libraries
if: steps.deps.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ${{ env.ECCE_DEPS }}
key: ${{ steps.deps.outputs.cache-primary-key }}
- name: Configure
# ECCE_HOME_DIR/ECCE_WRAPPER_DESTINATION point the install at a
# staging tree inside the workspace for the run step below.
# Homebrew's prefixes are ignored so nothing is picked up from
# them by accident.
run: |
export PATH="$ECCE_DEPS/bin:$PATH"
export PKG_CONFIG_LIBDIR="$ECCE_DEPS/lib/pkgconfig"
cmake -G Ninja -B build-cmake -DECCE_USE_COIN=ON \
-DCMAKE_OSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" \
-DCMAKE_PREFIX_PATH="$ECCE_DEPS" \
-DCMAKE_IGNORE_PREFIX_PATH="/opt/homebrew;/usr/local" \
-DCMAKE_MAKE_PROGRAM="$(command -v ninja)" -DPKG_CONFIG_EXECUTABLE="$(command -v pkg-config)" \
-DCMAKE_Fortran_COMPILER="$(ls $(brew --prefix)/bin/gfortran-* | head -1)" \
-DFREETYPE_UNQUALIFIED_INCLUDE_DIR="$ECCE_DEPS/include/freetype2/freetype" \
-DFREETYPE_ROOT_INCLUDE_DIR="$ECCE_DEPS/include/freetype2" \
-DECCE_HOME_DIR="$PWD/stage/ecce" -DECCE_WRAPPER_DESTINATION="$PWD/stage/bin" \
-DCMAKE_INSTALL_PREFIX="$PWD/stage/ecce"
- name: Build
# -k 0: report every failing file, not only the first few.
run: cmake --build build-cmake -- -k 0
# Does it run? (#133) Starts the installed apps on the runner's
# logged-in desktop and screenshots them. Allowed to fail; the
# verdict is in the summary and the "macos-run" artifact.
- name: Install to a staging prefix and run
continue-on-error: true
timeout-minutes: 25
run: |
export PATH="$ECCE_DEPS/sbin:$ECCE_DEPS/bin:$PATH"
cmake --install build-cmake
tests/macos/run.sh stage macos-run
cat macos-run/summary.txt >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
if: always()
with:
name: macos-run-${{ matrix.arch }}
path: macos-run/
if-no-files-found: ignore
# ECCE.app in a .dmg (#133), then the start test again on a copy of
# the app taken out of the mounted image, away from the build tree.
# The two steps below fail the job when they fail. The job itself
# is continue-on-error (the native build is still experimental), so
# the workflow stays green but the job and these steps show red and
# the summary says FAILED.
- name: Package ECCE.app
run: |
export PATH="$ECCE_DEPS/bin:$PATH"
packaging/macos/make-app.sh stage dist
# Oldest macOS the bundle can run on (#133): the step fails when any
# Mach-O needs more than the deployment target or is not this runner's arch.
- name: Minimum macOS of every Mach-O in the app
run: |
mkdir -p minos
app=$(mktemp -d)
mnt=$(mktemp -d)
hdiutil attach dist/ECCE-*.dmg -mountpoint "$mnt" -nobrowse -quiet
cp -R "$mnt/ECCE.app" "$app/"
hdiutil detach "$mnt" -quiet
python3 packaging/macos/minos.py "$app/ECCE.app" minos/minos.txt \
--floor "$MACOSX_DEPLOYMENT_TARGET" --arch "${{ matrix.arch }}" > /dev/null || rc=$?
echo "runner: $(sw_vers -productVersion) $(uname -m)" >> minos/minos.txt
{ echo '### Minimum macOS (ECCE.app)'; echo '```';
sed -n '/^Mach-O files/,$p' minos/minos.txt; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
exit ${rc:-0}
- name: Run the packaged app
if: always() && hashFiles('dist/*.dmg') != ''
timeout-minutes: 25
run: |
pkill -f "$PWD/stage" || true
mnt=$(mktemp -d)
hdiutil attach dist/ECCE-*.dmg -mountpoint "$mnt" -nobrowse -quiet
appdir=$(mktemp -d)
cp -R "$mnt/ECCE.app" "$appdir/"
hdiutil detach "$mnt" -quiet
# The build tree and the staging prefix must not be needed.
mv build-cmake build-cmake.gone
mv stage stage.gone
ECCE_APP="$appdir/ECCE.app" tests/macos/run.sh "$appdir/ECCE.app/Contents/Resources" macos-app-run
cat macos-app-run/summary.txt >> "$GITHUB_STEP_SUMMARY"
- name: Summary on failure
if: failure()
run: echo '### macOS packaging FAILED (see the failed step above)' >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
if: always()
with:
name: ECCE-macos-dmg-${{ matrix.arch }}
path: |
dist/*.dmg
minos/minos.txt
if-no-files-found: ignore
- uses: actions/upload-artifact@v4
if: always()
with:
name: macos-app-run-${{ matrix.arch }}
path: macos-app-run/
if-no-files-found: ignore