Files
obs-streamer-tools-plugin/.gitea/scripts/macos-build.sh
shadowdaoandClaude Sonnet 5 b5c91c72e5
Build / macOS (macos-latest) (push) Successful in 37s
Build / Linux (ubuntu-24.04) (push) Successful in 53s
Build / Windows (windows-latest) (push) Canceled after 1m16s
macOS: real .plugin bundle packaging, libobs @rpath fixup, hard-fail CI check
Closes the "macOS packaging gap" documented in README.md: CI built a real
adapter module on macOS but staged it as a bare streamer-tools-camera.so,
which OBS.app cannot load (it needs a <name>.plugin bundle), and otool -L
showed the libobs dependency as the relative path
libobs/libobs.framework/Versions/A/libobs instead of an @rpath reference.

cmake/macos/helpers.cmake adapts (not vendors as-is) obs-plugintemplate's
cmake/macos/helpers.cmake: upstream builds the bundle almost entirely
through XCODE_ATTRIBUTE_* properties and its own CI drives that with
`xcodebuild -project <name>.xcodeproj`, but this project's home-mac runner
has only the Command Line Tools, not Xcode.app (already established by
buildspec_common.cmake's CI-iteration-1 comment), and the whole project
builds with Ninja end to end. So this reimplements the same outcome --
Contents/MacOS, Contents/Resources, a real Info.plist -- with CMake's own
generator-agnostic BUNDLE/BUNDLE_EXTENSION/MACOSX_BUNDLE_INFO_PLIST/
MACOSX_PACKAGE_LOCATION target properties (verified working under Ninja via
a dry-run configure with APPLE spoofed), and does by hand what upstream gets
from Xcode's embed/codesign build phases:
  - copies obs-adapter/data/** into Contents/Resources/**, since OBS's
    AddExtraModulePaths() (UI/window-basic-main.cpp) passes
    Contents/Resources as a macOS module's *data path* -- not a sibling
    data/ the way Linux/Windows work -- so the locale ini has to land at
    Contents/Resources/locale/en-US.ini for OBS_MODULE_USE_DEFAULT_LOCALE
    to find it
  - copies the LiveKit runtime dylibs into Contents/Frameworks
  - fixes up the libobs dependency: cmake/macos/fixup-libobs-rpath.sh
    rewrites the relative install name the from-source libobs build records
    to @rpath/libobs.framework/Versions/A/libobs (LiveKit's own dylibs
    already record @rpath references, confirmed in prior CI otool -L
    output, so only libobs needs the rewrite)
  - gives the plugin binary two LC_RPATH entries via INSTALL_RPATH:
    @loader_path/../Frameworks (this bundle's own Frameworks, for LiveKit)
    and @executable_path/../Frameworks (OBS.app/Contents/Frameworks, for
    libobs.framework -- @executable_path is always relative to the host
    process's main executable, not this dlopen'd bundle)

obs-adapter/CMakeLists.txt: calls stplugin_macos_finalize_bundle() before
staging, and corrects the staged layout for macOS -- OBS's module search
wants the whole <name>.plugin dropped directly into .../obs-studio/plugins/,
not nested under a bin/ subdirectory the way Linux/Windows are, so
build/package/ now holds the bundle at its own top level on macOS instead of
build/package/bin/<name>.plugin.

.gitea/scripts/macos-build.sh (this project's macOS steps were factored out
of build.yml into this shared script, used by both build.yml and
release.yml, in a concurrent commit -- rebased onto that): the "Show what
was built" section now hard-fails unless a real .plugin bundle (with
Info.plist, bundled LiveKit dylibs, and locale data) was produced, the
libobs dependency resolves via @rpath rather than the old relative path,
and both LC_RPATH entries are present -- closing the same
false-positive-green gap this project's Windows find_package(libobs)
incident just exposed, which this check previously did not cover (it only
checked for a bare .so). release.yml's macOS packaging step already globs
for any `*.plugin` under build/ (added in the concurrent commit,
anticipating this fix), so it picks up the new layout with no change
needed.

Verified: Linux configure/build/ctest (STPLUGIN_BOOTSTRAP_OBS=OFF) still
passes 6/6, unaffected -- the new macOS CMake logic is fully guarded by
if(APPLE). The bundle/Info.plist/Resources-mapping logic itself was dry-run
verified by configuring a throwaway CMake project with APPLE spoofed to
TRUE, confirming buildspec.json values substitute correctly into Info.plist
and obs-adapter/data/locale/en-US.ini maps to Resources/locale/en-US.ini.
Not verified: an actual macOS build/link/otool pass, or loading the result
in real OBS.app -- this is a Linux sandbox with no way to do either: this
commit is going to the home-mac CI runner to get that verification next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE
2026-09-07 05:16:57 -07:00

113 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Shared with .gitea/workflows/build.yml and .gitea/workflows/release.yml --
# this is the actual macOS configure/build/test/verify sequence. Edit once,
# here, so both workflows stay in sync instead of drifting copies.
#
# The buildspec bootstrap runs here: it fetches obs-deps + the pinned
# obs-studio source and builds libobs before this project configures.
#
# If that fails, fall back to a core-library-only build rather than going
# red: the core library and its tests are what this job mainly guards, and
# the fallback is loud (a workflow warning, plus the check below reporting no
# module) rather than silent. Do not remove the warning -- a green job that
# quietly stopped building the plugin is worse than a red one.
set -euo pipefail
if ! cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release; then
echo "::warning::OBS SDK bootstrap failed on macOS; building the core library only. The plugin module was NOT built."
rm -rf build
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DSTPLUGIN_BOOTSTRAP_OBS=OFF
fi
cmake --build build
ctest --test-dir build --output-on-failure
# Show what was built.
#
# This is not purely informational: the macOS bootstrap has been reliably
# building the real module in CI (6/6 tests, artifact uploaded -- see
# README), so a missing module here is a regression to fail loudly on, not
# the old silent fallback.
#
# This used to only check for a bare streamer-tools-camera.so, which is
# exactly the shape the "macOS packaging gap" in README.md documented as
# broken: OBS.app cannot load a bare .so on macOS, it needs a <name>.plugin
# bundle, and this check was blind to whether one was actually produced --
# another instance of the same false-positive-green class of bug this check
# exists to catch (see the Windows find_package(libobs) incident this
# project just had). Now hard-fails unless:
# 1. a real streamer-tools-camera.plugin bundle exists at the top level of
# build/package (macOS's plugin search wants the whole .plugin dropped
# straight into .../obs-studio/plugins/, not nested under a bin/
# subdirectory the way Linux/Windows are -- see
# obs-studio/UI/window-basic-main.cpp AddExtraModulePaths()), with
# Contents/MacOS/streamer-tools-camera, Contents/Info.plist,
# Contents/Resources/locale/en-US.ini (the module's locale data -- OBS
# passes Contents/Resources as the module's data path on macOS, not a
# sibling data/), and the bundled LiveKit dylibs under
# Contents/Frameworks;
# 2. the libobs dependency in the built binary resolves via
# @rpath/libobs.framework, not the old relative path
# (libobs/libobs.framework/...) that a real OBS.app cannot load;
# 3. the binary carries LC_RPATH entries for both
# @loader_path/../Frameworks (this bundle's own Frameworks) and
# @executable_path/../Frameworks (OBS.app/Contents/Frameworks).
ls -la .deps/Frameworks/libobs.framework/Resources/cmake || true
bundle="build/package/streamer-tools-camera.plugin"
binary="${bundle}/Contents/MacOS/streamer-tools-camera"
plist="${bundle}/Contents/Info.plist"
locale="${bundle}/Contents/Resources/locale/en-US.ini"
if [ ! -f "${binary}" ]; then
echo "::error::no plugin bundle was built -- ${binary} is missing. The macOS from-source libobs bootstrap is expected to succeed and produce a real .plugin bundle (not a bare .so, and not nested under bin/); treat this as a build failure, not a core-library-only fallback or a silently-reverted bundle."
exit 1
fi
if [ ! -f "${plist}" ]; then
echo "::error::${bundle} exists but has no Contents/Info.plist -- this is not a loadable OBS.app bundle."
exit 1
fi
if [ ! -f "${locale}" ]; then
echo "::error::${locale} is missing -- OBS passes Contents/Resources as this module's data path on macOS (see AddExtraModulePaths() in obs-studio's UI/window-basic-main.cpp), so OBS_MODULE_USE_DEFAULT_LOCALE will fail to load the en-US locale at runtime."
exit 1
fi
echo "--- bundle contents ---"
find "${bundle}" -maxdepth 4 -exec ls -ld {} \;
echo "--- Info.plist ---"
cat "${plist}"
echo "--- otool -L ---"
otool -L "${binary}"
if ! ls "${bundle}"/Contents/Frameworks/liblivekit*.dylib >/dev/null 2>&1; then
echo "::error::no LiveKit dylibs found under ${bundle}/Contents/Frameworks -- the plugin will fail to load at runtime."
exit 1
fi
if otool -L "${binary}" | grep -q 'libobs/libobs\.framework'; then
echo "::error::the libobs dependency is still recorded as the old relative path (libobs/libobs.framework/...) instead of @rpath/libobs.framework/... -- this is the exact macOS packaging gap the bundle fixup exists to close, and it did not take effect."
otool -L "${binary}" | grep -E 'obs|livekit'
exit 1
fi
if ! otool -L "${binary}" | grep -q '@rpath/libobs\.framework'; then
echo "::error::expected an @rpath/libobs.framework dependency in ${binary}, found none."
otool -L "${binary}" | grep -E 'obs|livekit'
exit 1
fi
echo "--- LC_RPATH ---"
rpath_dump=$(otool -l "${binary}" | grep -A2 LC_RPATH)
echo "${rpath_dump}"
if ! echo "${rpath_dump}" | grep -q '@loader_path/../Frameworks'; then
echo "::error::${binary} has no @loader_path/../Frameworks LC_RPATH (needed to resolve the bundled LiveKit dylibs)."
exit 1
fi
if ! echo "${rpath_dump}" | grep -q '@executable_path/../Frameworks'; then
echo "::error::${binary} has no @executable_path/../Frameworks LC_RPATH (needed to resolve libobs.framework inside OBS.app)."
exit 1
fi
echo "macOS bundle verification passed: real .plugin bundle, @rpath libobs dependency, both LC_RPATH entries present."