From 2ea4ae814df31ff82010a6e8daf90b24f385b7fc Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Tue, 8 Sep 2026 12:03:40 -0700 Subject: [PATCH] Fix the macOS release plugin failing to load The v0.4.0 AsyncioDAT.plugin was reported by TouchDesigner as corrupted. The release runner is macOS 26 on Apple silicon, and nothing in the build pinned a deployment target or an architecture, so the bundle inherited the runner's macOS 26 as its minimum OS and arm64 as its only architecture, while TouchDesigner itself runs on macOS 13 and later, universal. The bundle also carried only the linker's partial ad-hoc signature, which covers the Mach-O but not the bundle, so codesign rejected it. The plugin already linked no Python library (Python3::Module resolves the symbols from the host process), so that part of the AnimationCHOP fix did not apply here. Deployment target is now 13.3 (std::format needs libc++ from 13.3), architectures are arm64 and x86_64, and the bundle is ad-hoc signed as a whole post-build, before the copy to tests/td/Plugins so the copy carries the signature. CI and the release gate now verify all four properties: no Python link, universal, minos 13.3, valid bundle signature. Gatekeeper still refuses a quarantined copy without notarization; the README's install steps already cover clearing the flag. Verified locally from a fresh build directory: the checklist passes on the bundle and on its copy, ctest 16/16, and the TouchDesigner integration suite 25/25 in TouchDesigner 2025.33230. Bumps the version to 0.4.1 and adds the CHANGELOG entry. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ .github/workflows/release.yml | 31 +++++++++++++++++++++++++++++++ CHANGELOG.md | 30 +++++++++++++++++++++++++++++- CMakeLists.txt | 25 ++++++++++++++++++++++++- README.md | 16 +++++++++++++--- 5 files changed, 128 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4adcf02..822df4c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,37 @@ jobs: - name: Build run: cmake --build build --config Release --parallel 2 + # The macOS plugin loads inside TouchDesigner on users' machines, so nothing + # about this runner may leak into it: no link against a Python that only + # exists here, a minimum OS TouchDesigner supports, both architectures, and + # a bundle signature codesign accepts. v0.4.0 shipped arm64-only with a + # minimum of macOS 26 (the runner's) and a linker-only signature. + - name: Verify macOS plugin + if: runner.os == 'macOS' + run: | + status=0 + plugin=build/bin/AsyncioDAT.plugin + bin="$plugin/Contents/MacOS/AsyncioDAT" + echo "== $plugin" + otool -L "$bin" + if otool -L "$bin" | grep -qi python; then + echo "::error::$plugin links a Python library; it must resolve Python from the host process." >&2; status=1 + fi + archs=$(lipo -archs "$bin") + echo "archs: $archs" + case "$archs" in *arm64*x86_64*|*x86_64*arm64*) ;; *) + echo "::error::$plugin is not a universal binary (got: $archs)." >&2; status=1 ;; + esac + minos=$(otool -l "$bin" | awk '/LC_BUILD_VERSION/{f=1} f&&/minos/{print $2; exit}') + echo "minos: $minos" + if [ "$minos" != "13.3" ]; then + echo "::error::$plugin minimum macOS is $minos, expected 13.3." >&2; status=1 + fi + if ! codesign --verify --deep --strict -vv "$plugin"; then + echo "::error::$plugin has no valid bundle signature." >&2; status=1 + fi + exit $status + # tests/cpp (Catch2) and tests/python (pytest) only — the tests/td # integration suite needs a licensed TouchDesigner and a GPU, so it runs # locally via run_td_tests.ps1 / run_td_tests.sh instead. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbdd879..2063583 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -43,6 +43,37 @@ jobs: - name: Build run: cmake --build build --config Release --parallel 2 + # The macOS plugin loads inside TouchDesigner on users' machines, so nothing + # about this runner may leak into it: no link against a Python that only + # exists here, a minimum OS TouchDesigner supports, both architectures, and + # a bundle signature codesign accepts. v0.4.0 shipped arm64-only with a + # minimum of macOS 26 (the runner's) and a linker-only signature. + - name: Verify macOS plugin + if: runner.os == 'macOS' + run: | + status=0 + plugin=build/bin/AsyncioDAT.plugin + bin="$plugin/Contents/MacOS/AsyncioDAT" + echo "== $plugin" + otool -L "$bin" + if otool -L "$bin" | grep -qi python; then + echo "::error::$plugin links a Python library; it must resolve Python from the host process." >&2; status=1 + fi + archs=$(lipo -archs "$bin") + echo "archs: $archs" + case "$archs" in *arm64*x86_64*|*x86_64*arm64*) ;; *) + echo "::error::$plugin is not a universal binary (got: $archs)." >&2; status=1 ;; + esac + minos=$(otool -l "$bin" | awk '/LC_BUILD_VERSION/{f=1} f&&/minos/{print $2; exit}') + echo "minos: $minos" + if [ "$minos" != "13.3" ]; then + echo "::error::$plugin minimum macOS is $minos, expected 13.3." >&2; status=1 + fi + if ! codesign --verify --deep --strict -vv "$plugin"; then + echo "::error::$plugin has no valid bundle signature." >&2; status=1 + fi + exit $status + - name: Install test dependencies run: python -m pip install --upgrade pip -r tests/python/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e0c64d..d089ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,33 @@ While the major version is `0`, breaking changes may land in a minor release. ## [Unreleased] +## [0.4.1] - 2026-09-08 + +Fixes the macOS release build, which TouchDesigner reported as corrupted. + +### Fixed + +- The v0.4.0 `AsyncioDAT.plugin` failed to load on every Mac. The release + runner is macOS 26 on Apple silicon, and nothing in the build pinned a + deployment target or an architecture, so the bundle inherited the runner's + macOS 26 as its minimum OS and arm64 as its only architecture. TouchDesigner + itself runs on macOS 13 and later, universal. The plugin is now built for + `arm64` and `x86_64` with a minimum of macOS 13.3 (the earliest libc++ that + provides the `std::format` the status messages use). +- The bundle carried only the linker's implicit ad-hoc signature, which covers + the Mach-O but not the bundle, so `codesign --verify` rejected it. The whole + bundle is now ad-hoc signed after linking. It is still not notarized, so a + downloaded copy still needs `xattr -dr com.apple.quarantine` (see the README + install steps). +- CI and the release gate now verify the macOS plugin links no Python library, + is universal, has the expected minimum OS, and passes `codesign --verify`, so + this class of binary cannot be published again. + +### Changed + +- README: the macOS build is universal, not arm64 only, and requires macOS 13.3 + or newer. + ## [0.4.0] - 2026-07-26 The release that opens the repository to the public. One crash fix, and the @@ -95,7 +122,8 @@ registry, the lifecycle callbacks, and the CI and release workflows. -[Unreleased]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.4.0...HEAD +[Unreleased]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.4.1...HEAD +[0.4.1]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.2.2...v0.3.0 [0.2.2]: https://github.com/Actualize-Interactive/AsyncioDAT/compare/v0.2.1...v0.2.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c15a94..ead1e12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,16 @@ cmake_minimum_required(VERSION 3.25) -project(AsyncioDAT VERSION 0.4.0 LANGUAGES CXX) + +# macOS: TouchDesigner 2025 runs on macOS 13 and later, on Apple silicon and +# Intel. Without these the plugin inherits the build machine's OS release as its +# minimum and its CPU as its only architecture -- the CI runner is macOS 26 on +# arm64, so v0.4.0 would only ever have loaded on a Mac newer than most users'. +# 13.3 rather than 13.0 because asyncio_dat.cpp formats its status messages +# with std::format, which libc++ only provides from 13.3. They must be set +# before project(), and are ignored on other platforms. +set(CMAKE_OSX_DEPLOYMENT_TARGET "13.3" CACHE STRING "Minimum macOS version the plugin loads on") +set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "macOS architectures to build the plugin for") + +project(AsyncioDAT VERSION 0.4.1 LANGUAGES CXX) set(VERBOSE_STATUS OFF CACHE BOOL "Verbose status messages" FORCE) set(SUPPRESS_NOT_REFERENCED_WARNINGS ON CACHE BOOL "Suppress not referenced warnings" FORCE) @@ -127,6 +138,18 @@ if(SUPPRESS_NOT_REFERENCED_WARNINGS) endif() endif() +if(APPLE) + # The linker's implicit ad-hoc signature covers only the Mach-O, not the + # bundle, so `codesign --verify` rejects the .plugin and Gatekeeper reports + # a downloaded copy as damaged. Sign the whole bundle (still ad-hoc: no + # certificate needed). This runs before the copy to tests/td/Plugins below, + # so the copy carries the signature too. + add_custom_command(TARGET asyncio_dat POST_BUILD + COMMAND codesign --force --sign - "$" + COMMENT "Ad-hoc signing AsyncioDAT.plugin" + ) +endif() + # Add post-build command to copy the library to the Plugins folder add_custom_command(TARGET asyncio_dat POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_SOURCE_DIR}/tests/td/Plugins" diff --git a/README.md b/README.md index 8c09050..5d548b5 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ AsyncioDAT is a C++ TouchDesigner operator that provides a managed asyncio event - **TouchDesigner 2025.32820** or newer. The operator is built against Custom Operator SDK v4 and TouchDesigner's embedded CPython **3.11**. -- **Windows 10/11**, or **macOS on Apple Silicon**. The macOS build is arm64 - only, matching TouchDesigner. +- **Windows 10/11**, or **macOS 13.3 or newer**. The macOS build is universal + (Apple silicon and Intel), like TouchDesigner itself. ## Installation @@ -415,6 +415,14 @@ Python is **not** vendored in this repository — a uv-managed CPython ships the headers and import library CMake needs. The TouchDesigner Custom Operator SDK headers are included under `ext/td/include/` (see [NOTICE](NOTICE)). +On macOS only the headers are used: the plugin links no Python library and +resolves Python symbols from TouchDesigner's own interpreter when it loads, as +any CPython extension module does. The build produces a universal (`arm64` and +`x86_64`) bundle with a minimum of macOS 13.3, ad-hoc signed as a whole so +`codesign --verify` accepts it; `CMAKE_OSX_DEPLOYMENT_TARGET` and +`CMAKE_OSX_ARCHITECTURES` can be overridden on the CMake command line. Because +they are cache variables, change them in a fresh build directory. + ### Build Steps 1. Clone the repository and install Python 3.11: `uv python install 3.11` @@ -436,7 +444,9 @@ headers are included under `ext/td/include/` (see [NOTICE](NOTICE)). The project includes GitHub Actions workflows for: - **CI**: builds and runs the unit suites on Windows and macOS for every push and - pull request to `main` + pull request to `main`. On macOS it also checks that the plugin links no + Python library, is universal, targets macOS 13.3, and has a valid bundle + signature - **Release**: triggered by pushing a `v*.*.*` tag. It builds, tests, and packages on both platforms before publishing anything, and takes the release notes from the matching [CHANGELOG.md](CHANGELOG.md) entry. See