Skip to content

feat: support lyric extension endpoints in mx::api (#394) #503

feat: support lyric extension endpoints in mx::api (#394)

feat: support lyric extension endpoints in mx::api (#394) #503

Workflow file for this run

name: CI
on:
pull_request:
push:
branches: [main]
# Cancel an in-flight run when a newer push supersedes it. The group is keyed
# on the ref, so a new push to a PR cancels that PR's prior run without
# touching other PRs or master. Limited to pull_request events: each master
# push still runs to completion rather than aborting mid-build.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Pin the Docker build-volume name so CI's bind-backed "mx-build" volume
# (created per-job below) is the one the Makefile actually mounts. Without
# this the Makefile computes a CURDIR-hashed name (for worktree isolation)
# that doesn't match the pre-created volume.
env:
DOCKER_VOLUME: mx-build
# Coverage is intentionally absent here -- the instrumented core/api builds are
# expensive and run only on demand from .github/workflows/coverage.yaml (the
# "Run workflow" button or a `/coverage` PR comment).
#
# Every Linux job runs its make targets inside the pinned mx-sdk Docker
# toolchain (ccache baked into the image); the macOS and Windows jobs build
# natively to catch AppleClang and MSVC fallout, kept incremental across runs by
# a content-addressed compiler cache -- ccache on macOS, sccache on Windows.
jobs:
# Cheap up-front diff check so the gen job (generator/audit tests, Go/C
# corert, the ratcheted quality/lint gates, and the gen-quality PR comment)
# only runs -- and only spends a PR comment -- when gen/ actually changed. A
# gen/ change that's md-only (README/AGENTS/DESIGN docs) doesn't move any of
# that, so it's excluded too.
changes:
name: "Detect gen/ changes"
runs-on: ubuntu-latest
outputs:
gen: ${{ steps.filter.outputs.gen }}
steps:
- uses: actions/checkout@v5
- uses: dorny/paths-filter@v3
id: filter
with:
# predicate-quantifier 'every' requires a changed file to match ALL
# patterns to count -- i.e. be under gen/ AND not markdown. Without
# it the patterns are OR'd, and the `!gen/**/*.md` rule then matches
# every non-gen-markdown path in the repo, so the filter fired on
# essentially every PR (the gen job ran even for pure Makefile/C++
# changes). See picomatch negation semantics.
predicate-quantifier: 'every'
filters: |
gen:
- 'gen/**'
- '!gen/**/*.md'
# The deep gate: every C++ suite -- the mx::core substrate (corert + unit)
# and the mx::api product surface (mxtest + examples + corpus round-trip) --
# built once through the pinned mx-sdk toolchain. This is the one place the
# core suites run, so it is the authoritative C++ correctness check.
test-linux:
name: "linux: test-all"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# Container-driver builder (needed to export the gha layer cache) plus
# the runtime-token export so the Makefile auto-detects
# ACTIONS_RUNTIME_TOKEN and pushes/pulls the Docker layer cache. Keeps
# the toolchain-install layer warm across runs.
- uses: docker/setup-buildx-action@v3
- uses: crazy-max/ghaction-github-runtime@v3
# Back the mx-build volume with a workspace path so actions/cache can
# persist the ccache dir it holds. Not circular: Docker resolves this
# backing dir host-side, and the volume shadows /workspace/build inside
# the container.
- name: Create bind-backed build volume
run: |
mkdir -p build/docker
docker volume create --driver local \
--opt type=none --opt o=bind \
--opt device="$GITHUB_WORKSPACE/build/docker" mx-build
- name: Cache ccache
uses: actions/cache@v4
with:
path: build/docker/.ccache
key: ccache-${{ runner.os }}-test-${{ github.sha }}
restore-keys: ccache-${{ runner.os }}-test-
# corert file counts are pinned by an in-suite counts case (see CoreRoundtripTest.cpp).
- name: All C++ suites (core roundtrip + unit, api mxtest + examples + roundtrip)
run: make test-all
# mx::api cross-compiled to WebAssembly via the pinned mx-sdk emsdk install
# (#386) -- denigma (rpatters1/denigma) depends on this path staying green.
emscripten:
name: emscripten
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: crazy-max/ghaction-github-runtime@v3
- name: Create bind-backed build volume
run: |
mkdir -p build/docker
docker volume create --driver local \
--opt type=none --opt o=bind \
--opt device="$GITHUB_WORKSPACE/build/docker" mx-build
# ccache for mx's own object files; EM_CACHE (below) is Emscripten's
# separate cache for its compiled system libraries.
- name: Cache ccache
uses: actions/cache@v4
with:
path: build/docker/.ccache
key: ccache-${{ runner.os }}-wasm-${{ github.sha }}
restore-keys: ccache-${{ runner.os }}-wasm-
- name: Cache EM_CACHE
uses: actions/cache@v4
with:
path: build/docker/.emcache
key: emcache-${{ runner.os }}-wasm-${{ github.sha }}
restore-keys: emcache-${{ runner.os }}-wasm-
- name: mx::api under Node (wasm)
run: make wasm-test
# The api product surface, built natively with AppleClang. macOS is a
# portability check on the product tier only (the core suites run on Linux in
# test-linux); this is the only C++ job off the pinned toolchain, by design,
# to keep finding AppleClang fallout.
#
# Incremental across runs via ccache (content-addressed, like the mx-sdk image
# bakes in for Linux). This replaces an earlier build/-dir cache keyed on
# hashFiles('src/**') -- that key was busted by every source change, so it gave
# nothing on exactly the PRs that recompile.
test-macos:
runs-on: macos-latest
# No Docker on the macOS runners: MX_RUNNING_IN_DOCKER=1 flips the Makefile
# to its direct-invocation branch so the build runs natively. CMake reads the
# *_COMPILER_LAUNCHER env vars as launcher defaults (the Makefile passes no -D
# launcher flag), the same mechanism the mx-sdk image uses for Linux.
env:
MX_RUNNING_IN_DOCKER: 1
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
steps:
- uses: actions/checkout@v5
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.18
with:
key: ${{ runner.os }}-api
max-size: 500M
- name: api suite (mxtest + examples)
run: make api-test
- name: api corpus round-trip (regression mode)
run: make api-roundtrip
# The api product surface, built natively with the real MSVC toolchain to keep
# finding Windows portability fallout. Windows has no make/Unix tools, so this
# job drives CMake directly.
#
# Generator is Ninja, not the Visual Studio generator: the VS generator ignores
# CMAKE_<LANG>_COMPILER_LAUNCHER, so sccache (the MSVC-capable compiler cache)
# can't hook in under it. Ninja honors the launcher and is faster besides.
# ilammy/msvc-dev-cmd puts cl.exe + the VS-bundled ninja on PATH via vcvars.
# Debug info is forced to Embedded (/Z7, not the default /Zi): sccache can't
# cache the shared-PDB writes /Zi makes, so /Z7 is what lets the object cache
# actually hit. Ninja is single-config, so binaries land in build/api/ (no
# Debug/ subdir).
test-windows:
runs-on: windows-latest
# Git Bash so multi-command run steps stop on first failure (set -e) and
# forward-slash paths + .exe invocation work uniformly.
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v5
# Puts cl.exe, link.exe, and the VS-bundled ninja on PATH -- exported to
# $GITHUB_ENV, so the later bash steps inherit the MSVC environment.
- uses: ilammy/msvc-dev-cmd@v1
- name: sccache
uses: hendrikmuhs/ccache-action@v1.2.18
with:
variant: sccache
key: ${{ runner.os }}-msvc
max-size: 1G
- name: Configure (MSVC, Ninja, MX_API)
run: >-
cmake -S . -B build/api -G Ninja
-DCMAKE_BUILD_TYPE=Debug
-DMX_API=on
-DCMAKE_C_COMPILER=cl
-DCMAKE_CXX_COMPILER=cl
-DCMAKE_C_COMPILER_LAUNCHER=sccache
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache
-DCMAKE_POLICY_DEFAULT_CMP0141=NEW
-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded
- name: Build
run: >-
cmake --build build/api
--target mx mxtest mxtest-api-roundtrip mxread mxwrite mxhide
--parallel
- name: api examples
run: |
build/api/mxread.exe
build/api/mxwrite.exe build/api/example.musicxml
build/api/mxhide.exe
- name: mxtest suite (api/impl/file/control)
run: build/api/mxtest.exe
- name: api corpus round-trip (regression mode)
run: >-
build/api/mxtest-api-roundtrip.exe regression
data src/private/mxtest/api/roundtrip-baseline.txt
# Proves mx compiles as a Swift package under AppleClang/SPM on every PR --
# the path komp takes when it depends on a sibling mx via `.package(path:)`.
# Runs no test suites; the binary xcframework release arm is follow-up work.
#
# SwiftPM has no compiler-launcher hook, so incremental builds come from caching
# its .build tree: llbuild tracks inputs by content signature, so a restored
# tree rebuilds only what changed. The per-commit key suffix makes each run save
# an updated tree while restore-keys pulls the newest prior one.
swift:
runs-on: macos-latest
steps:
- uses: actions/checkout@v5
- name: Cache SwiftPM build
uses: actions/cache@v4
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}-
${{ runner.os }}-spm-
- name: Swift toolchain
run: swift --version
- name: Build the Mx Swift package (local-checkout source mode)
run: swift build --product Mx
# Repo-wide, compiles no C++: the clang-format gate plus a generated-output
# drift check (regenerate every target, then fail on any tracked-file diff).
# Always runs -- both apply to the whole repo, not just gen/.
quality:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: crazy-max/ghaction-github-runtime@v3
- name: Create bind-backed build volume
run: |
mkdir -p build/docker
docker volume create --driver local \
--opt type=none --opt o=bind \
--opt device="$GITHUB_WORKSPACE/build/docker" mx-build
- name: Cache ccache
uses: actions/cache@v4
with:
path: build/docker/.ccache
key: ccache-${{ runner.os }}-quality-${{ github.sha }}
restore-keys: ccache-${{ runner.os }}-quality-
- name: fmt-check (clang-format, all C++ under src/)
run: make fmt-check
# The committed generated output must match the generator: regenerate
# every target, then any tracked-file drift fails the build.
- name: Regenerate all targets
run: make gen
- name: Generated output drift check
run: git diff --exit-code
# Generator + audit gates, gated on gen/ changes (see the `changes` job): the
# generator/audit Python tests, the Go and C corert suites (they exercise the
# generator's emitted targets), and the ratcheted quality/lint gates. Also
# stages the gen-quality report as a PR comment, posted by pr-comment.yaml.
gen:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.gen == 'true'
# Read-only token: this job builds untrusted fork PR code, so it never
# requests write. The gen-quality comment is posted by the pr-comment.yaml
# companion workflow (workflow_run) from the base-repo context instead.
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: crazy-max/ghaction-github-runtime@v3
- name: Create bind-backed build volume
run: |
mkdir -p build/docker
docker volume create --driver local \
--opt type=none --opt o=bind \
--opt device="$GITHUB_WORKSPACE/build/docker" mx-build
- name: Cache ccache
uses: actions/cache@v4
with:
path: build/docker/.ccache
key: ccache-${{ runner.os }}-gen-${{ github.sha }}
restore-keys: ccache-${{ runner.os }}-gen-
- name: gen unit tests (parser/IR/plates/press)
run: make gen-test
- name: audit tool unit tests (incl. failure classifier)
run: make audit-test
- name: Go corert suite
run: make test-go
- name: C corert suite
run: make test-c
# Static-analysis gates on the gen/ generator. Each fails the build below
# its floor (GEN_QUALITY_FLOOR / GEN_LINT_FLOOR in the Makefile).
- name: gen-quality gate
run: make gen-quality
- name: gen-lint gate
run: make gen-lint
# Surface the score in the run summary. always() so a gate failure still
# reports why.
- name: gen-quality report
if: always()
run: |
{
echo '### gen-quality `gen/`'
echo ''
cat data/testOutput/gen-quality/report.md 2>/dev/null || echo '(no report produced)'
} >> "$GITHUB_STEP_SUMMARY"
# Fork PRs run with a read-only token, so this job can't post the comment
# itself. Stage the body + PR number as an artifact; pr-comment.yaml posts
# it after CI completes. always() so a gate failure still reports why.
# Guarded on the report file existing so the comment-sending job gets no
# artifact -- and gracefully posts nothing -- if gen-quality crashed
# before writing it.
- name: Stage gen-quality PR comment
if: always() && github.event_name == 'pull_request'
run: |
if [ ! -f data/testOutput/gen-quality/report.md ]; then
echo 'No gen-quality report to stage (gate produced nothing).'
exit 0
fi
mkdir -p pr-comment
{
echo '### gen-quality `gen/`'
echo ''
cat data/testOutput/gen-quality/report.md
echo ''
echo 'Commit `${{ github.sha }}`.'
} > pr-comment/body.md
echo '${{ github.event.pull_request.number }}' > pr-comment/number
- name: Upload gen-quality PR comment
if: always() && github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: pr-comment-gen
path: pr-comment/
if-no-files-found: ignore