Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 29 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -95,7 +122,8 @@ registry, the lifecycle callbacks, and the CI and release workflows.
<!-- Entries for 0.1.0 through 0.3.0 are summarized retrospectively; this file
was introduced in 0.4.0. -->

[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
Expand Down
25 changes: 24 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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 - "$<TARGET_BUNDLE_DIR:asyncio_dat>"
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"
Expand Down
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`
Expand All @@ -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
Expand Down