# 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 # .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 .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/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 # "/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 .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 -------------------- # $ is .../.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 "$/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" "$" COMMENT "Rewriting the libobs dependency of ${target} to @rpath" VERBATIM ) endfunction()