Skip to content

Keep a round every millisecond minimised or not; quiet the outputs be… #78

Keep a round every millisecond minimised or not; quiet the outputs be…

Keep a round every millisecond minimised or not; quiet the outputs be… #78

Workflow file for this run

name: full
# The whole application, Slint included — **the only CI**, and **where a release is built** since
# 2026-10-03, when the user had every build and test that ran twice taken out: core.yml (its tests
# were a subset of this workflow's windows-msvc job, run again on every push) and release.yml
# (the windows-msvc build over again on the same commit, ~40 minutes, only so the exe could name
# a tag that did not exist yet when this ran). What went with core.yml: the quicker first answer,
# and its check that the engine builds without Slint ever being fetched.
#
# **No schedule**, by the user on 2026-10-05. A weekly run (Mondays, 06:00 UTC) dated from when
# this ran only on demand; once every push that touches the code ran it, the weekly one only
# tested main again — and GitHub, starting it seven hours late, put it in this concurrency group on
# top of 1.3.0's release run and cancelled that.
#
# **A release:** push the bump commit and its annotated tag together —
# `git push --atomic origin main vX.Y.Z` — so the tag is there when this checks out, and every
# binary built here names it. When all three jobs pass on a commit a `v*` tag points at, the
# windows-msvc job's files are attached to that tag's GitHub Release (the `publish` job below),
# with the tag's message as the notes. A tag pushed after its commit's run: rerun that run
# (`gh run rerun <id>`); its checkout then has the tag. A push to main while a release run is
# building cancels it (the concurrency group below): rerun it the same way.
#
# **Slint is cached** (2026-10-03, the same request): it was 26 of the windows-msvc job's 42
# minutes of building, every run, and it changes only when the pins below do. See "Slint, from
# the cache".
#
# This workflow is ENABLED, by the user on 2026-09-14. It had been `disabled_manually` since
# 2026-09-04, which left a UI change covered by nothing. That was noticed while releasing 0.9.6
# — a release whose every changed file was under src/ui/ — and the answer was to turn this back
# on rather than to keep dispatching it by hand and forgetting to.
#
# WINDOWS ONLY, by the user, 2026-09-06: it used to cost ≈150 billable minutes because macOS is
# billed at 10× and Slint takes 7–25 minutes to build. The macOS and Linux app entries are
# commented out rather than deleted so bringing them back is uncommenting — but bring them back
# knowing what they used to cost.
#
# **Plus the sanitizers, on every push the path filter below lets through (every push that
# touches the code), by the user on 2026-09-23** (the audit's T4, which
# found that neither had ever run in CI and that `linux-tsan` had never run anywhere):
# AddressSanitizer over the whole tree, the UI included, on Windows; and ThreadSanitizer on
# Linux, because there is no ThreadSanitizer for Windows at all. takt4's threads — the audio
# callback, the model, the tracker, the output thread, the UI, the OSC and MIDI listeners
# and Link's own — are the reason. The cost was put to the user and accepted.
#
# The path filter covers everything that can change the app (the audit found src/core,
# tests/ui, tests/CMakeLists.txt, assets and tools/embed_asset.py all missing from it, so a
# change there was not built when it was pushed; and the audit of 2026-09-25, B4, the Rust
# pin, which is the compiler Slint is built with, and LICENSE and THIRD-PARTY-NOTICES.txt,
# which are embedded in takt4.exe). Documentation alone does not trigger it.
#
# Note the concurrency group below cancels in-progress runs for the same ref: a second push
# to main while this is building kills the first run rather than queueing behind it.
on:
push:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
- 'assets/**'
- 'third_party/**'
- 'cmake/**'
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'tools/embed_asset.py'
- 'rust-toolchain.toml'
- 'LICENSE'
- 'THIRD-PARTY-NOTICES.txt'
- '.github/workflows/full.yml'
pull_request:
paths:
- 'src/**'
- 'tests/**'
- 'assets/**'
- 'third_party/**'
- 'cmake/**'
- 'CMakeLists.txt'
- 'CMakePresets.json'
- 'tools/embed_asset.py'
- 'rust-toolchain.toml'
- 'LICENSE'
- 'THIRD-PARTY-NOTICES.txt'
- '.github/workflows/full.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
build:
name: ${{ matrix.preset }}
runs-on: ${{ matrix.os }}
# The ASan job builds the whole tree instrumented and runs every test under it.
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
# The hosted Windows images (windows-latest and windows-2025 alike, as of the
# 2026-08-24 image) ship Visual Studio 2026 only. The preset names no
# generator, so CMake picks whatever Visual Studio is installed.
- os: windows-latest
preset: windows-msvc
app: build/windows-msvc/bin/Release/takt4.exe
cli: build/windows-msvc/bin/Release/takt4-cli.exe
# Where Corrosion has cargo build Slint: `<build>/<platform>/<config>/cargo` under
# Visual Studio, `<build>/cargo` under Ninja (Corrosion.cmake, `cargo_target_dir`).
cargo: build/windows-msvc/x64/Release/cargo
# AddressSanitizer over everything, UI included. cmake/sanitizers.cmake copies the
# ASan runtime DLL beside the binaries, so they start without Visual Studio's PATH.
- os: windows-latest
preset: windows-asan
app: build/windows-asan/bin/Release/takt4.exe
cli: build/windows-asan/bin/Release/takt4-cli.exe
cargo: build/windows-asan/x64/Release/cargo
# ThreadSanitizer: Linux is where it exists. TSAN_OPTIONS makes a report fail the
# test that produced it rather than scroll past in a passing run.
- os: ubuntu-24.04
preset: linux-tsan
app: build/linux-tsan/bin/takt4
cli: build/linux-tsan/bin/takt4-cli
cargo: build/linux-tsan/cargo
# Windows only for now; see the note at the top of this file.
# - os: macos-latest
# preset: macos
# app: build/macos/bin/takt4
# cli: build/macos/bin/takt4-cli
# - os: ubuntu-24.04
# preset: linux
# app: build/linux/bin/takt4
# cli: build/linux/bin/takt4-cli
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
# Every commit and tag, so `git describe` — what `--version` prints — names a release's
# tag rather than a hash. The whole history is ~33 MB.
fetch-depth: 0
- name: Install Linux packages
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
pkg-config libasound2-dev libjack-jackd2-dev libfontconfig-dev libfreetype-dev
# The hosted images ship CMake, Ninja and a Rust toolchain that satisfy the
# requirements in README.md. Record what was actually used.
- name: Toolchain versions
run: |
cmake --version
ninja --version || true
rustc --version || true
cargo --version || true
# **Slint, from the cache**: its source and everything cargo built from it, so a run builds
# only takt4 itself. Both are needed — cargo judges Slint's own crates stale by their
# sources' file times, and a fresh clone is newer than anything it built — so the source
# lives at one fixed place, handed to CMake (FETCHCONTENT_SOURCE_DIR_SLINT, below) and
# cached with its times, and is never cloned over. **And Slint's CMake build directory**
# (`_deps/slint-build`, ~1 MB): its build script writes the C++ headers there
# (`generated_include/private/slint_internal.h` …), outside cargo's own output, and a cargo
# that finds its output fresh does not run the script again — the first cached run, on
# 2026-10-03, restored everything else and failed on that header.
#
# The key is everything that decides what was built: the build type, the runner image
# (the C++ inside Slint's cargo build — Skia's bindings, ICU, harfbuzz — is compiled with
# the image's compiler), the Rust pin, and cmake/deps.cmake, which holds Slint's tag, its
# commit and its features. No fallback key: a near miss would mix two compilers' objects.
# `v2`: what is cached changed (the build directory above); a v1 cache lacks the headers.
# The image is read in a step: ImageOS and ImageVersion are the runner's environment, which
# the `env` context of an expression does not see.
- name: The runner image
id: image
run: echo "image=${ImageOS:?}-${ImageVersion:?}" >> "$GITHUB_OUTPUT"
- name: Slint, from the cache
id: slint
uses: actions/cache/restore@v6
with:
key: slint-v2-${{ matrix.preset }}-${{ steps.image.outputs.image }}-${{ hashFiles('rust-toolchain.toml', 'cmake/deps.cmake') }}
path: |
build/slint-src
build/${{ matrix.preset }}/_deps/slint-build
${{ matrix.cargo }}
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
# The tag cmake/deps.cmake fetches, cloned as FetchContent would; deps.cmake still checks
# the commit it checked out against the one it records.
- name: Slint source
if: steps.slint.outputs.cache-hit != 'true'
run: |
tag=$(grep -A1 'GIT_REPOSITORY https://github.com/slint-ui/slint.git' cmake/deps.cmake \
| sed -n 's/^ *GIT_TAG \(v[0-9.]*\)$/\1/p')
test -n "$tag"
rm -rf build/slint-src
git clone --depth 1 --branch "$tag" https://github.com/slint-ui/slint.git build/slint-src
# A path CMake reads on both systems: Git Bash's `pwd -W` is D:/a/..., Linux has no -W.
- name: Configure
run: |
src="$(pwd -W 2>/dev/null || pwd)/build/slint-src"
cmake --preset ${{ matrix.preset }} -DFETCHCONTENT_SOURCE_DIR_SLINT="$src"
# Linux goes on past the first error (Ninja's -k 0), so a run reports every file GCC
# refuses rather than one per run: GCC warns about things MSVC does not, and this tree
# builds with warnings as errors.
- name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel ${{ runner.os == 'Linux' && '-- -k 0' || '' }}
# Saved as soon as Slint is built, not at the end: a test that fails must not cost the next
# run the build. Two runs missing at once both try; the second is refused, harmlessly.
- name: Slint, into the cache
if: steps.slint.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@v6
with:
key: ${{ steps.slint.outputs.cache-primary-key }}
path: |
build/slint-src
build/${{ matrix.preset }}/_deps/slint-build
${{ matrix.cargo }}
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
# `-all`: a runner has no show on it to disturb, so it runs the [network] and [hardware]
# tests the default presets leave out on a rig (the audit's T2).
- name: Test
env:
TSAN_OPTIONS: halt_on_error=1:second_deadlock_stack=1
run: ctest --preset ${{ matrix.preset }}-all
- name: Version banner
run: ${{ matrix.app }} --version
- name: Console tool
run: |
${{ matrix.cli }} --version
${{ matrix.cli }} devices
# **A release's file**, on a commit a `v*` tag points at, pushed or dispatched — never a pull
# request. The tag must be the version in CMakeLists.txt, and takt4.exe must say it is exactly
# that tag: not "-dirty", not a commit after it. takt4.exe alone: takt4-cli is the development
# console, built and tested here but not released. Handed to `publish`, which attaches it
# once every job here has passed.
- name: Release files
id: release
if: matrix.preset == 'windows-msvc' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
run: |
tag=$(git tag --points-at HEAD 'v*' | head -1)
if [ -z "$tag" ]; then
echo "No v* tag on $GITHUB_SHA: not a release."
exit 0
fi
version=$(sed -n 's/^ VERSION \([0-9.]*\)$/\1/p' CMakeLists.txt | head -1)
echo "CMakeLists.txt says $version; the tag is $tag"
test "v$version" = "$tag"
bin=build/windows-msvc/bin/Release
"$bin/takt4.exe" --version | tee app.txt
grep -qx " commit: $tag" app.txt
mkdir dist
cp "$bin/takt4.exe" dist/
ls -l dist
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: Keep the release files
if: steps.release.outputs.tag != ''
uses: actions/upload-artifact@v7
with:
name: release
path: dist/
retention-days: 3
# Attaches the windows-msvc job's files to the tag's Release — created from the tag's message if
# there is none yet, the files replaced if one was made by hand — once all three jobs passed.
# Nothing to do on a commit no `v*` tag points at.
publish:
needs: build
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
# This token can write releases; it is handed to `gh` below and not left in .git.
persist-credentials: false
- name: Which release
id: tag
run: |
tag=$(git tag --points-at HEAD 'v*' | head -1)
echo "tag=$tag" >> "$GITHUB_OUTPUT"
if [ -z "$tag" ]; then echo "No v* tag on $GITHUB_SHA: nothing to publish."; fi
- name: The release files
if: steps.tag.outputs.tag != ''
uses: actions/download-artifact@v8
with:
name: release
path: dist
- name: Attach to the release
if: steps.tag.outputs.tag != ''
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
run: |
ls -l dist
if gh release view "$TAG" > /dev/null 2>&1; then
gh release upload "$TAG" dist/* --clobber
else
gh release create "$TAG" dist/* --verify-tag --notes-from-tag --title "takt4 ${TAG#v}"
fi