Skip to content

Merge pull request #2053 from contour-terminal/build/libunicode-0.9.3 #8910

Merge pull request #2053 from contour-terminal/build/libunicode-0.9.3

Merge pull request #2053 from contour-terminal/build/libunicode-0.9.3 #8910

Workflow file for this run

name: Build
on:
merge_group:
push:
paths-ignore:
- 'docs/**'
- '.github/ISSUE_TEMPLATE/**'
- '.github/*.yml'
- 'LICENSE.txt'
- 'mkdocs.yml'
- '*.md'
- '*.sh'
branches:
- master
pull_request:
paths-ignore:
- 'docs/**'
- '.github/ISSUE_TEMPLATE/**'
- '.github/*.yml'
- 'LICENSE.txt'
- 'mkdocs.yml'
- '*.md'
- '*.sh'
branches:
- master
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
env:
CTEST_OUTPUT_ON_FAILURE: 1
# Turn the workflow lint's offline skip into a hard failure. `check-workflows.py` skips when it
# cannot obtain actionlint, so that a fresh offline clone still gets a green `ctest`; in CI that
# would mean a gate quietly passing because its download failed, which is worse than no gate.
# Set at workflow level so it covers every job here that runs `ctest` -- the Linux matrix and
# packaging jobs via `--target test`, and the Windows job directly.
CHECK_WORKFLOWS_REQUIRE_TOOL: 1
# Single source of truth for the LLVM/Clang toolchain version used across this workflow
# (clang-format, clang-tidy and the Clang build matrix). Bump this one value to upgrade.
LLVM_VERSION: "22"
# Sccache GitHub Actions cache backend - currently disabled due to service reliability
# See https://github.com/mozilla/sccache/blob/main/docs/GHA.md for documentation
SCCACHE_GHA_ENABLED: "false"
SCCACHE_GHA_VERSION: "20260106-1"
jobs:
# {{{ Common checks
check_common:
name: "Check CI scripts"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: "Checking for common errors in build, deployment, and CI."
run: ./scripts/check-common.sh
check_PR_TODOs:
name: "Check PR-TODOs"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: "Checking for open PR-related TODO items"
run: ./scripts/check-pr-todos.sh
check_clang_format:
name: "Check C++ style"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh "$LLVM_VERSION"
sudo apt-get install "clang-format-$LLVM_VERSION"
# The predicates must stay grouped: an ungrouped `-o` binds looser than any predicate added
# later (e.g. -print0), which would silently reduce the file set instead of failing.
# `*.h` is not dead weight: src/vtrasterizer/shared_defines.h keeps that extension because it
# is shared with GLSL, and it is still a file we format.
- name: "Clang-format"
run: find ./src/ \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) -print0 | xargs -0 "clang-format-$LLVM_VERSION" --Werror --dry-run
- name: "Check includes"
run: ./scripts/check-includes.sh
check_clang_tidy:
name: "Check clang-tidy"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: "ccache-ubuntu2404-clang-tidy"
max-size: 256M
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: "install dependencies"
run: sudo env QTVER=6 ./scripts/install-deps.sh
- name: "Post-fix embedded dependency permissions."
run: sudo find _deps/sources -exec chown $UID {} \;
# Ubuntu 24.04's distro Qt (6.4.2) lacks the Qt6::GuiPrivate CMake package the RHI renderer needs;
# install a modern Qt6 over it (see the build matrix job for the same rationale).
- name: "Install Qt (GuiPrivate-capable)"
uses: jurplel/install-qt-action@v4
with:
version: "6.11.*"
modules: qtmultimedia qt5compat qtshadertools qtspeech
- name: Install clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh ${LLVM_VERSION}
sudo apt-get install clang-tidy-${LLVM_VERSION}
# Catches stale/misspelled option keys and removed config directives in seconds,
# rather than letting them silently no-op through a full tidy build.
- name: "Verify clang-tidy configuration"
run: CLANG_TIDY=clang-tidy-${LLVM_VERSION} ./scripts/check-clang-tidy-config.sh
- name: "cmake"
run: |
cmake \
--preset clang-debug \
-B build \
-G Ninja \
-D CONTOUR_WAYLAND=OFF \
-D CMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \
-D ENABLE_TIDY=ON \
-D CLANG_TIDY_EXE=/usr/bin/clang-tidy-${LLVM_VERSION}
- name: "build"
run: cmake --build build
# Builds the GUI-less configuration, whose product is `contour daemon` -- a session host for a
# server or a container. Being unbuilt is exactly how this configuration rotted before; one job
# is what keeps a new GUI source from landing outside its CONTOUR_FRONTEND_GUI guard.
#
# Deliberately NOT installing a modern Qt over the distro one: relying on Ubuntu's Qt 6.4.2
# proves the headless build needs nothing beyond Qt6::Core -- no Qt6::GuiPrivate, no Qt Quick.
# Clang only, since this gates a build configuration and not a toolchain.
build_headless:
name: "Ubuntu Linux (no GUI frontend)"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: "ccache-ubuntu2404-headless"
max-size: 256M
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: "install dependencies"
run: sudo env QTVER=6 ./scripts/install-deps.sh
- name: "Post-fix embedded dependency permissions."
run: sudo find _deps/sources -exec chown $UID {} \;
- name: Install clang
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh ${LLVM_VERSION}
# CMAKE_DISABLE_FIND_PACKAGE_Qt6 makes every find_package(Qt6 ...) fail, so this configuration
# cannot merely happen to build against a Qt that is lying around -- it has to want none. The
# step above installed Qt, which is what makes the assertion worth anything: the point is not
# that Qt is absent from the runner, it is that the build never reaches for it. Issue #2042 is
# reported from a machine where Qt6 does not exist at all.
- name: "cmake"
run: |
cmake \
--preset clang-debug \
-B build \
-G Ninja \
-D CONTOUR_WAYLAND=OFF \
-D CONTOUR_FRONTEND_GUI=OFF \
-D CONTOUR_TESTING=ON \
-D CMAKE_DISABLE_FIND_PACKAGE_Qt6=ON \
-D CMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}"
- name: "build"
run: cmake --build build --target contour contour_test
# ...and that the linked result carries no Qt either, which the configure-time gate above cannot
# see: a transitive Qt6::Core from some other target would still land here.
- name: "verify the binary links no Qt"
run: |
set -eu
if ldd build/src/contour/contour | grep -i "libQt"; then
echo "::error::GUI-less build links Qt"
exit 1
fi
# contour_test is the unit suite for the layers this configuration is made of -- the
# configuration model, the command vocabulary and the CLI -- and it links no Qt, which is the
# only reason it can run here at all. Before it existed, this job built the binary and asserted
# nothing about its behaviour.
#
# contour_e2e_cli_verbs asserts the verb surface this configuration is supposed to expose --
# `daemon` present, `client` gone. contour_e2e_shell_integration asserts that `generate
# integration` still emits the scripts verbatim, which is the verb that used to read them out of
# a Qt resource. All three are ctest tests rather than steps of this job, so they also run in the
# ordinary GUI builds, where they assert the other half.
- name: "tests"
run: ctest --test-dir build -R "contour_test|contour_e2e_cli_verbs|contour_e2e_shell_integration" --output-on-failure
# Building is not the same as being installable: this configuration reached issue #1780 with a
# `contour` binary that compiled and an install target that errored out on a missing one. The
# install rules are a per-configuration decision, so they need a per-configuration run.
# --component contour, because the build step above builds the `contour` target alone: a full
# install would also want vtbackend's bench-headless, which was never compiled. The component is
# exactly the set of rules this job is here to exercise.
- name: "install"
run: |
set -eu
cmake --install build --prefix "$RUNNER_TEMP/contour-headless" --component contour
test -x "$RUNNER_TEMP/contour-headless/bin/contour"
test -f "$RUNNER_TEMP/contour-headless/share/contour/shell-integration/shell-integration.zsh"
# The macOS half of the same guard. A .app bundle, macdeployqt and a signed .dmg all serve a
# window; a GUI-less build has none, so it must install a plain bin/contour like any other Unix
# CLI tool. The Linux job above cannot assert that -- it never enters CMake's APPLE branch.
#
# Configure-only on purpose: what this checks is which rules the configuration GENERATES, and
# every way it can go wrong (a REQUIRED find_package for a GUI module, a macdeployqt lookup, an
# install rule that stages a bundle) is decided at configure time. Compiling the same headless
# sources a second time would only repeat the job above at several times the cost.
build_headless_macos:
name: "macOS (no GUI frontend)"
runs-on: macos-15
steps:
- uses: actions/checkout@v4
# CONTOUR_INSTALL_BREW_QT: install-deps.sh leaves Qt out on macOS because Homebrew's split
# per-module prefixes break macdeployqt for release bundles. This build makes no bundle, so the
# Homebrew Qt is exactly the "dev build that is never packaged" case that script documents.
# It is installed here even though this configuration must not use it, for the same reason as in
# the Linux job: a Qt that is present and still untouched is the assertion worth making.
- name: "install dependencies"
run: env CONTOUR_INSTALL_BREW_QT=ON QTVER=6 ./scripts/install-deps.sh
- name: "cmake"
run: |
cmake \
--preset appleclang-debug \
-B build \
-D CONTOUR_FRONTEND_GUI=OFF \
-D CONTOUR_TESTING=OFF \
-D CMAKE_DISABLE_FIND_PACKAGE_Qt6=ON
- name: "verify no app-bundle machinery was generated"
run: |
set -eu
for pattern in macdeployqt contour.app DragNDrop MacOSNotarizeApp MacOSSignDmg; do
if grep -qF "$pattern" build/src/contour/cmake_install.cmake build/CPackConfig.cmake; then
echo "::error::GUI-less macOS build still generates '$pattern' rules"
exit 1
fi
done
# ...and that it still installs SOMETHING: the rules must have moved to the CLI branch, not
# gone missing with the bundle they used to live next to.
if ! grep -q 'CMAKE_INSTALL_PREFIX}/bin" TYPE EXECUTABLE' build/src/contour/cmake_install.cmake; then
echo "::error::GUI-less macOS build installs no executable into bin/"
exit 1
fi
check_links:
name: "Check links"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Run linksafe
uses: Yaraslaut/linksafe@main
with: # comma separated lists
# use relative paths, if no dirs specified root dir is scanned
ignored_dirs: "_deps"
ignored_files: "./scripts/install-deps.sh,
./scripts/install-deps.ps1"
ignored_links: "https://www.contributor-covenant.org/faq,
https://www.contributor-covenant.org,
https://flatpak.org/getting.html,
https://codeberg.org/dnkl/foot,
https://codecov.io/gh/contour-terminal/contour,
https://msdn.microsoft.com/zh-cn/library/f4k12ae8,
https://msdn.microsoft.com/en-us/library/fzt08www,
"
editorconfig:
name: "Check editorconfig"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: editorconfig-checker/action-editorconfig-checker@main
- run: editorconfig-checker
# }}}
# {{{ StaticBuild
staticBuild:
if: github.ref == 'refs/heads/master' || github.head_ref == 'release'
name: "Static build"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
with:
version: latest
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: Build release
shell: bash
run: |
docker buildx build \
--tag static_contour \
--progress=plain \
-f .github/static/DockerUbuntu \
--load \
.
docker create --name contour_static static_contour
docker cp contour_static:/contour/build/src/contour/contour contour
docker container rm contour_static
- name: "Uploading static executable"
uses: actions/upload-artifact@v4
with:
name: "contour"
path: "contour"
if-no-files-found: error
retention-days: 1
# }}}
# {{{ Fedora
# fedora:
# strategy:
# fail-fast: false
# matrix:
# os_version: [40]
# arch:
# [
# "linux/amd64 x86_64"
# ]
# name: "Fedora ${{ matrix.os_version }} ${{ matrix.arch }}"
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - name: Set up Docker Buildx
# id: buildx
# uses: docker/setup-buildx-action@v2
# with:
# version: latest
# - name: Read matrix info
# id: tags
# shell: bash
# run: |
# arch="${{ matrix.arch }}"
# echo "PLATFORM=${arch%% *}" >> "$GITHUB_OUTPUT"
# echo "ARCH=${arch##* }" >> "$GITHUB_OUTPUT"
# - name: "update APT database"
# run: sudo apt -q update
# - name: Installing xmllint for ci-set-vars
# run: sudo apt -qy install libxml2-utils
# - name: set environment variables
# id: set_vars
# run: ./scripts/ci-set-vars.sh
# env:
# REPOSITORY: ${{ github.event.repository.name }}
# - name: Fetch and unpack embeds
# run: ./scripts/install-deps.sh
# env:
# PREPARE_ONLY_EMBEDS: 'ON'
# SYSDEP_ASSUME_YES: 'ON'
# OS_OVERRIDE: 'fedora'
# - name: "Post-fix embedded dependency permissions."
# run: sudo find _deps/sources -exec chown $UID {} \;
# - name: prepare distfile
# run: |
# set -x
# PKGNAME="contour-${{ steps.set_vars.outputs.VERSION }}"
# DISTDIR="/tmp/dist/${PKGNAME}"
# mkdir -p ${DISTDIR}
# cp -rvp . ${DISTDIR}
# tar czpf ${PKGNAME}.tar.gz -C "/tmp/dist" .
# - name: disable pedantic compiler on certain OS versions
# # Generally disable -Werror.
# #if: ${{ matrix.os_version == 38 || matrix.os_version == 39 }}
# run: |
# set -ex
# #sed -i -e "s/PEDANTIC_COMPILER=ON/PEDANTIC_COMPILER=OFF/" .github/fedora/contour.spec
# sed -i -e "s/PEDANTIC_COMPILER_WERROR=ON/PEDANTIC_COMPILER_WERROR=OFF/" .github/fedora/contour.spec
# - name: Build ${{ matrix.arch }} release
# shell: bash
# run: |
# set -x
# ARCH="${{ steps.tags.outputs.ARCH }}"
# VERSION="${{ steps.set_vars.outputs.VERSION }}"
# OS_VERSION="${{ matrix.os_version }}"
# sed -i -e "s/fedora:35/fedora:${{ matrix.os_version }}/" .github/fedora/Dockerfile
# docker buildx build --platform ${{ steps.tags.outputs.PLATFORM }} \
# --tag contour:${ARCH} \
# --build-arg VERSION=${VERSION} \
# --build-arg VERSION_STRING=${VERSION} \
# -f .github/fedora/Dockerfile \
# --load \
# .
# docker create --name contour-${ARCH} contour:${ARCH}
# docker cp contour-${ARCH}:/app/rpmbuild/RPMS/${ARCH}/contour-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm \
# contour-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm
# docker cp contour-${ARCH}:/app/rpmbuild/RPMS/${ARCH}/contour-debuginfo-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm \
# contour-debuginfo-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm
# docker cp contour-${ARCH}:/app/rpmbuild/RPMS/${ARCH}/contour-debugsource-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm \
# contour-debugsource-${VERSION}-1.fc${OS_VERSION}.${ARCH}.rpm
# docker container rm contour-${{ steps.tags.outputs.ARCH }}
# echo "pwd: `pwd`" && ls -hla
# - name: "Uploading Fedora RPM package"
# uses: actions/upload-artifact@v4
# with:
# name: "contour-${{ steps.set_vars.outputs.VERSION }}-1.fc${{ matrix.os_version }}.${{ steps.tags.outputs.ARCH }}.rpm"
# path: |
# contour-${{ steps.set_vars.outputs.VERSION }}-1.fc${{ matrix.os_version }}.${{ steps.tags.outputs.ARCH }}.rpm
# contour-debuginfo-${{ steps.set_vars.outputs.VERSION }}-1.fc${{ matrix.os_version }}.${{ steps.tags.outputs.ARCH }}.rpm
# contour-debugsource-${{ steps.set_vars.outputs.VERSION }}-1.fc${{ matrix.os_version }}.${{ steps.tags.outputs.ARCH }}.rpm
# if-no-files-found: error
# retention-days: 7
# }}}
# {{{ FreeBSD
freebsd:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
name: FreeBSD 13
# env:
# MYTOKEN: "value1"
# MYTOKEN2: "value2"
steps:
- uses: actions/checkout@v4
- name: Test in FreeBSD
id: test
uses: vmactions/freebsd-vm@v1
timeout-minutes: 60
with:
envs: 'MYTOKEN MYTOKEN2'
usesh: true
copyback: false
prepare: pkg install -y wget
run: |
set -ex
pwd
ls -lah
whoami
env
freebsd-version
SYSDEP_ASSUME_YES=ON ./scripts/install-deps.sh
cmake -S . -B build -DCONTOUR_TESTING=ON -DLIBUNICODE_USE_STD_SIMD=OFF
cmake --build build/ -j2
# ctest, not a hand-written list: that list predated src/coro, src/net, src/vthost and
# src/vtworkspace, so those suites were built in this VM and thrown away -- and a BSD
# is exactly where the daemon's socket code would diverge. The label exclusions keep
# the VM's budget: `e2e` boots the real app repeatedly, and `lint` re-runs gates the
# Linux jobs already own.
#
# contour_gui_test is excluded BY NAME, not because it cannot work here -- it selects
# the offscreen QPA itself when there is no DISPLAY -- but because it has never once
# run on this VM, and this job gates master rather than a PR. Widening coverage to the
# daemon suites should not also stake master on an untried Qt run. Drop the exclusion
# once someone has watched it pass here.
ctest --test-dir build/ --output-on-failure -LE "e2e|lint" -E "contour_gui_test"
rm -rf _deps build
# }}}
# {{{ OpenBSD
openbsd:
if: false #github.ref == 'refs/heads/master' || github.head_ref == 'release'
runs-on: ubuntu-latest
name: OpenBSD 7.5
# env:
# MYTOKEN: "value1"
# MYTOKEN2: "value2"
steps:
- uses: actions/checkout@v4
- name: Test in OpenBSD
id: test
uses: vmactions/openbsd-vm@v1
timeout-minutes: 60
with:
envs: 'MYTOKEN MYTOKEN2'
usesh: true
copyback: false
prepare: pkg_add -DI wget
run: |
set -ex
pwd
ls -lah
whoami
env
./scripts/install-deps.sh
mkdir build
cmake -S . -B build -DCMAKE_CXX_COMPILER=clang++ -D CMAKE_CXX_FLAGS=-I/home/runner/work/contour/contour/_deps/sources/yaml-cpp-0.8.0/include -DCONTOUR_TESTING=ON -DLIBUNICODE_USE_STD_SIMD=OFF
cmake --build build/ -j2
./build/src/crispy/crispy_test
./build/src/vtparser/vtparser_test
./build/src/vtbackend/vtbackend_test
./build/src/vtrasterizer/vtrasterizer_test
rm -rf _deps build
# }}}
# {{{ macOS
macOSArm:
# Pinned, not macos-latest, so the toolchain and SDK behind a release are reproducible
# and a GitHub image rotation cannot silently change what ships.
#
# The minimum macOS of the .dmg no longer depends on this: the libraries Contour links
# are built from source by vcpkg against CMAKE_OSX_DEPLOYMENT_TARGET (13.3), not taken
# from the runner's Homebrew bottles. CONTOUR_MACOS_MIN_SUPPORTED enforces that.
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: set variables
id: set_vars
run: |
./scripts/ci-set-vars.sh
# Signing is gated on whether the secrets are actually reachable, not on the
# branch name. GitHub withholds secrets from fork pull requests, which is the
# real precondition; a branch-name test only approximates it, and approximated
# it badly in both directions -- fork PRs still asked codesign for a Developer ID
# that was in no keychain (breaking every PR run of this job), while a release
# branch's signing path went completely unexercised until it was already on
# master. Signing has to be proven before the merge, not after it.
if [[ -n "$HAS_SIGNING_SECRETS" ]]; then
echo "CODE_SIGN_CERTIFICATE_ID=Developer ID Application: Christian Parpart (6T525MU9UR)" >> "$GITHUB_OUTPUT"
echo "NOTARIZE=ON" >> "$GITHUB_OUTPUT"
else
echo "CODE_SIGN_CERTIFICATE_ID=-" >> "$GITHUB_OUTPUT"
echo "NOTARIZE=OFF" >> "$GITHUB_OUTPUT"
fi
# Stapled together with the image, never separately. A ticket on the .dmg alone
# covers the download, but the app dragged out of it carries none -- so its first
# launch depends on Gatekeeper reaching Apple, and a user who is offline, behind a
# captive portal or on a restricted network gets exactly the dialog this pipeline
# exists to prevent: "Apple could not verify ... is free of malware". Stapling the
# app makes the installed copy self-sufficient. It costs a second Apple round trip
# of a minute or two, which is not a price worth haggling over against that.
if [[ -n "$HAS_SIGNING_SECRETS" ]]; then
echo "STAPLE_APP=ON" >> "$GITHUB_OUTPUT"
else
echo "STAPLE_APP=OFF" >> "$GITHUB_OUTPUT"
fi
env:
REPOSITORY: ${{ github.event.repository.name }}
# Via the environment, not interpolated into the script: a branch name is
# attacker-controlled on a fork's pull request.
GH_REF: ${{ github.ref }}
GH_HEAD_REF: ${{ github.head_ref }}
# Empty on fork pull requests, where GitHub withholds secrets. Only the
# emptiness is observable here -- the value never reaches the log.
HAS_SIGNING_SECRETS: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
- name: Install the Apple signing certificate
if: steps.set_vars.outputs.NOTARIZE == 'ON'
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# @see https://docs.github.com/en/actions/deployment/deploying-xcode-applications/installing-an-apple-certificate-on-macos-runners-for-xcode-development
set -eu
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# -T grants codesign access to the imported key, and set-key-partition-list
# records that grant in the key's ACL. Without both, codesign blocks on a GUI
# confirmation prompt that never arrives on a runner, and the job hangs until
# its timeout rather than reporting anything useful.
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 \
-k $KEYCHAIN_PATH -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
security find-identity -v -p codesigning $KEYCHAIN_PATH
- name: Store the notarization credentials
if: steps.set_vars.outputs.NOTARIZE == 'ON'
env:
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }}
run: |
# An App Store Connect API key rather than an Apple ID and app-specific
# password: it carries no personal account, can be scoped to the Developer
# role, and is revocable on its own.
set -eu
KEY_PATH=$RUNNER_TEMP/notary-key.p8
echo -n "$APPLE_API_KEY_P8" | base64 --decode -o $KEY_PATH
xcrun notarytool store-credentials contour-notary \
--key "$KEY_PATH" \
--key-id "$APPLE_API_KEY_ID" \
--issuer "$APPLE_API_ISSUER_ID"
rm -f "$KEY_PATH"
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ccache-macosArm-r1
max-size: 256M
- name: Install Qt
# The official Qt binaries, as on Linux and Windows -- not Homebrew's. Homebrew
# splits Qt across ~40 per-module prefixes and macdeployqt only follows one of
# them, so a brew-Qt bundle ships missing frameworks and unresolvable @rpath
# references. That is the defect this job used to hand to users as a .dmg.
uses: jurplel/install-qt-action@v4
with:
version: 6.11.*
modules: qtmultimedia qt5compat qtshadertools qtspeech
cache: true
- name: "Install dependencies"
# Sometimes, brew thinks it needs to install from source rather than binary,
# so cap the runtime rather than burn CI credits on a source build.
#
# The libraries Contour links are NOT taken from Homebrew here -- see the vcpkg
# steps below for why. install-deps.sh also brings the autotools that some vcpkg
# ports need on the build host.
timeout-minutes: 15
run: |
set -ex
#brew update
# Trying to work around some Github CI issues causing `brew install` to fail
brew uninstall aws-sam-cli azure-cli
./scripts/install-deps.sh
- name: "Cache vcpkg"
uses: actions/cache@v4
with:
path: |
~/.cache/vcpkg/archives
key: vcpkg-macos-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'cmake/vcpkg-triplets/arm64-osx-contour.cmake') }}
restore-keys: vcpkg-macos-
- name: "vcpkg: clone and bootstrap"
# The libraries Contour links (openssl, libssh2, yaml-cpp, freetype, harfbuzz,
# cairo) are built from source against CMAKE_OSX_DEPLOYMENT_TARGET rather than
# taken from Homebrew. Homebrew ships one prebuilt bottle per macOS release and
# installs the one matching the runner, which made the .dmg's minimum macOS equal
# to the runner image's -- an invisible compatibility cliff that moved whenever
# GitHub rotated the image. vcpkg puts that floor under our control; the triplet at
# cmake/vcpkg-triplets/arm64-osx-contour.cmake is where it is set.
run: |
set -ex
git clone --depth 1 https://github.com/microsoft/vcpkg.git "$RUNNER_TEMP/vcpkg"
"$RUNNER_TEMP/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
echo "VCPKG_ROOT=$RUNNER_TEMP/vcpkg" >> "$GITHUB_ENV"
- name: "install tmux (oracle for the tmux-interop tests)"
# A CI-only tool, so it is here rather than in install-deps.sh, which lists what Contour
# needs to BUILD. Without it the tmux oracles self-skip, and switching this job to
# ctest (below) would otherwise have reached them only to watch them do nothing -- macOS
# is where the BSD socket and Apple-libc++ differences in that code path would show up.
#
# Built from source at the same pinned 3.7b as the Linux matrix, rather than taken from the
# formula. Both platforms then measure against the exact protocol version the control-mode
# server claims to speak (docs/internals/vthost.md), and neither can start or stop running
# the imsg oracle because a formula moved.
run: |
set -ex
# tmux's configure hard-errors without libevent; ncurses comes with the SDK on macOS.
# PKG_CONFIG_PATH is stated rather than assumed: the formula is not keg-only today, so its
# .pc file is normally on the default search path anyway -- but naming the prefix keeps
# this working if that ever changes, and costs nothing while it has not.
brew install libevent
export PKG_CONFIG_PATH="$(brew --prefix libevent)/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
curl -sSL -o "$RUNNER_TEMP/tmux.tar.gz" \
https://github.com/tmux/tmux/releases/download/3.7b/tmux-3.7b.tar.gz
tar xzf "$RUNNER_TEMP/tmux.tar.gz" -C "$RUNNER_TEMP"
# The release tarball ships a generated configure, so no autotools are needed here.
# --disable-utf8proc is required, not merely a choice: 3.7b's configure hard-errors
# ("must give --enable-utf8proc or --disable-utf8proc") rather than defaulting, and this
# oracle only has to speak the control-mode protocol -- how tmux measures east-Asian
# width does not enter into it. Stated on both platforms so they build the same binary.
cd "$RUNNER_TEMP/tmux-3.7b" \
&& ./configure --disable-utf8proc \
&& make -j"$(sysctl -n hw.ncpu)" \
&& sudo make install
tmux -V
- name: "Generate build files"
run: |
# The `macos-package` preset IS the definition of a shippable macOS build --
# Qt prefix, deployment target, warning policy, what lands in the bundle. This
# job overrides only the two things a preset cannot know: which identity is
# available in this run's keychain, and whether notarization secrets exist.
# Everything else must come from the preset, or the .dmg users download would
# again be built by a configuration nothing else exercises.
#
# QT_ROOT_DIR is exported by install-qt-action and is what the preset's
# CMAKE_PREFIX_PATH reads.
# The preset carries CONTOUR_MACOS_MIN_SUPPORTED=13.3, and it is enforced: if the
# bundled dylibs demand anything newer, the package step fails rather than
# shipping a .dmg that cannot launch. Configuring also triggers the vcpkg
# manifest install, which builds those dylibs against that same target.
cmake --preset macos-package \
-DCODE_SIGN_CERTIFICATE_ID="${{ steps.set_vars.outputs.CODE_SIGN_CERTIFICATE_ID }}" \
-DCONTOUR_MACOS_NOTARIZE="${{ steps.set_vars.outputs.NOTARIZE }}" \
-DCONTOUR_MACOS_STAPLE_APP="${{ steps.set_vars.outputs.STAPLE_APP }}"
- name: "Build"
run: cmake --build --preset macos-package
- name: "tests"
# ctest, not a hand-written list of binaries: the previous four-line list predated
# src/coro, src/net, src/vthost and src/vtworkspace, so those suites were built here and
# never run — macOS is exactly where the Apple-libc++ and BSD-socket differences bite.
# Registering a new test target must not require editing this file.
#
# 15 rather than 10: with tmux installed the four oracles now actually run instead of
# skipping, and each drives a real tmux server over a pty.
timeout-minutes: 15
run: ctest --preset macos-package
- name: "verify the tmux oracles actually ran"
# @see the identical assertion on the Linux matrix for why a green suite is not enough:
# a Catch2 SKIP exits 0, so "no tmux, nothing ran" is indistinguishable from success.
# Nothing is excluded, for parity with Linux: this job pins the same tmux 3.7b, so the imsg
# oracle runs here too rather than being carved out by name. Pinning is what makes that safe
# -- the version is stated in the workflow, not inherited from whatever a formula moved to.
run: |
set -o pipefail
binary=out/macos-package/src/vthost/vthost_test
output=$("$binary" '[oracle]' 2>&1) || { printf '%s\n' "$output"; exit 1; }
printf '%s\n' "$output"
if printf '%s' "$output" | grep -q 'skipped'; then
echo "::error::$binary reported SKIPPED; the tmux interop tests are not being exercised on macOS"
exit 1
fi
- name: "Create Package(s)"
# cpack deploys, prunes unloadable plugins, verifies the bundle is self-contained,
# signs it inside-out, notarizes and staples both the app and the image. Each of
# those steps is fatal on failure now, so a broken bundle stops here instead of
# being uploaded. Notarization waits on Apple, hence the generous timeout.
timeout-minutes: 30
run: |
set -ex
echo killing...; sudo pkill -9 XProtect >/dev/null || true; # see https://github.com/actions/runner-images/issues/7522
echo waiting...; while pgrep XProtect; do sleep 3; done;
cpack --preset macos-package --verbose
BASENAME="contour-${{ steps.set_vars.outputs.VERSION_STRING }}-macOS-arm"
mv -vf "out/macos-package/Contour-${{ steps.set_vars.outputs.VERSION_STRING }}-Darwin.dmg" "${BASENAME}.dmg"
echo "DMG=${BASENAME}.dmg" >> "$GITHUB_ENV"
- name: "verify the DMG is something a user can actually open"
# The assertion that was missing all along: a green package step used to mean only
# that hdiutil succeeded.
#
# Scoped to what cpack could not already check. The image's own signature, its
# staple and `spctl --type install` are asserted fatally inside the packaging run
# (macos-bundle.py sign/notarize and cmake/MacOSSignDmg.cmake), and the .dmg here
# is the byte-identical file cpack produced -- only `mv`'d. What is genuinely new
# is the app *as mounted from the image*, a different object from the staged
# bundle that was signed.
run: |
set -ex
MOUNT=$(mktemp -d)
hdiutil attach -nobrowse -readonly -mountpoint "$MOUNT" "$DMG"
trap 'hdiutil detach "$MOUNT" || true' EXIT
codesign --verify --deep --strict --verbose=4 "$MOUNT/contour.app"
# The image ships the app and nothing else -- plus the /Applications symlink the
# DragNDrop generator adds for drag-installing. CONTOUR_INSTALL_TOOLS=OFF in the
# preset is what keeps bench-headless out of Contents/MacOS; assert it, because
# the day someone flips that option back on, the only visible symptom would be a
# developer benchmark tool shipping inside a user-facing release.
test "$(ls "$MOUNT" | sort | tr '\n' ' ')" = "Applications contour.app "
test "$(ls "$MOUNT/contour.app/Contents/MacOS")" = "contour"
if [[ "${{ steps.set_vars.outputs.NOTARIZE }}" != 'ON' ]]; then
echo "::notice::unsigned build (no secrets); skipping Gatekeeper assertions"
exit 0
fi
# Reproduce what the user actually does, because that is where the
# "Apple could not verify ... is free of malware" dialog comes from: they drag
# the app OUT of the image and launch the copy. A ticket stapled only to the
# .dmg does not travel with that copy, leaving first launch dependent on
# Gatekeeper reaching Apple -- which fails offline, behind a captive portal, or
# on a restricted network. So the checks below run against the copy, not against
# the app sitting on the mounted image.
INSTALLED=$(mktemp -d)/contour.app
ditto "$MOUNT/contour.app" "$INSTALLED"
# A downloaded image carries this; the copy must clear Gatekeeper with it set.
xattr -w com.apple.quarantine "0081;00000000;Safari;" "$INSTALLED"
# The ticket is embedded in the copy, so first launch needs no network at all.
xcrun stapler validate "$INSTALLED"
# The exact decision Gatekeeper makes when that copy is first launched.
spctl --assess --verbose=4 --type exec "$INSTALLED"
# And the same for the image a browser just downloaded.
xcrun stapler validate "$DMG"
spctl --assess --verbose=4 --type install "$DMG"
- name: upload to artifact store (DMG)
uses: actions/upload-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-macOS-arm.dmg"
path: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-macOS-arm.dmg"
retention-days: 7
# }}}
# {{{ Windows
windows:
name: "Windows"
runs-on: windows-latest
permissions:
contents: read
packages: write
env:
CMAKE_PRESET: "msvc-release"
VCPKG_BINARY_SOURCES: "clear;nuget,GitHub,readwrite"
steps:
- uses: actions/checkout@v4
- name: Run sccache-cache
if: env.SCCACHE_GHA_ENABLED == 'true'
uses: mozilla-actions/sccache-action@v0.0.3
continue-on-error: true
id: sccache
- name: "Verify sccache is operational"
if: env.SCCACHE_GHA_ENABLED == 'true'
id: verify_sccache
shell: pwsh
run: |
# If GHA cache is disabled, force local disk cache
# Otherwise, allow the sccache-action's default configuration
if ($env:SCCACHE_GHA_ENABLED -eq "false") {
Write-Host "GHA cache disabled, forcing local disk cache"
echo "SCCACHE_GHA_ENABLED=" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "SCCACHE_DIR=$env:LOCALAPPDATA\Mozilla\sccache" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$env:SCCACHE_GHA_ENABLED = ""
$env:SCCACHE_DIR = "$env:LOCALAPPDATA\Mozilla\sccache"
}
# Quick test to ensure sccache is working
sccache --show-stats
if ($LASTEXITCODE -eq 0) {
Write-Host "Sccache is operational"
echo "sccache_enabled=true" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
} else {
Write-Host "::warning::Sccache failed, disabling compile caching"
echo "sccache_enabled=false" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
echo "CMAKE_C_COMPILER_LAUNCHER=" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "CMAKE_CXX_COMPILER_LAUNCHER=" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
}
- name: setup environment
shell: powershell
id: set_vars
run: .\scripts\ci-set-vars.ps1
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: "Fetch embedded dependencies"
shell: powershell
run: |
./scripts/install-deps.ps1 --skip-vcpkg
type ./_deps/sources/CMakeLists.txt
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
# 6.10, not the 6.11 the other platforms pin: Qt restructured its Windows package
# repository at 6.11 into per-architecture subdirectories
# (qt6_6111/qt6_6111_msvc2022_64/Updates.xml) where every release up to 6.10 kept a flat
# one (qt6_6103/Updates.xml). aqtinstall still requests the flat path, so a 6.11 install
# here dies with "Failed to locate XML data for Qt version '6.11.1'". 3.3.0 is its latest
# release, so there is nothing newer to move to; Linux and macOS are unaffected because
# their repositories stayed flat.
#
# 6.10 carries all four modules below, so this costs no feature -- only the configure-time
# notice that the detected Qt is older than the version Contour is tested against. Raise
# this to "6.11.*" once aqtinstall learns the new layout.
version: "6.10.*"
#version: "5.15.*"
modules: qtmultimedia qt5compat qtshadertools qtspeech
- name: "Setup NuGet for vcpkg caching"
shell: bash
run: |
dotnet nuget add source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
--name GitHub \
--username "${{ github.repository_owner }}" \
--password "${{ secrets.GITHUB_TOKEN }}" \
--store-password-in-clear-text
- name: "Cache vcpkg"
uses: actions/cache@v4
id: vcpkg-cache
with:
path: ${{ runner.workspace }}/vcpkg
key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json') }}
restore-keys: |
vcpkg-${{ runner.os }}-
- name: "Upgrade NuGet"
run: |
choco upgrade nuget.commandline -y
- name: "vcpkg: Clone and bootstrap"
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: |
git clone --depth 1 https://github.com/microsoft/vcpkg.git ${{ runner.workspace }}\vcpkg
${{ runner.workspace }}\vcpkg\bootstrap-vcpkg.bat
${{ runner.workspace }}\vcpkg\vcpkg.exe fetch nuget
env:
VCPKG_BINARY_SOURCES: "clear;nuget,GitHub,readwrite"
- name: "vcpkg: Install dependencies"
shell: pwsh
run: |
${{ runner.workspace }}\vcpkg\vcpkg.exe install --triplet x64-windows
env:
VCPKG_ROOT: "${{ runner.workspace }}/vcpkg"
VCPKG_BINARY_SOURCES: "clear;nuget,GitHub,readwrite"
- name: "Install cmake"
uses: lukka/get-cmake@v3.29.0
- name: "Check cmake version"
run: cmake --version
- uses: ilammy/msvc-dev-cmd@v1
- name: "Generate build files"
shell: pwsh
run: |
cmake --preset ${{ env.CMAKE_PRESET }}
env:
VCPKG_ROOT: "${{ runner.workspace }}/vcpkg"
- name: "Build"
shell: pwsh
run: |
cmake --build --preset ${{ env.CMAKE_PRESET }}
- name: "Show sccache statistics"
if: steps.verify_sccache.outputs.sccache_enabled == 'true'
shell: pwsh
run: |
sccache --show-stats
- name: "Test"
shell: pwsh
run: |
ctest --preset ${{ env.CMAKE_PRESET }} --build-config RelWithDebInfo
- name: "verify the AF_UNIX tests actually ran"
# Windows is the ONLY validation the daemon's Windows transport gets -- nobody develops it
# on a Windows box. The AF_UNIX cases self-skip where the platform has no AF_UNIX, and a
# Catch2 SKIP exits 0, so a green suite cannot distinguish "the transport works" from "it
# was never exercised". `windows-latest` is far past the 10 1803 that introduced AF_UNIX,
# so a skip here is a regression, not a platform fact. Same assertion the Linux matrix
# makes about the tmux oracles.
#
# On WIN32 the top-level CMakeLists redirects CMAKE_RUNTIME_OUTPUT_DIRECTORY to
# <binaryDir>/bin, so the executables do NOT sit beside their source directory the way they
# do on Linux. The preset's generator is Ninja (single-config), so there is no
# per-configuration subdirectory under it either.
shell: bash
run: |
set -o pipefail
for binary in \
"out/build/${CMAKE_PRESET}/bin/net_test.exe" \
"out/build/${CMAKE_PRESET}/bin/vthost_test.exe"; do
# A missing binary must not read as "nothing skipped": that is the very failure mode
# this step exists to catch, one directory layout further out.
[ -x "$binary" ] || { echo "::error::$binary not found; the assertion below would pass vacuously"; exit 1; }
echo "--- $binary [afunix]"
output=$("$binary" '[afunix]' 2>&1) || { printf '%s\n' "$output"; exit 1; }
printf '%s\n' "$output"
if printf '%s' "$output" | grep -q 'skipped'; then
echo "::error::$binary [afunix] reported SKIPPED; the daemon's AF_UNIX transport is not being exercised on Windows"
exit 1
fi
done
- name: "Install Wix Toolset"
run: |
# Use this to list all available versions
# dotnet tool search --global wix --detail
# or at URL: https://www.nuget.org/packages/wix#versions-body-tab
dotnet tool install --global wix --version 4.0.6
echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
- name: "Create Package(s)"
shell: powershell
run: |
cpack --preset ${{ env.CMAKE_PRESET }} -V
if ($LASTEXITCODE -ne 0) {
echo "CPack failed. Dumping wix.log..."
type "out/build/${{ env.CMAKE_PRESET }}/_CPack_Packages/win64/WIX/wix.log"
exit $LASTEXITCODE
}
- name: "Rename package(s)"
run: |
mv "out\build\${{ env.CMAKE_PRESET }}\Contour-${{ steps.set_vars.outputs.version }}-win64.msi" "contour-${{ steps.set_vars.outputs.version }}-win64.msi"
mv "out\build\${{ env.CMAKE_PRESET }}\Contour-${{ steps.set_vars.outputs.version }}-win64.zip" "contour-${{ steps.set_vars.outputs.version }}-win64.zip"
- name: "Uploading MSI to artifact store"
uses: actions/upload-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.version }}-win64.msi"
path: "contour-${{ steps.set_vars.outputs.version }}-win64.msi"
if-no-files-found: error
retention-days: 7
- name: "Uploading ZIP to artifact store"
uses: actions/upload-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.version }}-win64.zip"
path: "contour-${{ steps.set_vars.outputs.version }}-win64.zip"
if-no-files-found: error
retention-days: 7
# }}}
# {{{ Ubuntu 24.04 CC matrix
ubuntu_2404_cc_matrix:
strategy:
fail-fast: false
matrix:
cxx: [23]
build_type: ["RelWithDebInfo"]
compiler:
[
"GCC 14",
"Clang 22",
]
qt_version: [6]
runner:
[
"ubuntu-24.04",
"ubuntu-24.04-arm",
]
name: "${{ matrix.runner }} (${{ matrix.compiler }}, C++${{ matrix.cxx }}, Qt${{ matrix.qt_version }})"
runs-on: ${{ matrix.runner }}
outputs:
id: "${{ matrix.runner }} ${{ matrix.compiler }} (C++${{ matrix.cxx }}, ${{ matrix.build_type }}, ${{ matrix.qt_version }})"
steps:
- uses: actions/checkout@v4
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: "ccache-{{ matrix.runner }}-${{ matrix.compiler }}-${{ matrix.cxx }}-${{ matrix.build_type }}-${{ matrix.qt_version }}"
max-size: 256M
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: "Set up output var: CC_VERSION"
id: extract_matrix
run: |
CC_VERSION=$( echo "${{ matrix.compiler }}" | awk '{ print $2; }')
echo "CC_VERSION=${CC_VERSION}" >> "$GITHUB_OUTPUT"
- name: "Set up QTVER var"
id: set_qtver
run: |
if [[ ${{ matrix.qt_version }} = "5" ]]; then
echo "QTVER=5" >> "$GITHUB_OUTPUT"
else
echo "QTVER=6" >> "$GITHUB_OUTPUT"
fi
- name: "install dependencies"
run: sudo env QTVER="${{ matrix.qt_version }}" SYSDEP_ASSUME_YES=ON ./scripts/install-deps.sh
- name: "install tmux (oracle for the tmux-interop tests)"
# Without a tmux binary the tmux-compatibility tests self-skip, which is how they went
# their entire existence without once running: LayoutString's two select-layout oracles,
# TmuxGateway's real `tmux -C` drive, and TmuxController's GUI mirror. They are the only
# coverage `vthost/tmux/ControlModeSpawn.cpp` gets at all.
#
# Built from source rather than installed from the distro, because Ubuntu 24.04 ships tmux
# 3.4 and ImsgServer's "a real tmux binary attaches over imsg" needs the rewritten imsg
# framing that landed after 3.5. With 3.4 that oracle self-skipped, and the verification step
# below had to exclude it by name to keep the matrix green — so the one test covering the
# imsg endpoint never ran anywhere.
#
# 3.7b is not an arbitrary "new enough": it is the version the control-mode server is pinned
# to (see docs/internals/vthost.md), so the oracle now measures us against the exact protocol
# we claim to speak.
run: |
set -ex
# tmux's configure hard-errors without these two; the runner image carries neither the
# libevent nor the ncurses development headers, only their runtimes.
sudo apt -qy install libevent-dev libncurses-dev
curl -sSL -o "$RUNNER_TEMP/tmux.tar.gz" \
https://github.com/tmux/tmux/releases/download/3.7b/tmux-3.7b.tar.gz
tar xzf "$RUNNER_TEMP/tmux.tar.gz" -C "$RUNNER_TEMP"
# The release tarball ships a generated configure, so no autotools are needed here.
# --disable-utf8proc: see the macOS job for why it must be stated rather than defaulted.
# Passing it here too keeps both platforms measuring against the same tmux build.
cd "$RUNNER_TEMP/tmux-3.7b" \
&& ./configure --disable-utf8proc \
&& make -j"$(nproc)" \
&& sudo make install
tmux -V
- name: "Post-fix embedded dependency permissions."
run: sudo find _deps/sources -exec chown $UID {} \;
# Ubuntu 24.04's distro Qt is 6.4.2, which does not provide the Qt6::GuiPrivate CMake package
# the RHI renderer needs (rhi/qrhi.h). Install a modern Qt6 over it; install-qt-action prepends
# its Qt to CMAKE_PREFIX_PATH via the Qt6_DIR/QT_ROOT_DIR env vars it exports, so find_package
# resolves this one ahead of the distro package.
- name: "Install Qt (GuiPrivate-capable)"
uses: jurplel/install-qt-action@v4
with:
version: "6.11.*"
modules: qtmultimedia qt5compat qtshadertools qtspeech
- name: Install GCC
if: ${{ startsWith(matrix.compiler, 'GCC') }}
run: sudo apt install -y g++-${{ steps.extract_matrix.outputs.CC_VERSION }}
- name: Install Clang
if: ${{ startsWith(matrix.compiler, 'Clang') }}
env:
CC_VERSION: ${{ steps.extract_matrix.outputs.CC_VERSION }}
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh "$CC_VERSION"
sudo apt install -y "clang-$CC_VERSION"
sudo apt install -y "clang-tidy-$CC_VERSION"
- name: "create build directory"
run: mkdir build
- name: CMake version
run: cmake --version
- name: "cmake"
run: |
CC_NAME=$(echo "${{ matrix.compiler }}" | awk '{ print tolower($1); }')
CC_VER=$( echo "${{ matrix.compiler }}" | awk '{ print $2; }')
test "${{ matrix.compiler }}" = "GCC 8" && EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DPEDANTIC_COMPILER_WERROR=ON"
test "${CC_NAME}" = "gcc" && CC_EXE="g++"
if [[ "${CC_NAME}" = "clang" ]]; then
CC_EXE="clang++"
# CMAKE_CXX_FLAGS="-stdlib=libc++"
# CMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ -lc++abi"
# EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DENABLE_TIDY=ON"
# EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DPEDANTIC_COMPILER_WERROR=OFF"
fi
cmake \
$EXTRA_CMAKE_FLAGS \
-DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \
-DCMAKE_CXX_STANDARD=${{ matrix.cxx }} \
-DCMAKE_CXX_COMPILER="${CC_EXE}-${CC_VER}" \
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DCONTOUR_WAYLAND=OFF \
-DLIBUNICODE_UCD_BASE_DIR=$PWD/_ucd \
-DPEDANTIC_COMPILER_WERROR=OFF \
--preset gcc-debug
- name: "build"
run: cmake --build --preset gcc-debug -- -j3
- name: "tests"
# A hung test (e.g. a dead-loop) must fail the matrix fast, not stall for hours.
#
# Budget, measured rather than assumed: the offscreen suite is ~2 minutes here. The e2e
# app runs and contour_gui_test dominate it (~20-30s each); the unit suites are all under
# 3s. The two vtconformance gates would add ~3 minutes, but they self-skip on a runner
# that has not fetched vttest/esctest. Keep an eye on this ceiling when adding an e2e run.
timeout-minutes: 5
run: cmake --build --preset gcc-debug --target test
- name: "verify the tmux oracles actually ran"
# ctest prints nothing for a passing test, and a Catch2 SKIP exits 0 — so "the suite is
# green" cannot distinguish "the oracle agreed with us" from "no tmux, nothing ran". That
# ambiguity is exactly how these tests went their whole existence without executing. Assert
# it instead of inferring it: re-run the tagged subset and fail if anything reports skipped.
#
# Nothing is excluded any more: the job builds tmux 3.7b above, so the imsg oracle -- which
# needs the framing rewritten after 3.5 and had been carved out by name ever since -- runs
# like the rest and is held to the same "did not skip" bar.
run: |
set -o pipefail
for oracle in \
'out/gcc-debug/src/vthost/vthost_test|[oracle]' \
'out/gcc-debug/src/contour/contour_gui_test|[oracle]'; do
binary=${oracle%%|*}
filter=${oracle##*|}
echo "--- $binary $filter"
output=$("$binary" "$filter" 2>&1) || { printf '%s\n' "$output"; exit 1; }
printf '%s\n' "$output"
if printf '%s' "$output" | grep -q 'skipped'; then
echo "::error::$binary $filter reported SKIPPED; the tmux interop tests are not being exercised"
exit 1
fi
done
- name: "install weston (headless compositor for display-gated GUI tests)"
# Non-gating until proven on a GitHub runner: the local run is green (headless weston,
# 32 cases), but this step has not yet been validated on ubuntu-24.04 CI, so it must not
# be able to fail the matrix. Drop continue-on-error once a real runner has passed it.
continue-on-error: true
run: sudo apt-get install -qy weston qt6-wayland
- name: "display-gated GUI tests (headless weston)"
# Real-compositor run of the [display] suite (skipped by the offscreen ctest above): a
# private headless weston instance provides a stable Wayland compositor without GPU or X.
continue-on-error: true
run: ./scripts/run-display-tests.sh out/gcc-debug/src/contour/contour_gui_test
- name: "Upload unit tests"
if: ${{ matrix.compiler == 'GCC 14' && matrix.cxx == '23' && matrix.runner == 'ubuntu-24.04'}}
uses: actions/upload-artifact@v4
with:
name: contour-ubuntu2404-tests
path: |
out/gcc-debug/src/crispy/crispy_test
out/gcc-debug/src/vtparser/vtparser_test
out/gcc-debug/src/vtbackend/vtbackend_test
out/gcc-debug/src/vtbackend/bench-headless
out/gcc-debug/src/vtrasterizer/vtrasterizer_test
out/gcc-debug/src/coro/coro_test
out/gcc-debug/src/net/net_test
out/gcc-debug/src/vtworkspace/vtworkspace_test
out/gcc-debug/src/vthost/vthost_test
out/gcc-debug/src/vtconformance/vtconformance-run
test/images
retention-days: 1
# }}}
# {{{ Linux AppImage (Using Ubuntu 24.04 as base).
package_for_AppImage:
if: github.ref == 'disabled'
name: "Packaging for AppImage"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
with:
version: latest
- name: update APT database
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt-get -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: build inside docker
run: |
docker buildx build --tag contour-appimage \
-f .github/appimage/Dockerfile --load .
docker create --name contour-appimage contour-appimage
docker cp contour-appimage:/contour/Contour-latest-x86_64.AppImage contour-${{ steps.set_vars.outputs.VERSION_STRING }}.AppImage
docker container rm contour-appimage
- name: "set mode"
run: sudo chmod +x contour-${{ steps.set_vars.outputs.VERSION_STRING }}.AppImage
- name: "libfuse"
run: sudo apt-get -qy install libfuse2
- name: "Testing AppImage"
run: ./contour-${{ steps.set_vars.outputs.VERSION_STRING }}.AppImage version
- name: "Uploading AppImage"
uses: actions/upload-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}.AppImage"
path: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}.AppImage"
if-no-files-found: error
retention-days: 7
# }}}
# {{{ Ubuntu 24.04
package_for_Ubuntu:
name: "Packaging for Ubuntu ${{ matrix.os_version }}"
strategy:
fail-fast: false
matrix:
os_version: ['26.04']
runs-on: ubuntu-${{ matrix.os_version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: "ccache-ubuntu-${{ matrix.os_version }}"
max-size: 256M
- name: "install dependencies"
run: sudo env QTVER="${{ steps.set_qtver.outputs.QTVER }}" ./scripts/install-deps.sh
- name: "Post-fix embedded dependency permissions."
run: sudo find _deps/sources -exec chown $UID {} \;
- name: "create build directory"
run: mkdir build
- name: Install CMake
if: matrix.os_version != '24.04'
uses: ssrobins/install-cmake@v1
- name: CMake version
run: cmake --version
- name: "cmake"
run: |
# TODO: turn on -Werror again, that requires some code changes.
EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS"
EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DCONTOUR_PACKAGE_TERMINFO=OFF"
BUILD_DIR="build" \
CMAKE_BUILD_TYPE=RelWithDebInfo \
EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS \
-DCMAKE_CXX_STANDARD=$CMAKE_CXX_STANDARD \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DCONTOUR_TESTING=ON \
-DCONTOUR_WAYLAND=OFF \
-DPEDANTIC_COMPILER_WERROR=OFF \
" \
./scripts/ci-prepare-contour.sh
- name: "build"
run: cmake --build --preset gcc-release -- -j3
- name: "tests"
# Same reasoning as the matrix job's cap: a hung test must fail this job fast rather than
# stall it for hours. Release rather than debug, so the budget is more generous than the
# ~2 minutes measured there.
timeout-minutes: 10
run: cmake --build --preset gcc-release --target test
- name: "CPack: Creating DEB package"
run: |
set -ex
cpack --preset gcc-release
OS_VERSION=${{ matrix.os_version }}
OS_VERSION=${OS_OVERRIDE/./_}
mv -v "./out/gcc-release/Contour-${{ steps.set_vars.outputs.VERSION_STRING }}-Linux-contour.deb" \
"contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
mv -v "./out/gcc-release/Contour-${{ steps.set_vars.outputs.VERSION_STRING }}-Linux-contour-dbgsym.ddeb" \
"contour-dbgsym-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.ddeb"
- name: "Uploading artifact .deb package"
uses: actions/upload-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
path: |
contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb
contour-dbgsym-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.ddeb
if-no-files-found: error
retention-days: 7
- name: "Attempt installing the created .deb"
run: |
sudo dpkg --install "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
sudo dpkg --install "contour-dbgsym-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.ddeb"
- name: "Verify installation"
run: |
contour version
# }}}
# {{{ Ubuntu 24.04: test via valgrind
test_ubuntu2404_valgrind:
name: "Run tests via valgrind"
runs-on: ubuntu-24.04
needs: [ubuntu_2404_cc_matrix]
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
scripts
- name: "download artifact"
uses: actions/download-artifact@v4
with:
name: contour-ubuntu2404-tests
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: "fix unit test permissions"
run: |
find . -name '*_test' -exec chmod 0755 {} \;
find . -name 'bench-headless' -exec chmod 0755 {} \;
- name: "install dependencies"
run: ./scripts/ci-install-run-deps.sh valgrind
- name: "test: crispy (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/crispy/crispy_test
- name: "test: vtparser (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vtparser/vtparser_test
- name: "test: vtbackend (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vtbackend/vtbackend_test
- name: "test: vtrasterizer (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vtrasterizer/vtrasterizer_test
# The daemon's suites. Socket buffers, coroutine frames and fd ownership are precisely the
# code valgrind reads best, and this job is the closest thing the project has to a memory
# gate (no ASan/TSan job exists).
- name: "test: coro (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/coro/coro_test
- name: "test: net (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/net/net_test
- name: "test: vtworkspace (via valgrind)"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vtworkspace/vtworkspace_test
- name: "test: vthost (via valgrind)"
# ~[oracle]: the tmux interop cases fork a real tmux server over a pty and talk to it on a
# timeout. Under valgrind the child runs at full speed while our side crawls, so those
# budgets expire and the failure says nothing about memory. The Linux matrix runs them
# natively and asserts they ran.
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vthost/vthost_test '~[oracle]'
# }}}
# {{{ Ubuntu 24.04: VT conformance (esctest gates, vttest is advisory)
test_ubuntu2404_conformance:
name: "Run VT conformance suites"
runs-on: ubuntu-24.04
needs: [ubuntu_2404_cc_matrix]
steps:
# A full checkout, not a sparse one: the ratchet files the gate is judged against
# (src/vtconformance/test/*.txt) and the golden dumps live in the source tree.
- uses: actions/checkout@v4
- name: "download artifact"
uses: actions/download-artifact@v4
with:
name: contour-ubuntu2404-tests
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: "fix harness permissions"
run: find . -name 'vtconformance-run' -exec chmod 0755 {} \;
- name: "install dependencies"
run: ./scripts/ci-install-run-deps.sh python3
- name: "install vttest"
run: ./scripts/ci/vttest-install.sh
- name: "fetch esctest"
run: ./scripts/fetch-esctest.sh
# Deliberately WITHOUT --skip-if-missing. The ctest registration keeps that flag, because a
# contributor without the suites installed should not have a red build -- but in CI a suite that
# cannot be found has measured nothing, and must fail rather than quietly report success. That
# silent skip is exactly the hole this job closes: the ratchets existed for months while nothing
# in CI ever ran them.
- name: "conformance: esctest (gating)"
run: |
./out/gcc-debug/src/vtconformance/vtconformance-run run \
--suite esctest \
--suite-dir "$PWD/out/esctest2/esctest" \
--known-gaps "$PWD/src/vtconformance/test/esctest-known-gaps.txt" \
--known-failures "$PWD/src/vtconformance/test/esctest-known-failures.txt" \
--work-dir "$PWD/out/esctest-work"
# vttest gates per scenario (see Scenario::gatesBuild): its self-checking chapters replay a
# recorded command file and are reproducible, so they break the build; its visual chapters are
# still screen-driven and judged against unreviewed goldens, so they report only.
- name: "conformance: vttest"
run: |
./out/gcc-debug/src/vtconformance/vtconformance-run run \
--suite vttest \
--golden-dir "$PWD/src/vtconformance/test/golden" \
--known-gaps "$PWD/src/vtconformance/test/known-gaps.txt" \
--command-dir "$PWD/src/vtconformance/test/cmd" \
--work-dir "$PWD/out/vttest-work"
- name: "upload conformance transcripts"
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: contour-conformance-transcripts
path: |
out/esctest-work
out/vttest-work
retention-days: 3
# }}}
# {{{ Ubuntu 24.04: Test bench-headless
test_ubuntu2404_bench_headless:
strategy:
matrix:
test_case:
[
"grid cat",
"grid long"
]
name: "Run bench-headless"
runs-on: ubuntu-24.04
needs: [ubuntu_2404_cc_matrix]
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
scripts
- name: "download artifact"
uses: actions/download-artifact@v4
with:
name: contour-ubuntu2404-tests
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: "fix unit test permissions"
run: |
find . -name '*_test' -exec chmod 0755 {} \;
find . -name 'bench-headless' -exec chmod 0755 {} \;
- name: "install dependencies"
run: ./scripts/ci-install-run-deps.sh valgrind
- name: "bench-headless: ${{ matrix.test_case }}"
run: valgrind --error-exitcode=64 ./out/gcc-debug/src/vtbackend/bench-headless ${{ matrix.test_case }} size 1
# }}}
# {{{ Ubuntu check matrix
check_ubuntu2404_matrix_test_matrix:
if: ${{ always() }}
runs-on: ubuntu-latest
name: "Ubuntu Linux 24.04 post-check"
needs:
- package_for_Ubuntu
- ubuntu_2404_cc_matrix
- test_ubuntu2404_valgrind
- test_ubuntu2404_bench_headless
- test_ubuntu2404_conformance
steps:
- name: Print matrix status
run: |
echo "Result (CC matrix) : ${{ needs.ubuntu_2404_cc_matrix.result }}"
echo "Result (packaging matrix) : ${{ needs.package_for_Ubuntu.result }}"
echo "Result (conformance) : ${{ needs.test_ubuntu2404_conformance.result }}"
- name: Check build matrix status
if: ${{ needs.ubuntu_2404_cc_matrix.result != 'success' && needs.ubuntu_2404_cc_matrix.result != 'skipped' }}
run: exit 1
- name: Check package_for_Ubuntu matrix status
if: ${{ needs.package_for_Ubuntu.result != 'success' && needs.package_for_Ubuntu.result != 'skipped' }}
run: exit 1
- name: Check conformance status
if: ${{ needs.test_ubuntu2404_conformance.result != 'success' && needs.test_ubuntu2404_conformance.result != 'skipped' }}
run: exit 1
- name: "Delete artifact: contour-ubuntu2404-tests"
uses: geekyeggo/delete-artifact@v5
with:
name: contour-ubuntu2404-tests
# }}}
# {{{ GUI: test: contour quick shell exit
test_quick_exit:
name: "GUI: Quick Shell Exit"
needs: [package_for_Ubuntu]
strategy:
fail-fast: false
matrix:
os_version: ['26.04']
runs-on: ubuntu-${{ matrix.os_version }}
env:
# I'm giving up on eliminating all leaks for now.
# There are still some deep inside Qt I can't explain myself if it's because of me.
ASAN_OPTIONS: detect_leaks=0
# Can be used to execute contour within a certain environment, such as valgrind:
# Valgrind is much more precise, but 10x slower.
CONTOUR_PREFIX: "" # valgrind --leak-check=full --num-callers=64 --error-exitcode=112"
steps:
- uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- uses: actions/download-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
- name: "install dependencies"
run: ./scripts/ci/notcurses-install-deps.sh
- name: "install contour"
# `apt-get install ./<file>.deb` (note the ./) resolves and pulls the package's declared runtime
# dependencies (Qt6, libyaml-cpp, …) for this exact release — unlike `dpkg -i`, which fails if any
# dependency is absent. This is why the runtime-deps script no longer hardcodes Qt package names.
run: sudo apt-get install -y "./contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
- name: "contour executable test"
run: |
contour version
contour help
- name: "create and patch contour.yml config file"
run: |
set -ex
mkdir -p ~/.config/contour/
contour generate config to ~/.config/contour/contour.yml
sed -i -e 's/locator: native/locator: mock/' ~/.config/contour/contour.yml
sed -i -e 's/strict_spacing: true/strict_spacing: false/' ~/.config/contour/contour.yml
cat .github/mock-font-locator.yml >> ~/.config/contour/contour.yml
cat ~/.config/contour/contour.yml
- name: "Run Contour: quick exit"
timeout-minutes: 5
id: Xvfb-contour
run: |
./scripts/ci/Xvfb-contour-run.sh \
"quick-exit-dumps/${{ matrix.name }}" \
true
- name: "Save dump"
uses: actions/upload-artifact@v4
with:
name: quick-exit-contour-dump
path: quick-exit-dumps
if-no-files-found: ignore
retention-days: 1
- name: "Check result success"
run: |
exit ${{ steps.Xvfb-contour.outputs.exitCode }}
# }}}
# {{{ GUI: external test: notcurses
test_notcurses:
name: "GUI: notcurses-demo ${{ matrix.name }}"
# Must match the OS the consumed .deb targets (26.04): a 26.04-built package links Qt 6.10 and a
# newer libstdc++ than a 24.04 runner provides, so running it there fails with GLIBCXX/Qt-version
# "not found" at load time.
runs-on: ubuntu-26.04
needs: [package_for_Ubuntu]
strategy:
fail-fast: false
matrix:
include: # {{{ (name, id) tuples
# ixetunchmdbkywjgarvlsfqzo
- name: 'aanimate-box-chunli-dragon-eagle-fission-grid-highcon'
id: 'abcdefgh'
- name: 'intro-junle-keller-luigi-mojibake-normal-outro-qrcode'
id: 'ijklmnoq'
- name: 'reel-sliders-trans-uniblock-view-whiteout-xray-yield-zoo'
id: 'rstuvwxyz'
# }}}
env:
LD_LIBRARY_PATH: /home/runner/opt/notcurses/lib
# I'm giving up on eliminating all leaks for now.
# There are still some deep inside Qt I can't explain myself if it's because of me.
ASAN_OPTIONS: detect_leaks=0
# Can be used to execute contour within a certain environment, such as valgrind:
# Valgrind is much more precise, but 10x slower.
CONTOUR_PREFIX: "" # valgrind --leak-check=full --num-callers=64 --error-exitcode=112"
steps:
- uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- uses: actions/download-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu26.04-amd64.deb"
- name: "install dependencies"
run: ./scripts/ci/notcurses-install-deps.sh
- name: "git clone"
run: git clone https://github.com/dankamongmen/notcurses.git
- name: "configure cmake"
# The smoke test only needs the notcurses-demo binary, so build the minimum: skip the C++
# bindings and the doctest-based test suite (USE_DOCTEST/BUILD_TESTING — the source of the
# doctest dependency that broke configure), the proof-of-concept binaries (USE_POC), and the
# pandoc man pages. NB: the option is USE_CXX, not USE_CPP — the old -DUSE_CPP=OFF was a no-op,
# which is why the C++/doctest path stayed enabled.
run: |
cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/home/runner/opt/notcurses \
-DUSE_CXX=OFF \
-DUSE_DOCTEST=OFF \
-DBUILD_TESTING=OFF \
-DUSE_POC=OFF \
-DUSE_DEFLATE=OFF \
-DUSE_MULTIMEDIA=ffmpeg \
-DUSE_PANDOC=OFF \
-DUSE_STATIC=ON \
-S notcurses \
-B notcurses/build
- name: "Build notcurses"
run: cmake --build notcurses/build/ -- -j3
- name: "Install notcurses"
run: cmake --install notcurses/build
- name: "Run notcurses-demo -h"
timeout-minutes: 1
run: LD_LIBRARY_PATH="/home/runner/opt/notcurses/lib" ~/opt/notcurses/bin/notcurses-demo -p ~/opt/notcurses/share/notcurses -h
- name: "install contour"
# See the quick-shell-exit job: apt-get resolves the .deb's declared deps for this release.
run: sudo apt-get install -y "./contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu26.04-amd64.deb"
- name: "contour executable test"
run: |
contour version
contour help
ls -hl ~/opt/notcurses/
- name: "create and patch contour.yml config file"
run: |
set -ex
mkdir -p ~/.config/contour/
contour generate config to ~/.config/contour/contour.yml
sed -i -e 's/locator: native/locator: mock/' ~/.config/contour/contour.yml
sed -i -e 's/strict_spacing: true/strict_spacing: false/' ~/.config/contour/contour.yml
cat .github/mock-font-locator.yml >> ~/.config/contour/contour.yml
cat ~/.config/contour/contour.yml
- name: "Run Contour: notcurses-demo ${{ matrix.name }}"
timeout-minutes: 5
id: Xvfb-contour-notcurses
run: |
./scripts/ci/Xvfb-contour-run.sh \
"notcurses-demo-dumps/${{ matrix.name }}" \
~/opt/notcurses/bin/notcurses-demo \
-p ~/opt/notcurses/share/notcurses ${{ matrix.id }}
- name: "Save dump"
uses: actions/upload-artifact@v4
with:
name: notcurses-contour-dump
path: notcurses-demo-dumps
if-no-files-found: ignore
retention-days: 1
- name: "Check result success"
run: |
exit ${{ steps.Xvfb-contour-notcurses.outputs.exitCode }}
check_notcurses_test_matrix:
if: ${{ always() }}
runs-on: ubuntu-latest
name: tests matrix
needs: test_notcurses
steps:
- name: Check build matrix status
if: ${{ needs.test_notcurses.result != 'success' }}
run: exit 1
# }}}
# {{{ Check Ubuntu package
check_package_ubuntu:
name: "Check packages for Ubuntu "
strategy:
fail-fast: false
matrix:
os_version: ['26.04']
runs-on: ubuntu-${{ matrix.os_version}}
needs: [package_for_Ubuntu]
steps:
- uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set environment variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- uses: actions/download-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version }}-amd64.deb"
- name: "Check install package"
run: |
sudo apt install "./contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu${{ matrix.os_version}}-amd64.deb"
# }}}
# {{{ Release
check_release:
if: github.head_ref == 'release'
name: Check Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars.sh and check-release.sh
run: sudo apt -qy install libxml2-utils
- name: set variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
- name: run release checks
run: ./scripts/check-release.sh
do_release:
if: github.head_ref == 'release'
name: Create Github release
runs-on: ubuntu-latest
needs:
- check_links
- check_release
- check_notcurses_test_matrix
- macOSArm
- package_for_Ubuntu
- windows
permissions:
# We need write permissions on contents to create GitHub releases and on
# discussions to create the release announcement in the discussion forums
contents: write
discussions: write
steps:
- uses: actions/checkout@v4
- name: "update APT database"
run: sudo ./scripts/apt-update.sh
- name: Installing xmllint for ci-set-vars
run: sudo apt -qy install libxml2-utils
- name: set variables
id: set_vars
run: ./scripts/ci-set-vars.sh
env:
REPOSITORY: ${{ github.event.repository.name }}
# {{{ fetch release artifacts
- name: "fetch artifact: Ubuntu 24.04"
uses: actions/download-artifact@v4
with:
name: contour-${{ steps.set_vars.outputs.VERSION_STRING }}-ubuntu26.04-amd64.deb
- name: "fetch artifact: MacOS (ARM)"
uses: actions/download-artifact@v4
with:
# VERSION_STRING, matching the .deb above: `version` omits the `-prerelease`
# suffix, so a prerelease used to publish a .dmg whose name claimed otherwise.
name: "contour-${{ steps.set_vars.outputs.VERSION_STRING }}-macOS-arm.dmg"
- name: "fetch artifact: Windows (MSI)"
uses: actions/download-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.version }}-win64.msi"
- name: "fetch artifact: Windows (ZIP)"
uses: actions/download-artifact@v4
with:
name: "contour-${{ steps.set_vars.outputs.version }}-win64.zip"
# - name: "fetch artifact: AppImage"
# uses: actions/download-artifact@v4
# with:
# name: "contour-${{ steps.set_vars.outputs.version }}.AppImage"
- name: "fetch artifact: Static build"
uses: actions/download-artifact@v4
with:
name: "contour"
# }}}
- name: "inspect"
run: ls -lisahF
- name: inspect recurse
run: pwd; ls -hlaR
# -------------------------------------------------------------
- name: Create Github release page
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # token provided by Actions
with:
tag_name: v${{ steps.set_vars.outputs.version }}${{ steps.set_vars.outputs.tag_suffix}}
name: Contour ${{ steps.set_vars.outputs.version }} ${{ steps.set_vars.outputs.RELEASENAME_SUFFIX}}
body_path: ${{ github.workspace }}/release-body.md
draft: true
prerelease: ${{ steps.set_vars.outputs.IS_PRERELEASE }}
fail_on_unmatched_files: true
files: |
*.deb
*.ddeb
*.dmg
*.msi
*.zip
# *.AppImage
# }}}