Repository navigation
full #96
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 every build job passes 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. | |
| # | |
| # **macOS and Linux are built again, by the user on 2026-10-08**, with the repo public: GitHub's | |
| # standard runners cost a public repo nothing, macOS included. They had been left out since | |
| # 2026-09-06, when a private repo paid 10× for every macOS minute. macOS is built twice, on an | |
| # Apple Silicon runner and on an Intel one, because takt4 has to run on both; `linux` is the | |
| # release build, on Ubuntu 22.04 so the binary runs on any glibc from 2.35 up, where | |
| # `linux-tsan` (24.04) is the sanitizer. | |
| # | |
| # **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.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 | |
| # macOS on both kinds of Mac, from the same preset. `name` tells the two jobs, and their | |
| # Slint caches, apart: the runner image's name is the same on both. | |
| - os: macos-26 | |
| name: macos-arm64 | |
| preset: macos | |
| launch: true | |
| app: build/macos/bin/takt4.app/Contents/MacOS/takt4 | |
| cli: build/macos/bin/takt4-cli | |
| cargo: build/macos/cargo | |
| - os: macos-26-intel | |
| name: macos-x64 | |
| preset: macos | |
| launch: true | |
| app: build/macos/bin/takt4.app/Contents/MacOS/takt4 | |
| cli: build/macos/bin/takt4-cli | |
| cargo: build/macos/cargo | |
| # The Linux release: the oldest supported Ubuntu, since a binary needs at least the | |
| # glibc it was built against. Its own GCC 11 is older than anything else here builds | |
| # with, so GCC 12, which 22.04 also ships. | |
| - os: ubuntu-22.04 | |
| preset: linux | |
| launch: true | |
| # What the launch below needs: a screen to draw on, something to photograph it with, | |
| # and the X libraries Slint opens when it starts, which the runner image may lack. | |
| apt: xvfb imagemagick libxkbcommon-x11-0 libxcursor1 libxrandr2 libxi6 libegl1 libgl1 libgl1-mesa-dri | |
| cc: gcc-12 | |
| cxx: g++-12 | |
| app: build/linux/bin/takt4 | |
| cli: build/linux/bin/takt4-cli | |
| cargo: build/linux/cargo | |
| 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 | |
| # **Bounded, and tried again.** On 2026-10-07 the runners' own mirror stopped answering and | |
| # apt, which has no timeout of its own, waited on archive.ubuntu.com for over an hour in two | |
| # runs in a row, until each was cancelled. Each try now has a time limit, over IPv4, three times. | |
| - name: Install Linux packages | |
| if: runner.os == 'Linux' | |
| timeout-minutes: 20 | |
| run: | | |
| apt=(-o Acquire::ForceIPv4=true -o Acquire::Retries=3 -o Acquire::http::Timeout=30 | |
| -o Acquire::https::Timeout=30) | |
| for try in 1 2 3; do | |
| if sudo timeout 240 apt-get "${apt[@]}" update && | |
| sudo timeout 300 apt-get "${apt[@]}" install -y --no-install-recommends \ | |
| pkg-config libasound2-dev libjack-jackd2-dev libfontconfig-dev libfreetype-dev \ | |
| ${{ matrix.cxx }} ${{ matrix.apt }}; then | |
| exit 0 | |
| fi | |
| echo "apt did not finish (try $try of 3)" | |
| sudo dpkg --configure -a || true # an install cut short leaves dpkg half done | |
| sleep 20 | |
| done | |
| exit 1 | |
| # A job that names its compiler has every later step use it: CMake reads CC and CXX once, at | |
| # configure, but the C++ inside Slint's cargo build is compiled by the cc crate, which reads | |
| # them when cargo runs. | |
| - name: The compiler | |
| if: matrix.cxx | |
| run: | | |
| echo "CC=${{ matrix.cc }}" >> "$GITHUB_ENV" | |
| echo "CXX=${{ matrix.cxx }}" >> "$GITHUB_ENV" | |
| # The macOS presets build with Ninja, which not every macOS image ships. | |
| - name: Ninja | |
| if: runner.os == 'macOS' | |
| run: command -v ninja || brew install ninja | |
| # The hosted images ship CMake 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 | |
| ${CXX:-c++} --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.name || 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" | |
| # Line tables on macOS while it is being ported, for the backtraces below. TEMPORARY. | |
| extra="" | |
| if [ "$RUNNER_OS" = macOS ]; then extra="-DCMAKE_CXX_FLAGS=-gline-tables-only"; fi | |
| cmake --preset ${{ matrix.preset }} -DFETCHCONTENT_SOURCE_DIR_SLINT="$src" $extra | |
| # **Slint on its own first**, so it reaches the cache below even when takt4's own code then | |
| # fails to compile — which, on a system nothing has built for a while, is most runs: without | |
| # this, every one of those runs built Slint again from cold. | |
| - name: Build Slint | |
| if: steps.slint.outputs.cache-hit != 'true' | |
| run: cmake --build --preset ${{ matrix.preset }} --parallel --target cargo-build_slint_cpp cargo-build_slint-compiler | |
| # 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 | |
| # Ninja goes on past the first error (-k 0), so a run reports every file GCC or clang | |
| # refuses rather than one per run: each 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 != 'Windows' && '-- -k 0' || '' }} | |
| # **One std::exception** (macOS): the app and the UI tests use libc++abi's typeinfo, not a | |
| # copy of their own. Skia's prebuilt bindings bring one, and with it every | |
| # `catch (const std::exception&)` missed takt4's own exceptions — a MIDI port that was not | |
| # there ended the program (2026-10-08; cmake/apple_skia_typeinfo.cmake keeps it to Skia). | |
| - name: One std::exception | |
| if: runner.os == 'macOS' | |
| run: | | |
| for b in takt4_ui_tests takt4.app/Contents/MacOS/takt4; do | |
| if nm -m "build/macos/bin/$b" | grep -E '(non-external|private external).*__ZTISt9exception$'; then | |
| echo "$b has a copy of typeinfo for std::exception of its own" | |
| exit 1 | |
| fi | |
| done | |
| echo "both use libc++abi's" | |
| # `-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 | |
| # The bundle macOS reads: an Info.plist it can parse, naming the executable that is there, and | |
| # the two sentences it shows when it asks for the microphone and the local network. | |
| - name: The app bundle | |
| if: runner.os == 'macOS' | |
| run: | | |
| plist=build/macos/bin/takt4.app/Contents/Info.plist | |
| plutil -lint "$plist" | |
| plutil -p "$plist" | |
| test "$(plutil -extract CFBundleExecutable raw "$plist")" = takt4 | |
| plutil -extract NSMicrophoneUsageDescription raw "$plist" | |
| plutil -extract NSLocalNetworkUsageDescription raw "$plist" | |
| test "$(plutil -extract CFBundleIconFile raw "$plist")" = takt4.icns | |
| test -s build/macos/bin/takt4.app/Contents/Resources/takt4.icns | |
| codesign -dv build/macos/bin/takt4.app 2>&1 || true | |
| - name: Console tool | |
| run: | | |
| ${{ matrix.cli }} --version | |
| ${{ matrix.cli }} devices | |
| # **Does it open?** The real app, started as a person would and left running for fifteen | |
| # seconds, then photographed — with the tests' sandbox on, so it opens no device and reaches | |
| # nothing on the network. macOS runners have a screen; Linux gets a virtual one. What no test | |
| # shows: the window drawing on this system. The pictures are kept with the run. | |
| - name: Launch | |
| if: matrix.launch | |
| env: | |
| TAKT4_TEST_SANDBOX: '1' | |
| TAKT4_NO_CRASH_DIALOG: '1' | |
| run: | | |
| mkdir -p shots launch | |
| export TAKT4_SETTINGS_DIR="$PWD/launch" | |
| shot="shots/${{ matrix.name || matrix.preset }}.png" | |
| if [ "$RUNNER_OS" = macOS ]; then | |
| "${{ matrix.app }}" > launch/out.txt 2>&1 & | |
| pid=$! | |
| sleep 15 | |
| screencapture -x "$shot" || echo "no screenshot" | |
| else | |
| xvfb-run -a -s "-screen 0 1280x1000x24" bash -c ' | |
| "$1" > launch/out.txt 2>&1 & | |
| pid=$! | |
| sleep 15 | |
| import -window root "$2" || echo "no screenshot" | |
| if kill -0 $pid; then kill $pid; echo running > launch/state; fi | |
| ' launch "${{ matrix.app }}" "$shot" | |
| fi | |
| if [ "$RUNNER_OS" = macOS ] && kill -0 $pid 2>/dev/null; then | |
| kill $pid | |
| echo running > launch/state | |
| fi | |
| cat launch/out.txt || true | |
| test "$(cat launch/state 2>/dev/null)" = running | |
| - name: Keep the launch pictures | |
| if: matrix.launch && always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: shots-${{ matrix.name || matrix.preset }} | |
| path: shots/ | |
| retention-days: 7 | |
| if-no-files-found: ignore | |
| # **The downloads.** Each Mac's app, zipped with ditto so the bundle and its permissions come | |
| # through the upload whole, for `macos-universal` to make one app of. The Linux program, | |
| # tarred for the same reason, with what it needs from the system said here: the libraries it | |
| # links, and the newest glibc and libstdc++ versions it asks for — the oldest a system can | |
| # have and still run it. | |
| - name: Package | |
| if: matrix.launch | |
| run: | | |
| mkdir -p dist | |
| if [ "$RUNNER_OS" = macOS ]; then | |
| ditto -c -k --keepParent build/macos/bin/takt4.app "dist/takt4-${{ matrix.name }}.app.zip" | |
| else | |
| # Without its symbols: no crash report reads them (the Windows release has none either), | |
| # and they were most of a 60 MB download. | |
| strip build/linux/bin/takt4 | |
| tar -C build/linux/bin -czf dist/takt4-linux-x86_64.tar.gz takt4 | |
| ldd build/linux/bin/takt4 | |
| echo "newest glibc asked for: $(objdump -T build/linux/bin/takt4 | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -1)" | |
| echo "newest libstdc++ asked for: $(objdump -T build/linux/bin/takt4 | grep -o 'GLIBCXX_[0-9.]*' | sort -uV | tail -1)" | |
| fi | |
| ls -l dist | |
| - name: Keep the downloads | |
| if: matrix.launch | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: ${{ runner.os == 'macOS' && format('app-{0}', matrix.name) || 'release-linux' }} | |
| path: dist/ | |
| retention-days: 7 | |
| # **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 | |
| # **One app for both kinds of Mac**: the two builds' executables put into one with lipo, the | |
| # bundle signed again — ad hoc, since there is no developer account; Apple Silicon will not run | |
| # an executable without a signature, and changing the executable broke the linker's — and | |
| # checked, then zipped for the Release. | |
| macos-universal: | |
| needs: build | |
| runs-on: macos-26 | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: app-macos-* | |
| path: in | |
| - name: One app | |
| run: | | |
| set -e | |
| mkdir arm x64 out dist | |
| ditto -x -k in/app-macos-arm64/takt4-macos-arm64.app.zip arm | |
| ditto -x -k in/app-macos-x64/takt4-macos-x64.app.zip x64 | |
| cp -R arm/takt4.app out/ | |
| lipo -create arm/takt4.app/Contents/MacOS/takt4 x64/takt4.app/Contents/MacOS/takt4 \ | |
| -output out/takt4.app/Contents/MacOS/takt4 | |
| lipo -info out/takt4.app/Contents/MacOS/takt4 | |
| lipo -info out/takt4.app/Contents/MacOS/takt4 | grep -q 'x86_64 arm64\|arm64 x86_64' | |
| codesign --force --deep --sign - out/takt4.app | |
| codesign --verify --deep --strict --verbose=2 out/takt4.app | |
| out/takt4.app/Contents/MacOS/takt4 --version | |
| arch -x86_64 out/takt4.app/Contents/MacOS/takt4 --version || echo "(no Rosetta here to try the Intel half)" | |
| ditto -c -k --keepParent out/takt4.app dist/takt4-macos.zip | |
| ls -l dist | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: release-macos | |
| path: dist/ | |
| retention-days: 7 | |
| # 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 |