Files
obs-streamer-tools-plugin/cmake/common/buildspec_common.cmake
T
shadowdaoandClaude Sonnet 5 edb0c02be2
Build / macOS (macos-latest) (push) Failing after 23s
Build / Linux (ubuntu-24.04) (push) Successful in 56s
Build / Windows (windows-latest) (push) Failing after 9m22s
ci: restore the full OBS install, and stop passing a Windows SDK to -A
Two more findings from the three-platform run, both from reading the actual
CI logs rather than guessing.

macOS. libobs now builds, installs, and is FOUND by find_package -- and then
every consumer fails with:

  IMPORTED_LOCATION or IMPORTED_IMPLIB not set for imported target
  "OBS::libobs" configuration "Release".

Restricting --install to the libobs subdirectory (the previous commit's fix
for the obs-frontend-api install error) also loses the per-configuration
export file, so libobsTargets.cmake lands without its
libobsTargets-release.cmake sibling and the imported target has no location
for any configuration. The install therefore goes back to the whole build
tree, exactly as upstream does, with its exit code tolerated: it gets all the
way through libobs and only then trips over the install rule of a target this
build deliberately skips. If libobs genuinely did not install,
find_package(libobs) in the top-level CMakeLists is where that surfaces, with
a far better message than a half-installed tree.

Belt and braces, the top-level CMakeLists also picks up
obs-plugintemplate's CMAKE_MAP_IMPORTED_CONFIG_* fallbacks, so an imported
target exported under a different configuration name still resolves.

Windows. The sub-configure was re-entering obs-studio's OWN dependency
downloader with a corrupted architecture:

  string sub-command JSON member 'hashes windows-x64,version=10.0.26100.0'
    not found
  Unable to download .../windows-deps-2023-11-03-x64,version=10.0.26100.0.zip

