Files
monocoque/.github/workflows/ci.yaml
T

502 lines
23 KiB
YAML

name: Make Packages
on:
push:
tags: [ "*" ]
workflow_dispatch: {}
# Called by release-please.yml once it has cut a release. A tag pushed by
# release-please carries the default GITHUB_TOKEN, and GitHub deliberately
# refuses to start a workflow from an event that token created -- so the
# `push: tags` trigger above never fires for a release-please tag. Being
# callable means the release build runs inside release-please's own run
# instead, with no personal access token to store on a public repo.
workflow_call:
inputs:
release_tag:
description: >-
Tag release-please just created. Supplies the package version and
names the release to upload to, both of which would otherwise come
from github.ref_name.
type: string
required: true
jobs:
build-monocoque-debs:
# Previously this matrix used `runs-on: ${{ matrix.os }}` with
# debian-latest/debian-stable as runner *labels*, which only resolve to
# self-hosted runners. With none online those two legs sat queued
# indefinitely (observed: four runs stuck 17-60min, only the
# ubuntu-latest leg ever completing) until cancelled by hand. Building
# each distro in a container on ordinary GitHub-hosted runners removes
# the self-hosted dependency entirely.
#
# matrix.os stays the distro *name* because it's load-bearing: it picks
# the per-distro control file under tools/distro/debian/dpkg/<os>/ and
# names the released .deb. matrix.image is what it actually builds in.
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
image: ubuntu:latest
- os: debian-latest
image: debian:testing
- os: debian-stable
image: debian:stable-slim
runs-on: ubuntu-latest
container:
image: ${{ matrix.image }}
permissions:
contents: write
steps:
# Must come before actions/checkout: these images are minimal and ship
# no git, so checkout would fall back to a tarball download and silently
# skip submodules -- simapi and nappgui_src are both required to build.
# No sudo either; container jobs already run as root.
- name: Install build dependencies
run: |
apt-get update
apt-get install -y --no-install-recommends \
ca-certificates curl git \
build-essential cmake pkg-config \
libgtk-3-dev libuv1-dev libargtable2-dev libserialport-dev \
libconfig-dev libhidapi-dev liblua5.4-dev libxdg-basedir-dev \
libxml2-dev libpulse-dev libproc2-dev libcurl4-openssl-dev \
libglu1-mesa-dev
- uses: actions/checkout@v4
with:
submodules: recursive
# $GITHUB_WORKSPACE, not ${{ github.workspace }}: in a container job the
# expression still renders the *host* path (/home/runner/work/...) while
# the repo is actually mounted at /__w/... inside the container. Using
# the expression made cmake fail with `The source directory
# "/home/runner/work/monocoque/monocoque" does not exist`. The env var is
# set to the container-side path, so it's correct either way.
- name: Set build dir
id: strings
shell: bash
run: |
echo "build-output-dir=$GITHUB_WORKSPACE/build" >> "$GITHUB_OUTPUT"
github_sha_hash=${{ github.sha }}
echo "github-sha-short=${github_sha_hash:0:7}" >> $GITHUB_OUTPUT
- name: Configure CMake
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DBUILD_GUI=on
-DCMAKE_CXX_COMPILER=g++
-DCMAKE_C_COMPILER=gcc
-DCMAKE_BUILD_TYPE=Release
-S "$GITHUB_WORKSPACE"
- name: Build
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release
- name: Create directories in package
run: |
mkdir PKG_SOURCE
mkdir -p PKG_SOURCE/DEBIAN
mkdir -p PKG_SOURCE/usr/bin/
mkdir -p PKG_SOURCE/usr/share/applications
mkdir -p PKG_SOURCE/usr/share/pixmaps
mkdir -p PKG_SOURCE/usr/share/icons/hicolor/scalable/apps
mkdir -p PKG_SOURCE/usr/share/monocoque
- name: Download xpm icon
run: |
curl -sSL https://repo.spacefreak18.xyz/monocoque/icons/monocoque.xpm -o PKG_SOURCE/usr/share/pixmaps/monocoque.xpm
- name: Download svg icon
run: |
curl -sSL https://repo.spacefreak18.xyz/monocoque/icons/monocoque.svg -o PKG_SOURCE/usr/share/icons/hicolor/scalable/apps/monocoque.svg
- name: Determine package version
id: pkgver
shell: bash
env:
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
# The control files carry a hardcoded `Version: 1` that nobody
# maintains, so every .deb ever released claims version 1 regardless
# of its tag. Take the version from the tag instead.
#
# Guarded rather than used blindly: a Debian version must start with a
# digit, so a branch build (workflow_dispatch) or a non-release tag
# like `pkgtest-5` would make dpkg-deb fail outright. Anything that
# doesn't look like a version falls back to 0.0.0.
v=""
# RELEASE_TAG is set only when release-please calls this workflow;
# its tag exists but the run itself is on a branch, so GITHUB_REF_*
# would report master and fall through to 0.0.0.
v="${RELEASE_TAG#v}"
[ -z "$v" ] && [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}"
case "$v" in
[0-9]*) ;;
*) v="0.0.0" ;;
esac
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "packaging as version $v"
- name: Copy script files around to stop .github from being added to the package then build the package
run: |
cp ./tools/distro/debian/dpkg/${{ matrix.os }}/control ./PKG_SOURCE/DEBIAN/control
sed -i "s/^Version:.*/Version: ${{ steps.pkgver.outputs.version }}/" ./PKG_SOURCE/DEBIAN/control
cp ./tools/distro/debian/dpkg/copyright ./PKG_SOURCE/DEBIAN/copyright
cp ./build/monocoque ./PKG_SOURCE/usr/bin/monocoque
cp ./build/gmonocoque ./PKG_SOURCE/usr/bin/gmonocoque
cp ./tools/monocoque.desktop ./PKG_SOURCE/usr/share/applications/monocoque.desktop
dpkg-deb --build PKG_SOURCE monocoque-${{ matrix.os }}.deb
echo "--- control as packaged ---"
dpkg-deb --field monocoque-${{ matrix.os }}.deb Package Version
- name: Release the Package
# Only a tag can publish a release; without this a workflow_dispatch run
# builds everything correctly and then reports failure on a step that
# cannot work, which makes a green build look broken.
if: github.ref_type == 'tag' || inputs.release_tag != ''
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ inputs.release_tag || github.ref_name }}
files: monocoque-${{ matrix.os }}.deb
# release-please writes the release body from the changelog it just
# generated. Letting this action generate its own notes as well would
# overwrite them, so it only does so for a hand-pushed tag.
generate_release_notes: ${{ inputs.release_tag == '' }}
build-monocoque-rpms:
# Same self-hosted-label problem as the deb jobs above (fedora-43 was a
# runner label with nothing online to claim it); now a container on an
# ordinary GitHub-hosted runner, with fedora-44 added alongside 43.
strategy:
fail-fast: false
matrix:
include:
- os: fedora-43
image: fedora:43
- os: fedora-44
image: fedora:44
runs-on: ubuntu-latest
container:
image: ${{ matrix.image }}
permissions:
contents: write
steps:
# The -devel list is the one fedora.spec's own header comment documents,
# plus gtk3/libcurl/mesa-libGLU to match its `Requires:` line -- the spec
# configures with -DBUILD_GUI=on, so the GUI's deps are build deps too.
#
# Retried because this pulls openh264 from Fedora's third-party Cisco
# repo (ciscobinary.openh264.org), which is a single-mirror CDN outside
# Fedora's own infrastructure and has proven flaky: the fedora-43 leg of
# the first run died on `Curl error (7): Could not connect to server`
# for it while fedora-44 downloaded the very same package fine in the
# same run -- i.e. transient, not a dependency or package-name problem.
# Not disabling that repo outright: openh264 is still pulled in even
# with weak deps off, so something here requires it outright (which
# package wasn't traced) and dropping the repo would likely break
# dependency resolution rather than fix anything.
- name: Install build dependencies
run: |
for attempt in 1 2 3; do
dnf install -y --setopt=install_weak_deps=False \
rpm-build cmake gcc gcc-c++ make git curl \
pulseaudio-libs-devel argtable-devel libconfig-devel hidapi-devel \
libserialport-devel lua-devel libuv-devel libxdg-basedir-devel \
libxml2-devel procps-ng-devel gtk3-devel libcurl-devel \
mesa-libGLU-devel && exit 0
echo "dnf install failed (attempt $attempt), retrying in 15s..."
sleep 15
done
echo "dnf install failed after 3 attempts" >&2
exit 1
# submodules: recursive is new here. It didn't matter while %prep cloned
# upstream and ran `git submodule update` itself; now that the staged
# checkout is what gets built, an empty nappgui_src fails configure with
# "does not contain a CMakeLists.txt file". Missed by the local fedora:44
# rehearsal, which mounted a working tree that already had its submodules.
- uses: actions/checkout@v4
with:
submodules: recursive
- name: create rpmbuild dirs
run: mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
# The spec's %prep used to `git clone` upstream master, so the rpm's
# contents tracked master rather than the tag being built -- an rpm built
# from a tag would quietly ship master's code. Staged here and copied by
# %prep instead, so a package's contents are the commit it was built from.
- name: Stage the checked-out tree as the rpm source
run: |
rm -rf ~/rpmbuild/SOURCES/monocoque
cp -r "$GITHUB_WORKSPACE" ~/rpmbuild/SOURCES/monocoque
rm -rf ~/rpmbuild/SOURCES/monocoque/.github
# Uses the spec from the checked-out tree instead of curl'ing it from
# upstream's master branch, so a tag builds the spec that tag contains.
#
# The spec's `Version: 0.0.5` is stale and hand-maintained -- real tags are
# well past it, so released rpms have been installing as monocoque-0.0.5.
# Stamped from the tag here rather than edited in the spec, so the file
# stays a working default for local rpmbuild while CI releases carry the
# real version. Same digit guard as the deb job: rpm versions can't start
# with a non-digit, and scratch tags / branch builds would break it.
#
# NOTE (pre-existing, deliberately not fixed here): the spec's own %prep
# still does a fresh `git clone` of upstream master for the *sources*,
# so the rpm's contents track master rather than the tag being built.
# Worth fixing separately -- it needs the spec parameterised, which is a
# bigger change than getting these jobs running again.
- name: run spec file
shell: bash
env:
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
v=""
# RELEASE_TAG is set only when release-please calls this workflow;
# its tag exists but the run itself is on a branch, so GITHUB_REF_*
# would report master and fall through to 0.0.0.
v="${RELEASE_TAG#v}"
[ -z "$v" ] && [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}"
case "$v" in
[0-9]*) ;;
*) v="0.0.0" ;;
esac
echo "packaging as version $v"
sed -i "s/^Version:.*/Version: $v/" tools/distro/fedora/rpm/fedora.spec
rpmbuild -ba tools/distro/fedora/rpm/fedora.spec
# Glob rather than a hardcoded monocoque-0.0.5-1.x86_64.rpm: the filename
# tracks the spec's Version/Release, which is now stamped per-tag above.
- name: rename file
run: |
cp ~/rpmbuild/RPMS/x86_64/monocoque-*.rpm "$GITHUB_WORKSPACE/monocoque-${{ matrix.os }}.rpm"
echo "--- rpm identity as built ---"
rpm -qp --qf '%{NAME} %{VERSION}-%{RELEASE}\n' "$GITHUB_WORKSPACE/monocoque-${{ matrix.os }}.rpm"
- name: Release the Package
# Only a tag can publish a release; without this a workflow_dispatch run
# builds everything correctly and then reports failure on a step that
# cannot work, which makes a green build look broken.
if: github.ref_type == 'tag' || inputs.release_tag != ''
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ inputs.release_tag || github.ref_name }}
files: monocoque-${{ matrix.os }}.rpm
# release-please writes the release body from the changelog it just
# generated. Letting this action generate its own notes as well would
# overwrite them, so it only does so for a hand-pushed tag.
generate_release_notes: ${{ inputs.release_tag == '' }}
build-monocoque-appimage:
# An AppImage only runs where glibc is at least as new as the one it
# linked against, so it wants the OLDEST base that can build the code --
# building on current Ubuntu silently excludes older distros and defeats
# the point of shipping one.
#
# That floor is set by the compiler, not by choice: simapi's F1 2018
# headers use C23 enum-with-fixed-underlying-type (`enum TrackID :
# uint8_t`), which GCC only implements from 13 onwards. ubuntu-22.04
# ships GCC 11 and fails outright with `expected identifier or '(' before
# ':' token` -- confirmed by a real run, and no -std= value rescues it
# (verified separately: the construct is gated on compiler version, not
# on the standard flag). ubuntu-24.04 / GCC 13 is therefore the oldest
# usable base, giving a glibc 2.39 floor.
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
# librsvg2-common and libgtk-3-bin are for linuxdeploy's gtk plugin, not
# the build itself -- it needs an svg pixbuf loader and
# gtk-update-icon-cache to assemble a self-contained GTK runtime.
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential cmake pkg-config \
libgtk-3-dev libuv1-dev libargtable2-dev libserialport-dev \
libconfig-dev libhidapi-dev liblua5.4-dev libxdg-basedir-dev \
libxml2-dev libpulse-dev libproc2-dev libcurl4-openssl-dev \
libglu1-mesa-dev \
desktop-file-utils file patchelf wget \
librsvg2-common libgtk-3-bin
# -std=gnu2x for the C23 enum syntax above: GCC 13 implements it but
# still defaults to gnu17, unlike GCC 15+ (which the container-based
# deb/rpm legs get, and which accept it without the flag). gnu2x rather
# than c2x so GNU extensions stay on -- simapi also relies on asprintf.
- name: Build
run: |
cmake -B build -DBUILD_GUI=on -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_FLAGS=-std=gnu2x -S .
cmake --build build --config Release
# Uses the install() rules rather than hand-copying binaries the way the
# deb job does -- that's what they were added for.
- name: Populate AppDir
run: |
DESTDIR="$PWD/AppDir" cmake --install build --prefix /usr
# Icon basename must match the desktop file's `Icon=monocoque`.
# tools/monocoque.svg is the square copy shared with the Flatpak
# build; the published 100x50 artwork is rejected by icon tooling.
install -Dm644 tools/monocoque.svg \
AppDir/usr/share/icons/hicolor/scalable/apps/monocoque.svg
# APPIMAGE_EXTRACT_AND_RUN=1 because these tools are themselves
# AppImages and there is no FUSE on the runner to mount them with.
- name: Build AppImage
env:
APPIMAGE_EXTRACT_AND_RUN: 1
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
# An AppImage carries no package metadata, so its filename is the only
# place a version can live -- hence versioning the output name here
# rather than stamping a field as the deb/rpm jobs do. Same digit
# guard, so scratch tags and branch builds still produce a valid name.
v=""
# RELEASE_TAG is set only when release-please calls this workflow;
# its tag exists but the run itself is on a branch, so GITHUB_REF_*
# would report master and fall through to 0.0.0.
v="${RELEASE_TAG#v}"
[ -z "$v" ] && [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}"
case "$v" in
[0-9]*) ;;
*) v="0.0.0" ;;
esac
export OUTPUT="monocoque-${v}-x86_64.AppImage"
echo "packaging as $OUTPUT"
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/raw/master/linuxdeploy-plugin-gtk.sh
chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-gtk.sh
# Deploy dependencies first, without packaging, so there is a populated
# AppDir to take licences from -- an AppImage distributes every library
# it bundles, so their copyright files have to travel with it. A .deb
# needs none of this because its dependencies stay separate packages.
PATH="$PWD:$PATH" ./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--plugin gtk \
--desktop-file AppDir/usr/share/applications/monocoque.desktop \
--icon-file tools/monocoque.svg
tools/appimage-collect-licenses.sh AppDir
# monocoque itself and its vendored components are built here rather
# than installed from packages, so dpkg cannot account for them.
install -Dm644 LICENSE.rst AppDir/usr/share/doc/monocoque/LICENSE.rst
install -Dm644 tools/distro/debian/dpkg/copyright AppDir/usr/share/doc/monocoque/copyright
install -Dm644 src/monocoque/mgui/nappgui_src/LICENSE AppDir/usr/share/doc/nappgui/LICENSE
# Now package the AppDir, licences included.
PATH="$PWD:$PATH" ./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--output appimage
ls -la monocoque*.AppImage
echo "--- licence files in the image ---"
find AppDir/usr/share/doc -name copyright | wc -l
- name: Upload the AppImage as a build artifact
uses: actions/upload-artifact@v4
with:
name: monocoque-x86_64.AppImage
path: monocoque*.AppImage
- name: Release the Package
# Only a tag can publish a release; without this a workflow_dispatch run
# builds everything correctly and then reports failure on a step that
# cannot work, which makes a green build look broken.
if: github.ref_type == 'tag' || inputs.release_tag != ''
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ inputs.release_tag || github.ref_name }}
files: monocoque*.AppImage
# release-please writes the release body from the changelog it just
# generated. Letting this action generate its own notes as well would
# overwrite them, so it only does so for a hand-pushed tag.
generate_release_notes: ${{ inputs.release_tag == '' }}
build-monocoque-flatpak:
# Folded in from the standalone flatpak.yml so every artifact lands on
# one release. Kept as continue-on-error because it is the newest and
# least battle-tested leg: a Flatpak failure must never turn a release
# red or block the deb/rpm/AppImage artifacts, which are independent
# jobs and upload regardless.
runs-on: ubuntu-22.04
continue-on-error: true
permissions:
contents: write
steps:
# No submodules: recursive here -- the manifest fetches simapi and
# nappgui_src itself as explicit `type: git` sources (pinned commits,
# reproducible regardless of what's checked out on disk). Confirmed
# via a failed CI run: recursive submodule checkout populates those
# paths with real .git files first, which then collides with
# flatpak-builder's own git source trying to clone into the same spot
# ("cannot overwrite non-directory .git with directory").
- uses: actions/checkout@v4
# ppa:flatpak/stable, not Ubuntu's own package: 22.04's flatpak-builder
# predates freedesktop Sdk 25.08 dropping the legacy appstream-compose
# binary in favour of `appstreamcli compose`, and fails at the finish
# stage without that fallback.
- name: Install flatpak-builder + freedesktop runtime/SDK
run: |
sudo add-apt-repository -y ppa:flatpak/stable
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
sudo flatpak install -y --noninteractive flathub org.freedesktop.Platform//25.08 org.freedesktop.Sdk//25.08
- name: Build .flatpak
working-directory: flatpak
run: |
flatpak-builder --force-clean --repo=repo build-dir io.github.spacefreak18.monocoque.yml
flatpak build-bundle repo monocoque.flatpak io.github.spacefreak18.monocoque
- name: Upload .flatpak as a build artifact
uses: actions/upload-artifact@v4
with:
name: monocoque.flatpak
path: flatpak/monocoque.flatpak
- name: Release the Package
if: github.ref_type == 'tag' || inputs.release_tag != ''
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ inputs.release_tag || github.ref_name }}
files: flatpak/monocoque.flatpak
# release-please writes the release body from the changelog it just
# generated. Letting this action generate its own notes as well would
# overwrite them, so it only does so for a hand-pushed tag.
generate_release_notes: ${{ inputs.release_tag == '' }}
#arch-build:
# runs-on: ubuntu-latest
# container:
# image: archlinux
# options: --privileged
# steps:
# - uses: actions/checkout@v3
# - name: Set proper repository url in PKGBUILD
# run: sed -i "s|%URL%|${{ github.server_url }}/${{ github.repository }}|" $GITHUB_WORKSPACE/tools/distro/arch/git/PKGBUILD
# - run: echo "$GITHUB_WORKSPACE/.github/bin" >> "$GITHUB_PATH"
# - name: Build Arch Linux package
# uses: FFY00/build-arch-package@v1
# with:
# BUILD_SCRIPT: extra-x86_64-build
# PKGBUILD: $GITHUB_WORKSPACE/tools/distro/arch/git/PKGBUILD
# OUTDIR: $HOME/arch-packages
# - name: Release the Package
# uses: softprops/action-gh-release@v1
# with:
# files: $HOME/arch-packages/monocoque*.zst