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
This commit is contained in:
@@ -27,14 +27,86 @@ ctest --test-dir build --output-on-failure
|
|||||||
# This is not purely informational: the macOS bootstrap has been reliably
|
# This is not purely informational: the macOS bootstrap has been reliably
|
||||||
# building the real module in CI (6/6 tests, artifact uploaded -- see
|
# 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
|
# README), so a missing module here is a regression to fail loudly on, not
|
||||||
# the old silent fallback. (This is separate from the macOS packaging gap in
|
# the old silent fallback.
|
||||||
# README -- that the module doesn't yet load as an OBS.app bundle -- which
|
#
|
||||||
# this check does not and cannot test.)
|
# 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
|
ls -la .deps/Frameworks/libobs.framework/Resources/cmake || true
|
||||||
if [ ! -f build/package/bin/streamer-tools-camera.so ]; then
|
|
||||||
echo "::error::no plugin module was built -- build/package/bin/streamer-tools-camera.so is missing. The macOS from-source libobs bootstrap is expected to succeed; treat this as a build failure, not a core-library-only fallback."
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
ls -la build/package/bin
|
if [ ! -f "${plist}" ]; then
|
||||||
otool -L build/package/bin/streamer-tools-camera.so
|
echo "::error::${bundle} exists but has no Contents/Info.plist -- this is not a loadable OBS.app bundle."
|
||||||
otool -L build/package/bin/streamer-tools-camera.so | grep -E 'obs|livekit'
|
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."
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_EXECUTABLE@</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_ID@</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_NAME@</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_VERSION@</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_VERSION@</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>@STPLUGIN_BUNDLE_MIN_OS@</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright (c) @STPLUGIN_BUNDLE_YEAR@ @STPLUGIN_BUNDLE_AUTHOR@</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# cmake/macos/fixup-libobs-rpath.sh <plugin-binary>
|
||||||
|
#
|
||||||
|
# The from-source libobs build this project's macOS bootstrap runs
|
||||||
|
# (cmake/common/buildspec_common.cmake) records its own install name (its
|
||||||
|
# LC_ID_DYLIB) as a relative path -- "libobs/libobs.framework/Versions/A/libobs"
|
||||||
|
# -- rather than an @rpath reference. That is a property of that from-source
|
||||||
|
# OBS build itself, not something this project's own link step controls: the
|
||||||
|
# LC_LOAD_DYLIB entry our plugin binary gets for a dependency is copied
|
||||||
|
# straight from that dependency's own LC_ID_DYLIB by the linker. A relative
|
||||||
|
# path is not resolvable at runtime from inside an OBS.app plugin bundle --
|
||||||
|
# see the "macOS packaging gap" this script fixes, documented in README.md.
|
||||||
|
#
|
||||||
|
# Rewrite that one dependency entry to @rpath/libobs.framework/Versions/A/libobs.
|
||||||
|
# stplugin_macos_finalize_bundle() (cmake/macos/helpers.cmake) gives the
|
||||||
|
# plugin binary an LC_RPATH of @executable_path/../Frameworks, which resolves
|
||||||
|
# @rpath against the *host* OBS.app's own Contents/Frameworks/libobs.framework
|
||||||
|
# at runtime (@executable_path is always relative to the process's main
|
||||||
|
# executable -- OBS.app/Contents/MacOS/obs -- not to this dlopen'd bundle, no
|
||||||
|
# matter how deeply the .plugin is nested under
|
||||||
|
# ~/Library/Application Support/obs-studio/plugins/<name>/bin/).
|
||||||
|
#
|
||||||
|
# The LiveKit runtime dylibs this same helper copies into the bundle's own
|
||||||
|
# Contents/Frameworks are NOT touched here: client-sdk-cpp's own release
|
||||||
|
# build already records their install names as @rpath references (confirmed
|
||||||
|
# by otool -L on prior CI runs -- see README's "Where the macOS bootstrap
|
||||||
|
# actually got to"), so the @loader_path/../Frameworks half of the same
|
||||||
|
# LC_RPATH already resolves them with no rewrite needed.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
binary="$1"
|
||||||
|
|
||||||
|
if [ ! -f "$binary" ]; then
|
||||||
|
echo "fixup-libobs-rpath: no such file: $binary" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_ref=$(otool -L "$binary" \
|
||||||
|
| awk '/libobs\.framework\/Versions\/A\/libobs/ && $1 !~ /^@rpath/ {print $1; exit}')
|
||||||
|
|
||||||
|
if [ -n "${old_ref:-}" ]; then
|
||||||
|
echo "fixup-libobs-rpath: rewriting '$old_ref' -> '@rpath/libobs.framework/Versions/A/libobs' in $binary"
|
||||||
|
install_name_tool -change "$old_ref" "@rpath/libobs.framework/Versions/A/libobs" "$binary"
|
||||||
|
else
|
||||||
|
echo "fixup-libobs-rpath: libobs dependency in $binary is already @rpath-relative (or was not found via otool -L); nothing to rewrite"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# cmake/macos/helpers.cmake
|
||||||
|
#
|
||||||
|
# macOS ".plugin" bundle packaging for the OBS adapter module -- closes the
|
||||||
|
# "macOS packaging gap" documented in README.md.
|
||||||
|
#
|
||||||
|
# Adapted from obsproject/obs-plugintemplate's cmake/macos/helpers.cmake
|
||||||
|
# (fetched 2026-09-07, master branch) -- deliberately NOT vendored as-is.
|
||||||
|
# Upstream's set_target_properties_plugin() builds the bundle almost
|
||||||
|
# entirely through XCODE_ATTRIBUTE_* target properties
|
||||||
|
# (XCODE_ATTRIBUTE_GENERATE_INFOPLIST_FILE for Info.plist, an Xcode "Embed
|
||||||
|
# Frameworks" build phase for bundling its own dependencies, Xcode-driven
|
||||||
|
# codesigning), and upstream's own CI drives that with `xcodebuild -project
|
||||||
|
# <name>.xcodeproj` (.github/scripts/build-macos). None of that is available
|
||||||
|
# here: XCODE_ATTRIBUTE_* properties are silent no-ops under any generator
|
||||||
|
# but Xcode, and this project cannot use the Xcode generator on the `home-mac`
|
||||||
|
# CI runner -- see cmake/common/buildspec_common.cmake's CI-iteration-1
|
||||||
|
# comment, which already established empirically that this runner has only
|
||||||
|
# the Command Line Tools installed, not Xcode.app, so there is no xcodebuild
|
||||||
|
# to drive an .xcodeproj with. This project builds macOS with Ninja
|
||||||
|
# end-to-end (both the from-source libobs bootstrap and this project's own
|
||||||
|
# configure -- see the CI workflow), so this reimplements the same
|
||||||
|
# *outcome* -- a real <name>.plugin bundle with Contents/MacOS,
|
||||||
|
# Contents/Resources and a working Info.plist -- using CMake's own
|
||||||
|
# generator-agnostic bundle support (the BUNDLE / BUNDLE_EXTENSION /
|
||||||
|
# MACOSX_BUNDLE_INFO_PLIST target properties, which the Makefile/Ninja
|
||||||
|
# generators implement natively, not just Xcode's), and does the two things
|
||||||
|
# upstream gets "for free" from Xcode's embed/codesign build phases by hand:
|
||||||
|
#
|
||||||
|
# 0. copies obs-adapter/data/** into Contents/Resources/**, since OBS's own
|
||||||
|
# macOS module search (UI/window-basic-main.cpp AddExtraModulePaths())
|
||||||
|
# passes "<plugin>.plugin/Contents/Resources" as a module's *data path*
|
||||||
|
# -- not a sibling "data/" directory next to "bin/", which is the
|
||||||
|
# Linux/Windows shape and does NOT apply on macOS. Concretely: this
|
||||||
|
# project's OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") loads
|
||||||
|
# "<data_path>/locale/en-US.ini", so on macOS that file must land at
|
||||||
|
# Contents/Resources/locale/en-US.ini, not a top-level data/locale/.
|
||||||
|
# Mirrors upstream's own target_install_resources() -- also not
|
||||||
|
# Xcode-specific, since MACOSX_PACKAGE_LOCATION is another generator-
|
||||||
|
# agnostic bundle-content source-file property.
|
||||||
|
# 1. copies the LiveKit runtime dylibs into Contents/Frameworks (the
|
||||||
|
# conventional location for a plugin bundle's own bundled dependencies)
|
||||||
|
# 2. rewrites the libobs dependency from the relative path the from-source
|
||||||
|
# libobs build records as its own install name
|
||||||
|
# (libobs/libobs.framework/Versions/A/libobs) to
|
||||||
|
# @rpath/libobs.framework/Versions/A/libobs -- see
|
||||||
|
# cmake/macos/fixup-libobs-rpath.sh for why that rewrite is needed at
|
||||||
|
# all and cannot be done by our own link step -- and gives the plugin
|
||||||
|
# binary the LC_RPATH entries that make @rpath resolve for both
|
||||||
|
# dependencies: @loader_path/../Frameworks (this bundle's own
|
||||||
|
# Contents/Frameworks, for LiveKit) and @executable_path/../Frameworks
|
||||||
|
# (OBS.app/Contents/Frameworks, for libobs.framework itself).
|
||||||
|
#
|
||||||
|
# Bundle metadata (name/version/bundle id/author) is sourced from
|
||||||
|
# buildspec.json, the same file the OBS-SDK bootstrap (buildspec_common.cmake)
|
||||||
|
# already reads -- see its "platformConfig.macos.bundleId" / "name" /
|
||||||
|
# "version" / "author" fields.
|
||||||
|
|
||||||
|
include_guard(GLOBAL)
|
||||||
|
|
||||||
|
# stplugin_macos_finalize_bundle: turn a MODULE library target into a real
|
||||||
|
# OBS.app-loadable <name>.plugin bundle. No-op on non-Apple hosts so callers
|
||||||
|
# do not need to guard every call site with if(APPLE).
|
||||||
|
function(stplugin_macos_finalize_bundle target)
|
||||||
|
if(NOT APPLE)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED LIVEKIT_SDK_RUNTIME_LIBS)
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"stplugin_macos_finalize_bundle(${target}): LIVEKIT_SDK_RUNTIME_LIBS is "
|
||||||
|
"not set. include(LiveKitSDK) + livekit_sdk_setup() must run before "
|
||||||
|
"this is called (see top-level CMakeLists.txt).")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- bundle metadata, from buildspec.json ---------------------------------
|
||||||
|
file(READ "${CMAKE_SOURCE_DIR}/buildspec.json" _stplugin_buildspec)
|
||||||
|
string(JSON _stplugin_bundle_name GET "${_stplugin_buildspec}" name)
|
||||||
|
string(JSON _stplugin_bundle_version GET "${_stplugin_buildspec}" version)
|
||||||
|
string(JSON _stplugin_bundle_author GET "${_stplugin_buildspec}" author)
|
||||||
|
string(JSON _stplugin_bundle_id GET "${_stplugin_buildspec}" platformConfig macos bundleId)
|
||||||
|
|
||||||
|
string(TIMESTAMP STPLUGIN_BUNDLE_YEAR "%Y" UTC)
|
||||||
|
set(STPLUGIN_BUNDLE_EXECUTABLE "${target}")
|
||||||
|
set(STPLUGIN_BUNDLE_NAME "${_stplugin_bundle_name}")
|
||||||
|
set(STPLUGIN_BUNDLE_VERSION "${_stplugin_bundle_version}")
|
||||||
|
set(STPLUGIN_BUNDLE_AUTHOR "${_stplugin_bundle_author}")
|
||||||
|
set(STPLUGIN_BUNDLE_ID "${_stplugin_bundle_id}")
|
||||||
|
if(CMAKE_OSX_DEPLOYMENT_TARGET)
|
||||||
|
set(STPLUGIN_BUNDLE_MIN_OS "${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||||
|
else()
|
||||||
|
set(STPLUGIN_BUNDLE_MIN_OS "13.0")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(_stplugin_plist_out "${CMAKE_CURRENT_BINARY_DIR}/${target}-Info.plist")
|
||||||
|
configure_file(
|
||||||
|
"${CMAKE_SOURCE_DIR}/cmake/macos/Info.plist.in"
|
||||||
|
"${_stplugin_plist_out}"
|
||||||
|
@ONLY
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- Contents/Resources: the module's data path on macOS ----------------
|
||||||
|
# See the "0." bullet in the file header. CMAKE_CURRENT_SOURCE_DIR here is
|
||||||
|
# obs-adapter (this function is called from obs-adapter/CMakeLists.txt, and
|
||||||
|
# include() does not change directory scope), so this globs
|
||||||
|
# obs-adapter/data/** the same way the flat-layout staging step elsewhere
|
||||||
|
# in that file does.
|
||||||
|
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data")
|
||||||
|
file(GLOB_RECURSE _stplugin_data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*")
|
||||||
|
foreach(_stplugin_data_file IN LISTS _stplugin_data_files)
|
||||||
|
cmake_path(
|
||||||
|
RELATIVE_PATH _stplugin_data_file
|
||||||
|
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data"
|
||||||
|
OUTPUT_VARIABLE _stplugin_data_relpath
|
||||||
|
)
|
||||||
|
cmake_path(GET _stplugin_data_relpath PARENT_PATH _stplugin_data_reldir)
|
||||||
|
target_sources(${target} PRIVATE "${_stplugin_data_file}")
|
||||||
|
if(_stplugin_data_reldir)
|
||||||
|
set_property(SOURCE "${_stplugin_data_file}" PROPERTY
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources/${_stplugin_data_reldir}")
|
||||||
|
else()
|
||||||
|
set_property(SOURCE "${_stplugin_data_file}" PROPERTY
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# --- bundle shape -----------------------------------------------------
|
||||||
|
# BUNDLE + BUNDLE_EXTENSION is CMake's own generator-agnostic mechanism
|
||||||
|
# (not Xcode-specific) for turning a MODULE library into a CFBundle;
|
||||||
|
# MACOSX_BUNDLE_INFO_PLIST tells the same mechanism which Info.plist to
|
||||||
|
# copy into Contents/. This is the traditional, non-Xcode-only path CMake
|
||||||
|
# has supported since long before XCODE_ATTRIBUTE_GENERATE_INFOPLIST_FILE
|
||||||
|
# existed, and is exactly what makes this work under Ninja.
|
||||||
|
set_target_properties(${target} PROPERTIES
|
||||||
|
BUNDLE TRUE
|
||||||
|
BUNDLE_EXTENSION "plugin"
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST "${_stplugin_plist_out}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- rpaths -------------------------------------------------------------
|
||||||
|
# BUILD_WITH_INSTALL_RPATH ON (matching the non-Apple UNIX branch in
|
||||||
|
# obs-adapter/CMakeLists.txt) means these two rpaths get embedded as
|
||||||
|
# LC_RPATH load commands by the linker itself at build time -- the
|
||||||
|
# artifact that ships is a straight copy of what gets built here, so the
|
||||||
|
# build-tree RPATH must never leak in.
|
||||||
|
# @loader_path/../Frameworks -> this bundle's own Contents/Frameworks
|
||||||
|
# (LiveKit dylibs, staged below)
|
||||||
|
# @executable_path/../Frameworks -> OBS.app/Contents/Frameworks
|
||||||
|
# (libobs.framework itself).
|
||||||
|
# @executable_path is always relative
|
||||||
|
# to the process's main executable
|
||||||
|
# (OBS.app/Contents/MacOS/obs), not to
|
||||||
|
# this dlopen'd bundle, regardless of
|
||||||
|
# how deeply the .plugin is nested
|
||||||
|
# under the user's plugin directory.
|
||||||
|
set_target_properties(${target} PROPERTIES
|
||||||
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
|
INSTALL_RPATH "@loader_path/../Frameworks;@executable_path/../Frameworks"
|
||||||
|
)
|
||||||
|
|
||||||
|
# --- bundled runtime deps + the libobs @rpath fixup --------------------
|
||||||
|
# $<TARGET_BUNDLE_DIR:target> is .../<target>.plugin for a BUNDLE target
|
||||||
|
# (available since CMake 3.0, unlike TARGET_BUNDLE_CONTENT_DIR which needs
|
||||||
|
# CMake >= 3.20 -- this project's cmake_minimum_required(VERSION 3.19)
|
||||||
|
# floor in the top-level CMakeLists.txt), so appending Contents/Frameworks
|
||||||
|
# avoids a ".." in the path TARGET_FILE_DIR would otherwise need.
|
||||||
|
set(_stplugin_frameworks_dir "$<TARGET_BUNDLE_DIR:${target}>/Contents/Frameworks")
|
||||||
|
|
||||||
|
add_custom_command(TARGET ${target} POST_BUILD
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E make_directory "${_stplugin_frameworks_dir}"
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E copy_if_different ${LIVEKIT_SDK_RUNTIME_LIBS} "${_stplugin_frameworks_dir}/"
|
||||||
|
COMMENT "Copying LiveKit runtime dylibs into ${target}.plugin/Contents/Frameworks"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(TARGET ${target} POST_BUILD
|
||||||
|
COMMAND sh "${CMAKE_SOURCE_DIR}/cmake/macos/fixup-libobs-rpath.sh" "$<TARGET_FILE:${target}>"
|
||||||
|
COMMENT "Rewriting the libobs dependency of ${target} to @rpath"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
+57
-18
@@ -42,18 +42,32 @@ set_target_properties(${STPLUGIN_PROJECT_NAME} PROPERTIES
|
|||||||
# straight copy of the built module (see the staging step below), so the
|
# straight copy of the built module (see the staging step below), so the
|
||||||
# build-tree RPATH must never be baked in -- it would work on the build
|
# build-tree RPATH must never be baked in -- it would work on the build
|
||||||
# machine and nowhere else.
|
# machine and nowhere else.
|
||||||
if(APPLE)
|
#
|
||||||
set_target_properties(${STPLUGIN_PROJECT_NAME} PROPERTIES
|
# APPLE's rpath (and its whole bundle shape) is handled by
|
||||||
BUILD_WITH_INSTALL_RPATH ON
|
# stplugin_macos_finalize_bundle() below instead -- a bundle needs two
|
||||||
INSTALL_RPATH "@loader_path"
|
# rpaths (its own Contents/Frameworks *and* the host OBS.app's
|
||||||
)
|
# Contents/Frameworks for libobs.framework), not the single flat
|
||||||
elseif(UNIX)
|
# "@loader_path" this used to set back when macOS staged a bare .so. See
|
||||||
|
# cmake/macos/helpers.cmake.
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
set_target_properties(${STPLUGIN_PROJECT_NAME} PROPERTIES
|
set_target_properties(${STPLUGIN_PROJECT_NAME} PROPERTIES
|
||||||
BUILD_WITH_INSTALL_RPATH ON
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
INSTALL_RPATH "$ORIGIN"
|
INSTALL_RPATH "$ORIGIN"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# --- macOS: turn the flat MODULE library into a real OBS.app-loadable
|
||||||
|
# <name>.plugin bundle (Contents/MacOS, Contents/Resources, Info.plist,
|
||||||
|
# Contents/Frameworks for the bundled LiveKit dylibs, and the libobs
|
||||||
|
# @rpath fixup). Closes the "macOS packaging gap" in README.md. Must run
|
||||||
|
# before the staging step below, which copies the finished bundle directory
|
||||||
|
# -- add_custom_command(POST_BUILD) commands attached to the same target run
|
||||||
|
# in the order they were registered.
|
||||||
|
if(APPLE)
|
||||||
|
include(helpers)
|
||||||
|
stplugin_macos_finalize_bundle(${STPLUGIN_PROJECT_NAME})
|
||||||
|
endif()
|
||||||
|
|
||||||
# --- staged, runnable layout ------------------------------------------------
|
# --- staged, runnable layout ------------------------------------------------
|
||||||
# Everything a human needs to copy into an OBS plugin directory ends up under
|
# Everything a human needs to copy into an OBS plugin directory ends up under
|
||||||
# build/package/, with the LiveKit shared libraries and the licence files
|
# build/package/, with the LiveKit shared libraries and the licence files
|
||||||
@@ -68,17 +82,32 @@ set(STPLUGIN_PACKAGE_DIR "${CMAKE_BINARY_DIR}/package")
|
|||||||
# <config>/obs-studio/plugins/<name>/data/
|
# <config>/obs-studio/plugins/<name>/data/
|
||||||
# so staging into bin/64bit makes build/package/ a straight drop-in.
|
# so staging into bin/64bit makes build/package/ a straight drop-in.
|
||||||
#
|
#
|
||||||
# macOS is NOT this shape -- there OBS looks for a
|
# macOS is a COMPLETELY different shape, not just "bin without 64bit": the
|
||||||
# <name>.plugin/Contents/MacOS bundle -- and this build does not produce one.
|
# same AddExtraModulePaths() passes GetConfigPath(...,
|
||||||
# See the macOS packaging gap in README.md; the flat bin/ here is honest
|
# "obs-studio/plugins/%module%.plugin") as the base, then
|
||||||
# about being unfinished rather than pretending to be installable.
|
# obs_add_module_path(base + "/Contents/MacOS", base + "/Contents/Resources")
|
||||||
|
# -- i.e. OBS wants the ENTIRE <name>.plugin bundle dropped directly into
|
||||||
|
# .../obs-studio/plugins/, not nested under a bin/ subdirectory the way
|
||||||
|
# Linux/Windows are. So build/package/ on macOS holds the bundle itself at
|
||||||
|
# its top level (build/package/<name>.plugin), not build/package/bin/....
|
||||||
|
# stplugin_macos_finalize_bundle() (cmake/macos/helpers.cmake) makes
|
||||||
|
# ${STPLUGIN_PROJECT_NAME} an actual BUNDLE target for APPLE and also copies
|
||||||
|
# obs-adapter/data/** into Contents/Resources/** -- see that file for why
|
||||||
|
# Contents/Resources (not a sibling data/) is where OBS looks for this
|
||||||
|
# module's data on macOS.
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(STPLUGIN_PACKAGE_BIN_DIR "${STPLUGIN_PACKAGE_DIR}/bin")
|
add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${STPLUGIN_PACKAGE_DIR}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${STPLUGIN_PACKAGE_DIR}/${STPLUGIN_PROJECT_NAME}.plugin"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"$<TARGET_BUNDLE_DIR:${STPLUGIN_PROJECT_NAME}>"
|
||||||
|
"${STPLUGIN_PACKAGE_DIR}/${STPLUGIN_PROJECT_NAME}.plugin"
|
||||||
|
COMMENT "Staging the ${STPLUGIN_PROJECT_NAME}.plugin bundle into ${STPLUGIN_PACKAGE_DIR}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
set(STPLUGIN_PACKAGE_BIN_DIR "${STPLUGIN_PACKAGE_DIR}/bin/64bit")
|
set(STPLUGIN_PACKAGE_BIN_DIR "${STPLUGIN_PACKAGE_DIR}/bin/64bit")
|
||||||
endif()
|
add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
||||||
|
|
||||||
add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${STPLUGIN_PACKAGE_BIN_DIR}"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${STPLUGIN_PACKAGE_BIN_DIR}"
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${STPLUGIN_PROJECT_NAME}>" "${STPLUGIN_PACKAGE_BIN_DIR}/"
|
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:${STPLUGIN_PROJECT_NAME}>" "${STPLUGIN_PACKAGE_BIN_DIR}/"
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy ${LIVEKIT_SDK_RUNTIME_LIBS} "${STPLUGIN_PACKAGE_BIN_DIR}/"
|
COMMAND ${CMAKE_COMMAND} -E copy ${LIVEKIT_SDK_RUNTIME_LIBS} "${STPLUGIN_PACKAGE_BIN_DIR}/"
|
||||||
@@ -86,9 +115,19 @@ add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/data/locale/en-US.ini"
|
"${CMAKE_CURRENT_SOURCE_DIR}/data/locale/en-US.ini"
|
||||||
"${STPLUGIN_PACKAGE_DIR}/data/locale/"
|
"${STPLUGIN_PACKAGE_DIR}/data/locale/"
|
||||||
# Redistributing LiveKit's prebuilt binaries means shipping their licence
|
COMMENT "Staging plugin + LiveKit runtime libraries + locale data into ${STPLUGIN_PACKAGE_DIR}"
|
||||||
# and notice with them. See third_party/livekit/README.md -- including
|
VERBATIM
|
||||||
# what upstream does NOT ship, which is an open question, not a solved one.
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Licence files: not something OBS's module loader looks for at all (unlike
|
||||||
|
# the locale data above), so these always land in the same top-level spot
|
||||||
|
# regardless of platform -- next to the bundle on macOS, next to bin/ on
|
||||||
|
# Linux/Windows. Redistributing LiveKit's prebuilt binaries means shipping
|
||||||
|
# their licence and notice with them; see third_party/livekit/README.md,
|
||||||
|
# including what upstream does NOT ship, which is an open question, not a
|
||||||
|
# solved one.
|
||||||
|
add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${STPLUGIN_PACKAGE_DIR}/licenses/livekit"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${STPLUGIN_PACKAGE_DIR}/licenses/livekit"
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
"${CMAKE_SOURCE_DIR}/third_party/livekit/LICENSE"
|
"${CMAKE_SOURCE_DIR}/third_party/livekit/LICENSE"
|
||||||
@@ -98,6 +137,6 @@ add_custom_command(TARGET ${STPLUGIN_PROJECT_NAME} POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
"${CMAKE_SOURCE_DIR}/LICENSE"
|
"${CMAKE_SOURCE_DIR}/LICENSE"
|
||||||
"${STPLUGIN_PACKAGE_DIR}/licenses/"
|
"${STPLUGIN_PACKAGE_DIR}/licenses/"
|
||||||
COMMENT "Staging plugin + LiveKit runtime libraries + licences into ${STPLUGIN_PACKAGE_DIR}"
|
COMMENT "Staging licences into ${STPLUGIN_PACKAGE_DIR}"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user