Upstream passes "-A x64,version=<Windows SDK>", and with a current CMake that
",version=" suffix comes back out verbatim in the sub-build's
CMAKE_VS_PLATFORM_NAME -- which obs-studio keys its release assets off. Plain
"-A x64" now. The Windows SDK is selected automatically anyway ("Selecting
Windows SDK version 10.0.26100.0" in the same log), and CMAKE_SYSTEM_VERSION
is passed explicitly.

The macOS job also lists the installed libobs export directory, so the next
run answers "which target files actually landed" from CI output instead of
inference.

Linux remains green and unaffected: ctest 6/6.

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

371 lines
15 KiB
CMake

# Adapted from obsproject/obs-plugintemplate (cmake/common/buildspec_common.cmake,
# master as of 2026-09-06). Deliberate changes from upstream, all recorded here
# so a future re-sync knows what to keep:
#
# 1. _setup_obs_studio builds and installs the `libobs` target, not
# `obs-frontend-api`. This plugin's properties UI is plain
# obs_properties_* and it never touches the frontend API, so building it
# would only drag Qt back in -- which is the whole point of dropping qt6
# from dependencies_list.
# 2. It passes -DENABLE_UI:BOOL=OFF and -DENABLE_SCRIPTING:BOOL=OFF as well
# as upstream's -DENABLE_FRONTEND:BOOL=OFF. The pinned OBS (30.0.2)
# predates the ENABLE_FRONTEND option and gates its Qt-dependent UI on
# ENABLE_UI instead; without this the sub-build configures the whole
# OBS UI and demands Qt.
# 3. Only the Release configuration is built and installed. Upstream builds
# Debug as well; nothing here consumes a debug libobs, and it doubles the
# slowest step in CI.
# 4. macOS uses the Ninja generator and a single architecture, not upstream's
# Xcode generator and forced universal build. See the comment at that
# branch: a runner with only the Command Line Tools has no xcodebuild.
# 5. Generator flags are built as CMake lists so each becomes its own argv
# entry, rather than upstream's space-separated strings passed unquoted.
# 6. The install step targets the libobs subdirectory rather than the whole
# build tree, so it does not trip over the install rules of targets that
# were deliberately never built.
# 7. _resolve_versioned_macos_sdk exists at all -- see its own comment.
#
include_guard(GLOBAL)
# _check_deps_version: Checks for obs-deps VERSION file in prefix paths
function(_check_deps_version version)
set(found FALSE)
foreach(path IN LISTS CMAKE_PREFIX_PATH)
if(EXISTS "${path}/share/obs-deps/VERSION")
if(dependency STREQUAL qt6 AND NOT EXISTS "${path}/lib/cmake/Qt6/Qt6Config.cmake")
set(found FALSE)
continue()
endif()
file(READ "${path}/share/obs-deps/VERSION" _check_version)
string(REPLACE "\n" "" _check_version "${_check_version}")
string(REPLACE "-" "." _check_version "${_check_version}")
string(REPLACE "-" "." version "${version}")
if(_check_version VERSION_EQUAL version)
set(found TRUE)
break()
elseif(_check_version VERSION_LESS version)
message(
AUTHOR_WARNING
"Older ${label} version detected in ${path}: \n"
"Found ${_check_version}, require ${version}"
)
list(REMOVE_ITEM CMAKE_PREFIX_PATH "${path}")
list(APPEND CMAKE_PREFIX_PATH "${path}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH})
continue()
else()
message(
AUTHOR_WARNING
"Newer ${label} version detected in ${path}: \n"
"Found ${_check_version}, require ${version}"
)
set(found TRUE)
break()
endif()
endif()
endforeach()
return(PROPAGATE found CMAKE_PREFIX_PATH)
endfunction()
# _resolve_versioned_macos_sdk: return an SDK path that satisfies OBS's own
# macOS SDK version check.
#
# Not upstream. OBS 30.0.2's cmake/macos/compilerconfig.cmake reads the SDK
# version straight out of CMAKE_OSX_SYSROOT with this regex:
#
# ".+/MacOSX.platform/Developer/SDKs/MacOSX([0-9]+\\.[0-9])+\\.sdk$"
#
# and hard-fails if it does not match ("Your macOS SDK version () is too low",
# with an empty version, which is the tell). That pattern only ever matches a
# full-Xcode SDK path; a Command-Line-Tools-only install has its SDK at
# /Library/Developer/CommandLineTools/SDKs/MacOSX<ver>.sdk, with no
# MacOSX.platform/Developer/SDKs segment at all, and can never match.
#
# Upstream never hits this because it uses the Xcode generator, where
# CMAKE_OSX_SYSROOT stays the literal string "macosx" and Xcode resolves it
# late. This project uses Ninja (the CI runner has no xcodebuild), so CMake
# resolves the sysroot eagerly and the regex runs against a real path.
#
# So: if the toolchain's own SDK path already matches, use it untouched.
# Otherwise build a symlink tree under the build directory whose shape matches
# the regex and which points at exactly the same SDK. Nothing about the
# compilation changes -- only the spelling of the path, which is all the check
# reads.
function(_resolve_versioned_macos_sdk out_path)
set(${out_path} "" PARENT_SCOPE)
execute_process(
COMMAND xcrun --show-sdk-path
OUTPUT_VARIABLE _sdk
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _rc
ERROR_QUIET
)
if(NOT _rc EQUAL 0 OR NOT _sdk)
message(WARNING "Could not determine the macOS SDK path via xcrun; leaving CMAKE_OSX_SYSROOT alone.")
return()
endif()
if(_sdk MATCHES "/MacOSX\\.platform/Developer/SDKs/MacOSX[0-9]+\\.[0-9]+\\.sdk$")
set(${out_path} "${_sdk}" PARENT_SCOPE)
return()
endif()
execute_process(
COMMAND xcrun --show-sdk-version
OUTPUT_VARIABLE _sdk_version
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _rc
ERROR_QUIET
)
if(NOT _rc EQUAL 0 OR NOT _sdk_version MATCHES "^([0-9]+)\\.([0-9]+)")
message(WARNING "Could not determine the macOS SDK version via xcrun (got '${_sdk_version}').")
return()
endif()
set(_short "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}")
# Deliberately under the BUILD directory, not .deps/: cmake/macos/
# buildspec.cmake runs `xattr -r -d com.apple.quarantine` over the whole
# dependency directory, which would follow this symlink into the read-only
# system SDK and fail with "Permission denied".
set(_link_dir "${CMAKE_BINARY_DIR}/macos-sdk/MacOSX.platform/Developer/SDKs")
set(_link "${_link_dir}/MacOSX${_short}.sdk")
file(MAKE_DIRECTORY "${_link_dir}")
if(NOT EXISTS "${_link}")
file(CREATE_LINK "${_sdk}" "${_link}" SYMBOLIC)
endif()
if(EXISTS "${_link}")
message(STATUS "macOS SDK ${_short} at ${_sdk}; presenting it to OBS as ${_link}")
set(${out_path} "${_link}" PARENT_SCOPE)
else()
message(WARNING "Could not create the versioned macOS SDK symlink at ${_link}.")
endif()
endfunction()
# _setup_obs_studio: Create obs-studio build project, then build libobs and obs-frontend-api
function(_setup_obs_studio)
if(NOT libobs_DIR)
set(_is_fresh --fresh)
endif()
# Every generator-specific flag is built as a proper CMake list, so each
# element becomes its own argv entry. Upstream packs several flags into one
# space-separated string and passes it unquoted, which execute_process hands
# to cmake as a single argument -- it happens not to matter there because
# those flags are optional, but -DCMAKE_BUILD_TYPE is not.
set(_cmake_arch "")
set(_cmake_extra "")
if(OS_WINDOWS)
set(_cmake_generator "${CMAKE_GENERATOR}")
# Plain "-A x64", NOT upstream's "-A x64,version=<Windows SDK>". With a
# current CMake the ",version=" suffix comes back out verbatim in the
# sub-build's CMAKE_VS_PLATFORM_NAME, and obs-studio's OWN dependency
# downloader keys its release assets off that value -- so the sub-configure
# goes looking for a file that cannot exist:
#
# string sub-command JSON member 'hashes windows-x64,version=10.0.26100.0'
# not found
# Unable to download .../windows-deps-2023-11-03-x64,version=10.0.26100.0.zip
#
# The Windows SDK is selected automatically anyway ("Selecting Windows SDK
# version 10.0.26100.0"), and CMAKE_SYSTEM_VERSION is passed below.
list(APPEND _cmake_arch -A "${arch}")
list(APPEND _cmake_extra "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}")
elseif(OS_MACOS)
# Ninja, not upstream's Xcode generator. A runner with only the Command
# Line Tools installed has no xcodebuild, and the Xcode generator then
# fails the OBS sub-configure outright with "No CMAKE_C_COMPILER could be
# found" -- observed on the `home-mac` CI runner. Ninja is single-config,
# hence the explicit CMAKE_BUILD_TYPE below.
set(_cmake_generator "Ninja")
# Single-architecture, not upstream's forced universal build: this plugin
# is built for one architecture anyway (client-sdk-cpp ships single-arch
# dylibs), so building libobs universal would double the slowest step in
# CI for a slice nothing links against.
if(CMAKE_OSX_ARCHITECTURES)
list(APPEND _cmake_arch "-DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES}")
endif()
list(APPEND _cmake_extra "-DCMAKE_BUILD_TYPE=Release")
if(CMAKE_OSX_DEPLOYMENT_TARGET)
list(APPEND _cmake_extra "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
_resolve_versioned_macos_sdk(_sdk_path)
if(_sdk_path)
list(APPEND _cmake_extra "-DCMAKE_OSX_SYSROOT=${_sdk_path}")
endif()
endif()
message(STATUS "Configure ${label} (${arch})")
execute_process(
COMMAND
"${CMAKE_COMMAND}" -S "${dependencies_dir}/${_obs_destination}" -B
"${dependencies_dir}/${_obs_destination}/build_${arch}" -G ${_cmake_generator} ${_cmake_arch}
-DOBS_CMAKE_VERSION:STRING=3.0.0 -DENABLE_PLUGINS:BOOL=OFF -DENABLE_FRONTEND:BOOL=OFF
-DENABLE_UI:BOOL=OFF -DENABLE_SCRIPTING:BOOL=OFF -DENABLE_BROWSER:BOOL=OFF
-DOBS_VERSION_OVERRIDE:STRING=${_obs_version} "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" ${_is_fresh}
${_cmake_extra}
RESULT_VARIABLE _process_result
COMMAND_ERROR_IS_FATAL ANY
)
message(STATUS "Configure ${label} (${arch}) - done")
message(STATUS "Build ${label} (Release - ${arch})")
execute_process(
COMMAND "${CMAKE_COMMAND}" --build build_${arch} --target libobs --config Release --parallel
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _process_result
COMMAND_ERROR_IS_FATAL ANY
)
message(STATUS "Build ${label} (Release - ${arch}) - done")
# Install the whole build tree, and tolerate a non-zero exit.
#
# Installing from the top-level build directory walks every subproject's
# cmake_install.cmake, including UI/obs-frontend-api's, which fails on a
# binary this build deliberately never produced (that is the whole point of
# dropping Qt):
#
# file INSTALL cannot find ".../obs-frontend-api.dylib": No such file
#
# Restricting --install to the libobs subdirectory avoids that error, but
# then the per-configuration export file (libobsTargets-release.cmake) never
# lands, and every consumer fails with "IMPORTED_LOCATION or IMPORTED_IMPLIB
# not set for imported target OBS::libobs configuration Release". So run the
# full install exactly as upstream does, let it get through libobs, and
# ignore the error it hits afterwards. If libobs really did not install, the
# find_package(libobs) in the top-level CMakeLists is where that surfaces --
# loudly, and with a far more useful message than a half-installed tree.
message(STATUS "Install ${label} (${arch})")
execute_process(
COMMAND
"${CMAKE_COMMAND}" --install build_${arch} --component Development --config Release --prefix "${dependencies_dir}"
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _install_result
OUTPUT_QUIET
)
if(NOT _install_result EQUAL 0)
message(
STATUS
"Install ${label} (${arch}) reported errors; expected, because install rules exist for "
"targets this build skips. Continuing -- find_package(libobs) is the real check."
)
endif()
message(STATUS "Install ${label} (${arch}) - done")
endfunction()
# _check_dependencies: Fetch and extract pre-built OBS build dependencies
function(_check_dependencies)
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec)
string(JSON dependency_data GET ${buildspec} dependencies)
foreach(dependency IN LISTS dependencies_list)
string(JSON data GET ${dependency_data} ${dependency})
string(JSON version GET ${data} version)
string(JSON hash GET ${data} hashes ${platform})
string(JSON url GET ${data} baseUrl)
string(JSON label GET ${data} label)
string(JSON revision ERROR_VARIABLE error GET ${data} revision ${platform})
message(STATUS "Setting up ${label} (${arch})")
set(file "${${dependency}_filename}")
set(destination "${${dependency}_destination}")
string(REPLACE "VERSION" "${version}" file "${file}")
string(REPLACE "VERSION" "${version}" destination "${destination}")
string(REPLACE "ARCH" "${arch}" file "${file}")
string(REPLACE "ARCH" "${arch}" destination "${destination}")
if(revision)
string(REPLACE "_REVISION" "_v${revision}" file "${file}")
string(REPLACE "-REVISION" "-v${revision}" file "${file}")
else()
string(REPLACE "_REVISION" "" file "${file}")
string(REPLACE "-REVISION" "" file "${file}")
endif()
if(EXISTS "${dependencies_dir}/.dependency_${dependency}_${arch}.sha256")
file(
READ
"${dependencies_dir}/.dependency_${dependency}_${arch}.sha256"
OBS_DEPENDENCY_${dependency}_${arch}_HASH
)
endif()
set(skip FALSE)
if(dependency STREQUAL prebuilt OR dependency STREQUAL qt6)
if(OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash})
_check_deps_version(${version})
if(found)
set(skip TRUE)
endif()
endif()
endif()
if(skip)
message(STATUS "Setting up ${label} (${arch}) - skipped")
continue()
endif()
if(dependency STREQUAL obs-studio)
set(url ${url}/${file})
else()
set(url ${url}/${version}/${file})
endif()
if(NOT EXISTS "${dependencies_dir}/${file}")
message(STATUS "Downloading ${url}")
file(DOWNLOAD "${url}" "${dependencies_dir}/${file}" STATUS download_status EXPECTED_HASH SHA256=${hash})
list(GET download_status 0 error_code)
list(GET download_status 1 error_message)
if(error_code GREATER 0)
message(STATUS "Downloading ${url} - Failure")
message(FATAL_ERROR "Unable to download ${url}, failed with error: ${error_message}")
file(REMOVE "${dependencies_dir}/${file}")
else()
message(STATUS "Downloading ${url} - done")
endif()
endif()
if(NOT OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash})
file(REMOVE_RECURSE "${dependencies_dir}/${destination}")
endif()
if(NOT EXISTS "${dependencies_dir}/${destination}")
file(MAKE_DIRECTORY "${dependencies_dir}/${destination}")
if(dependency STREQUAL obs-studio)
file(ARCHIVE_EXTRACT INPUT "${dependencies_dir}/${file}" DESTINATION "${dependencies_dir}")
else()
file(ARCHIVE_EXTRACT INPUT "${dependencies_dir}/${file}" DESTINATION "${dependencies_dir}/${destination}")
endif()
endif()
file(WRITE "${dependencies_dir}/.dependency_${dependency}_${arch}.sha256" "${hash}")
if(dependency STREQUAL prebuilt)
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}/${destination}")
elseif(dependency STREQUAL qt6)
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}/${destination}")
elseif(dependency STREQUAL obs-studio)
set(_obs_version ${version})
set(_obs_destination "${destination}")
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}")
endif()
message(STATUS "Setting up ${label} (${arch}) - done")
endforeach()
list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} CACHE PATH "CMake prefix search path" FORCE)
_setup_obs_studio()
endfunction()