From 7529a4043b5b9ee0dd9ab88377f15225cca059b4 Mon Sep 17 00:00:00 2001 From: Zeyu Yang <40936154+zeyuyang42@users.noreply.github.com> Date: Thu, 20 Aug 2026 01:39:21 +0200 Subject: [PATCH] Replace the workflow_run chain with a single same-run build graph The core and PureData workflows were chained with `workflow_run`. That is not merely awkward, it is wrong: `workflow_run` checks out the *default branch* rather than the ref that triggered it, while downloading the core artifact from the triggering run. Dispatching the core build on core_modernization therefore made the PureData workflow compile old main sources against a new libzerr_core.a, failing with undefined reference to zerr::EnvelopeGenerator::EnvelopeGenerator( zerr::SystemConfigs, std::string, std::string) because the constructor had become GenMode-typed. That looked like a real defect and was not. ci.yml puts every job in one run. Wrapper jobs take the core artifact from their own run -- no run-id, no github-token, no "Ensure core artifact id present" guard -- so a cross-commit mix is not representable rather than merely unlikely. Artifacts are keyed by TOOLCHAIN rather than by platform. Windows needs two distinct core builds: MinGW for PureData, and MSVC with static CRT for Max/MSP, whose max-pretarget.cmake forces /MT. A single zerr-core-windows artifact could never express that, so the old structure could not reach the target matrix this project is heading for (Mac/Linux/Windows x PD/Max/JACK/SuperCollider). Adding a platform or a compiler is now one matrix row plus one row in the consumer. Max/MSP gains its first CI job on any platform. macOS only for now -- Max does not exist on Linux, and Windows needs the MSVC/static-CRT path above. Also folded in: - The Windows core build moves off its inline conan settings onto profiles/mingw. Those inline settings omitted compiler.cppstd, compiler.threads and compiler.exception, all of which are package_id inputs, so the core and PureData jobs could resolve *different binary packages* of fftw and yaml-cpp for one logical build -- the single-resolve invariant broken at the CI layer. - A composite setup-deps action replaces the conan block that was duplicated 6 times, and caches ~/.conan2. fftw and yaml-cpp currently build from source on macOS and Windows every run; the Windows job spent most of its 3m41s there. - Path filters dropped. A core change must rebuild every wrapper and a wrapper-only change must still build; the old filters are why a puredata- or maxmsp-only change triggered nothing at all. - concurrency group so superseded runs are cancelled, and checkout v3 -> v4 (v3 targets the deprecated Node 20). deploy-doxygen.yml is untouched: it fails by design pending the organization owner's decision on Pages, and is documented in docs/design/github-pages-deployment.md. Co-Authored-By: Claude Opus 5 (1M context) --- .github/actions/setup-deps/action.yml | 46 ++++ ...d-puredata-plugins-with-pd-lib-builder.yml | 244 ------------------ .../build-zerr-core-static-library.yml | 135 ---------- .github/workflows/ci.yml | 239 +++++++++++++++++ 4 files changed, 285 insertions(+), 379 deletions(-) create mode 100644 .github/actions/setup-deps/action.yml delete mode 100644 .github/workflows/build-puredata-plugins-with-pd-lib-builder.yml delete mode 100644 .github/workflows/build-zerr-core-static-library.yml create mode 100644 .github/workflows/ci.yml diff --git a/.github/actions/setup-deps/action.yml b/.github/actions/setup-deps/action.yml new file mode 100644 index 0000000..b13f021 --- /dev/null +++ b/.github/actions/setup-deps/action.yml @@ -0,0 +1,46 @@ +name: Set up Zerr* dependencies +description: > + Resolve fftw and yaml-cpp once, at the repo root, using a committed profile -- + the same single resolve that build.sh performs locally, so CI and developer + machines share one dependency configuration. + +inputs: + profile: + description: 'Committed conan profile to resolve with, e.g. profiles/macos' + required: true + +runs: + using: composite + steps: + # Keyed on the profile as well as the OS: windows-mingw and windows-msvc are + # both runner.os == Windows but must never share a cache entry, since their + # package_ids differ. Any change to conanfile.txt or to any profile + # invalidates every entry, which is coarse but cheap -- profiles change rarely, + # and a stale hit here would silently reintroduce the mismatched-dependency + # problem the single root resolve exists to prevent. + - name: Cache conan packages + uses: actions/cache@v4 + with: + path: ~/.conan2 + key: conan-${{ runner.os }}-${{ inputs.profile }}-${{ hashFiles('conanfile.txt', 'profiles/*') }} + restore-keys: | + conan-${{ runner.os }}-${{ inputs.profile }}- + + # Pinned below the next major: a bare `pip install conan` would float to + # conan 3.x and break these jobs with no change on our side. + - name: Install conan + shell: bash + run: pip install "conan>=2.13,<3" + + # profiles/macos and profiles/linux are `include(default)`, so the default + # profile has to exist even though every invocation passes an explicit one. + # profiles/mingw is standalone and does not need it. + - name: Detect default profile + shell: bash + run: conan profile detect --force + + - name: Resolve dependencies + shell: bash + run: | + conan install . --output-folder=build --build=missing \ + -pr:h="${{ inputs.profile }}" -pr:b="${{ inputs.profile }}" diff --git a/.github/workflows/build-puredata-plugins-with-pd-lib-builder.yml b/.github/workflows/build-puredata-plugins-with-pd-lib-builder.yml deleted file mode 100644 index 30507cc..0000000 --- a/.github/workflows/build-puredata-plugins-with-pd-lib-builder.yml +++ /dev/null @@ -1,244 +0,0 @@ ---- -name: build pd-lib-builder based pure data externals - -on: - workflow_call: - workflow_dispatch: - inputs: - core_run_id: - description: "Run ID of a successful 'build zerr-core static library' workflow (needed when not triggered via workflow_run)" - required: false - type: string - # push: - # branches: - # - main - # paths: - # - 'puredata/**' # Trigger when Pure Data related code changes - - workflow_run: - workflows: ["build zerr-core static library"] - types: - - completed - -jobs: - macOS: - runs-on: macos-latest - env: - cflags: -mmacosx-version-min=10.13 - steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - submodules: true - - - name: Ensure core artifact id present - env: - CORE_RUN_ID: ${{ github.event.workflow_run.id || inputs.core_run_id }} - run: | - if [ -z "${CORE_RUN_ID}" ]; then - echo "CORE_RUN_ID missing. Provide inputs.core_run_id when dispatching manually or trigger via workflow_run." >&2 - exit 1 - fi - - - name: Download zerr-core library - uses: actions/download-artifact@v4 - with: - name: zerr-core-macos - path: core/lib/ - run-id: ${{ github.event.workflow_run.id || inputs.core_run_id }} - github-token: ${{ github.token }} - - - name: Display structure of downloaded files - run: ls -R core/lib/ - - - name: Install Pure Data - run: brew install --cask pd - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect - - # Resolved at the repo root so this wrapper and zerr_core share one profile; - # puredata/Makefile reads ../build/conandeps.mk by default. - - name: Configure Conan - run: | - conan install . --output-folder=build --build=missing \ - -pr:h=profiles/macos -pr:b=profiles/macos - - - name: Build & Install - working-directory: puredata - run: | - make - make install PDLIBDIR=build - - - name: Upload macOS artifacts - uses: actions/upload-artifact@v4 - with: - name: zerr-pd-macos - path: puredata/build/zerr - linux: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - submodules: true - - - name: Ensure core artifact id present - env: - CORE_RUN_ID: ${{ github.event.workflow_run.id || inputs.core_run_id }} - run: | - if [ -z "${CORE_RUN_ID}" ]; then - echo "CORE_RUN_ID missing. Provide inputs.core_run_id when dispatching manually or trigger via workflow_run." >&2 - exit 1 - fi - - - name: Download zerr-core library - uses: actions/download-artifact@v4 - with: - name: zerr-core-linux - path: core/lib/ - run-id: ${{ github.event.workflow_run.id || inputs.core_run_id }} - github-token: ${{ github.token }} - - - name: Display structure of downloaded files - run: ls -R core/lib/ - - - name: Install Pure Data - run: sudo apt update && sudo apt install puredata-dev - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect - - - name: Configure Conan - run: | - conan install . --output-folder=build --build=missing \ - -pr:h=profiles/linux -pr:b=profiles/linux - - - name: Build & Install - working-directory: puredata - run: | - make - make install PDLIBDIR=build - - - name: Upload Linux artifacts - uses: actions/upload-artifact@v4 - with: - name: zerr-pd-linux - path: puredata/build/zerr - - windows: - runs-on: windows-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - with: - submodules: true - - - name: Ensure core artifact id present - shell: bash - env: - CORE_RUN_ID: ${{ github.event.workflow_run.id || inputs.core_run_id }} - run: | - if [ -z "${CORE_RUN_ID}" ]; then - echo "CORE_RUN_ID missing. Provide inputs.core_run_id when dispatching manually or trigger via workflow_run." >&2 - exit 1 - fi - - - name: Set up MinGW - uses: egor-tensin/setup-mingw@v3 - with: - arch: x64 - - - name: Install Pd - id: pd - shell: pwsh - run: | - $ErrorActionPreference = "Stop" - - $url = "http://msp.ucsd.edu/Software/pd-0.53-2.msw.zip" - $zip = Join-Path $env:RUNNER_TEMP "Pd.zip" - $pdDir = Join-Path $env:ProgramFiles "pd" - - Invoke-WebRequest $url -OutFile $zip - - if (Test-Path $pdDir) { Remove-Item $pdDir -Recurse -Force } - New-Item -ItemType Directory -Path $pdDir | Out-Null - - Expand-Archive -LiteralPath $zip -DestinationPath $pdDir -Force - Remove-Item $zip -Force - - # flatten nested directory - $top = Get-ChildItem $pdDir | Where-Object { $_.PSIsContainer } | Select-Object -First 1 - if ($top) { - Get-ChildItem $top.FullName | Move-Item -Destination $pdDir -Force - Remove-Item $top.FullName -Recurse -Force - } - - $pd = "$pdDir\bin\pd.com" - "PD=$pd" | Out-File -FilePath $env:GITHUB_ENV -Append - "PD=$pd" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - - - name: Download zerr-core library - uses: actions/download-artifact@v4 - with: - name: zerr-core-windows - path: core/lib/ - run-id: ${{ github.event.workflow_run.id || inputs.core_run_id }} - github-token: ${{ github.token }} - - # - name: Set up Conan - # shell: bash - # run: | - # pip install conan - - # conan profile detect --name mingw --force - - # PROFILE_PATH=$(conan profile path default) - - # echo "Default profile path: $PROFILE_PATH" - - # conan profile set settings.os=Windows mingw - # conan profile set settings.arch=x86_64 mingw - # conan profile set settings.compiler=gcc mingw - # conan profile set settings.compiler.version=13 mingw - # conan profile set settings.compiler.libcxx=libstdc++11 mingw - # conan profile set settings.build_type=Release mingw - - # conan profile set conf.tools.build:compiler_executables.c=x86_64-w64-mingw32-gcc mingw - # conan profile set conf.tools.build:compiler_executables.cpp=x86_64-w64-mingw32-g++ mingw - - # conan profile show mingw - - # - name: Install Dependencies - # shell: bash - # working-directory: puredata - # run: | - # mkdir -p build - # conan install . --output-folder=build --build=missing -pr:b=mingw -pr:h=mingw - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect --force - - # mingw-profile.txt moved to profiles/mingw alongside the other profiles. - - name: Install Dependencies - run: | - conan install . -pr:h=profiles/mingw -pr:b=profiles/mingw --output-folder=build --build=missing - - - name: Build External - shell: bash - working-directory: puredata - run: | - mingw32-make - mingw32-make install PDLIBDIR=build - - - name: Upload Windows artifacts - uses: actions/upload-artifact@v4 - with: - name: zerr-pd-windows - path: puredata/build/zerr diff --git a/.github/workflows/build-zerr-core-static-library.yml b/.github/workflows/build-zerr-core-static-library.yml deleted file mode 100644 index 0e471de..0000000 --- a/.github/workflows/build-zerr-core-static-library.yml +++ /dev/null @@ -1,135 +0,0 @@ -name: build zerr-core static library - -on: - workflow_dispatch: - # Deliberately unfiltered, unlike the push trigger below: a pull request should - # be gated on the core still building regardless of which directory it touched. - # Nothing ran on PRs #13-#16 at all, so they merged on manual dispatch alone. - # - # Only this workflow gains the trigger for now. The PureData workflow takes its - # core artifact from `workflow_run`, so on a pull_request event its CORE_RUN_ID - # would be empty and it would fail at its own guard; the Doxygen deploy fails by - # design pending the org owner, and would paint every PR red. Both are addressed - # by the ci.yml restructure that follows this change. - pull_request: - push: - branches: - - main - paths: - - "core/**" - - "conanfile.txt" - - "profiles/**" - -# Dependencies are resolved once at the repo root (see /conanfile.txt) using a -# committed profile, so CI and local builds share one dependency configuration. -# conan is pinned below the next major: a bare `pip install conan` would float to -# conan 3.x and break these jobs with no change on our side. - -jobs: - macOS: - runs-on: macos-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect - - - name: Install Dependencies - run: | - conan install . --output-folder=build --build=missing \ - -pr:h=profiles/macos -pr:b=profiles/macos - - - name: Build Static Library - run: | - cmake -S core -B core/build \ - -DCMAKE_TOOLCHAIN_FILE="$GITHUB_WORKSPACE/build/conan_toolchain.cmake" \ - -DCMAKE_BUILD_TYPE=Release - cmake --build core/build - cmake --install core/build - - - name: Upload zerr-core library - uses: actions/upload-artifact@v4 - with: - name: zerr-core-macos - path: core/lib - retention-days: 3 - - linux: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect - - - name: Install Dependencies - run: | - conan install . --output-folder=build --build=missing \ - -pr:h=profiles/linux -pr:b=profiles/linux - - - name: Build Static Library - run: | - export CXXFLAGS="-fPIC" - cmake -S core -B core/build \ - -DCMAKE_TOOLCHAIN_FILE="$GITHUB_WORKSPACE/build/conan_toolchain.cmake" \ - -DCMAKE_BUILD_TYPE=Release - cmake --build core/build - cmake --install core/build - - - name: Upload zerr-core library - uses: actions/upload-artifact@v4 - with: - name: zerr-core-linux - path: core/lib - retention-days: 3 - - windows: - runs-on: windows-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Set up MinGW - uses: egor-tensin/setup-mingw@v3 - with: - arch: x64 - - - name: Set up Conan - run: | - pip install "conan>=2.13,<3" - conan profile detect --force - - # Deliberately keeps its inline settings rather than -pr:h=profiles/mingw: - # this job builds natively with "MinGW Makefiles" on windows-latest, whereas - # profiles/mingw additionally pins compiler_executables for the - # cross-compiling PureData job. Switching it is untested — see docs/design. - - name: Install Dependencies - run: | - conan install . --output-folder=build --build=missing ` - -s compiler=gcc ` - -s compiler.version=13 ` - -s compiler.libcxx=libstdc++11 ` - -s build_type=Release - - - name: Build Static Library - run: | - cmake -S core -B core/build ` - -G "MinGW Makefiles" ` - -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" ` - -DCMAKE_BUILD_TYPE=Release ` - -DCMAKE_CXX_STANDARD=17 - cmake --build core/build --config Release - cmake --install core/build - - - name: Upload zerr-core library - uses: actions/upload-artifact@v4 - with: - name: zerr-core-windows - path: core/lib - retention-days: 3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4872c91 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,239 @@ +name: CI + +# One run = one commit = one consistent set of artifacts. +# +# This replaces a pair of workflows chained with `workflow_run`. That chaining was +# not merely awkward, it was wrong: `workflow_run` checks out the *default branch* +# rather than the ref that triggered it, while downloading the core artifact from +# the triggering run. Dispatching the core build on a feature branch therefore made +# the PureData workflow link old main sources against a new libzerr_core.a, which +# failed with an undefined reference that looked like a real defect and was not. +# +# Here every wrapper job takes its core artifact from its own run via `needs:`, so +# a cross-commit mix is not representable rather than merely unlikely. + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +# Deliberately no `paths:` filters. A core change must rebuild every wrapper, and +# a wrapper-only change must still build -- the old per-workflow filters are why a +# puredata/- or maxmsp/-only change used to trigger nothing at all. The extra +# minutes are paid back by the conan cache and by cancelling superseded runs. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Artifacts are keyed by TOOLCHAIN, not by platform. Windows needs two distinct + # core builds -- MinGW for PureData, and later MSVC/static-CRT for Max/MSP, whose + # max-pretarget.cmake forces /MT. A single `zerr-core-windows` artifact could + # never express that, which is the structural reason this file exists. + # + # Adding a platform or a compiler is one row here plus one row in the consumer. + core: + name: core (${{ matrix.id }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - id: macos + runner: macos-latest + profile: profiles/macos + - id: linux + runner: ubuntu-latest + profile: profiles/linux + cxxflags: -fPIC + - id: windows-mingw + runner: windows-latest + profile: profiles/mingw + mingw: true + # Without this CMake picks Visual Studio whenever one is installed and + # compiles with MSVC against the gcc/libstdc++ packages profiles/mingw + # resolved -- objects that cannot link together. + cmake_args: -G "MinGW Makefiles" + + steps: + - uses: actions/checkout@v4 + + - name: Set up MinGW + if: matrix.mingw + uses: egor-tensin/setup-mingw@v3 + with: + arch: x64 + + - uses: ./.github/actions/setup-deps + with: + profile: ${{ matrix.profile }} + + # github.workspace yields a mixed-separator path on Windows + # (D:\a\Zerr\Zerr/build/...). CMake normalises it -- verified in run + # 32297202438, which logged `Using Conan toolchain: D:/a/Zerr/Zerr/build/...`. + - name: Build and install + shell: bash + env: + CXXFLAGS: ${{ matrix.cxxflags }} + run: | + cmake -S core -B core/build ${{ matrix.cmake_args }} \ + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" \ + -DCMAKE_BUILD_TYPE=Release + cmake --build core/build --config Release + cmake --install core/build + + - name: Upload core library + uses: actions/upload-artifact@v4 + with: + name: zerr-core-${{ matrix.id }} + path: core/lib + retention-days: 7 + + puredata: + name: puredata (${{ matrix.id }}) + needs: core + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - id: macos + runner: macos-latest + profile: profiles/macos + cflags: -mmacosx-version-min=10.13 + - id: linux + runner: ubuntu-latest + profile: profiles/linux + # The only job that can catch core/wrapper API drift at build time. + # macOS links externals with `-undefined suppress` and Linux permits + # undefined symbols in a .so, so both happily produce an external that + # fails at load; a .dll must resolve every symbol at link time. + - id: windows-mingw + runner: windows-latest + profile: profiles/mingw + mingw: true + make: mingw32-make + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Set up MinGW + if: matrix.mingw + uses: egor-tensin/setup-mingw@v3 + with: + arch: x64 + + # No run-id and no github-token: this resolves to the current run, which is + # what makes the artifact and the checked-out sources the same commit. + - name: Download core library + uses: actions/download-artifact@v4 + with: + name: zerr-core-${{ matrix.id }} + path: core/lib + + - name: Install Pure Data (macOS) + if: matrix.id == 'macos' + run: brew install --cask pd + + - name: Install Pure Data (Linux) + if: matrix.id == 'linux' + run: sudo apt update && sudo apt install -y puredata-dev + + - name: Install Pure Data (Windows) + if: matrix.id == 'windows-mingw' + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + + $url = "http://msp.ucsd.edu/Software/pd-0.53-2.msw.zip" + $zip = Join-Path $env:RUNNER_TEMP "Pd.zip" + $pdDir = Join-Path $env:ProgramFiles "pd" + + Invoke-WebRequest $url -OutFile $zip + + if (Test-Path $pdDir) { Remove-Item $pdDir -Recurse -Force } + New-Item -ItemType Directory -Path $pdDir | Out-Null + + Expand-Archive -LiteralPath $zip -DestinationPath $pdDir -Force + Remove-Item $zip -Force + + # flatten nested directory + $top = Get-ChildItem $pdDir | Where-Object { $_.PSIsContainer } | Select-Object -First 1 + if ($top) { + Get-ChildItem $top.FullName | Move-Item -Destination $pdDir -Force + Remove-Item $top.FullName -Recurse -Force + } + + "PD=$pdDir\bin\pd.com" | Out-File -FilePath $env:GITHUB_ENV -Append + + - uses: ./.github/actions/setup-deps + with: + profile: ${{ matrix.profile }} + + - name: Build and install externals + shell: bash + working-directory: puredata + env: + cflags: ${{ matrix.cflags }} + run: | + ${{ matrix.make || 'make' }} + ${{ matrix.make || 'make' }} install PDLIBDIR=build + + - name: Upload PureData externals + uses: actions/upload-artifact@v4 + with: + name: zerr-pd-${{ matrix.id }} + path: puredata/build/zerr + retention-days: 7 + + # Max/MSP has never had a CI job on any platform. macOS only for now: Max does + # not exist on Linux, and Windows needs an MSVC toolchain with static CRT to + # match max-pretarget.cmake's /MT, which means a profiles/windows-msvc and a + # second Windows core build. That is a follow-up, tracked in the PR body. + maxmsp: + name: maxmsp (${{ matrix.id }}) + needs: core + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - id: macos + runner: macos-latest + profile: profiles/macos + + steps: + # recursive: min-api carries max-sdk-base as a nested submodule, and the + # Max headers live there. + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Download core library + uses: actions/download-artifact@v4 + with: + name: zerr-core-${{ matrix.id }} + path: core/lib + + - uses: ./.github/actions/setup-deps + with: + profile: ${{ matrix.profile }} + + - name: Build externals + shell: bash + run: | + cmake -S maxmsp -B maxmsp/build \ + -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/build/conan_toolchain.cmake" \ + -DCMAKE_BUILD_TYPE=Release + cmake --build maxmsp/build --config Release + + - name: Upload Max/MSP externals + uses: actions/upload-artifact@v4 + with: + name: zerr-max-${{ matrix.id }} + path: maxmsp/externals + retention-days: 7