# Verify the installed Companion .app carries no references to libraries outside itself. # # Configured from companion/targets/mac/bundle_verify.cmake.in - do not edit the copy in the # build tree. # # A .dmg that links against /opt/homebrew, /usr/local/Cellar or a build-machine Qt prefix # starts fine on the machine that built it and fails everywhere else, which is exactly how the # sdl2-compat breakage reached users. Catch it at install time instead. # # Disable with -DCOMPANION_VERIFY_BUNDLE=OFF; widen with -DCOMPANION_VERIFY_BUNDLE_ALLOW=. set(_app "${CMAKE_INSTALL_PREFIX}/@companion_app_dir@") set(_allow "@COMPANION_VERIFY_BUNDLE_ALLOW@") set(_expect_sdl3 "@_bundle_expects_sdl3@") if(NOT IS_DIRECTORY "${_app}") message(FATAL_ERROR "Bundle not found for verification: ${_app}") endif() # sdl2-compat dlopens "@loader_path/libSDL3.dylib" - invisible to otool, so check by hand if(_expect_sdl3 AND NOT EXISTS "${_app}/Contents/Frameworks/libSDL3.dylib") message(FATAL_ERROR "Contents/Frameworks/libSDL3.dylib is missing. Companion was linked against " "sdl2-compat, which loads SDL3 at runtime, so the bundle will not start.") endif() file(GLOB_RECURSE _candidates "${_app}/Contents/*") set(_offenders "") set(_toonew "") set(_checked 0) set(_max_minos "0.0") set(_target_minos "@CMAKE_OSX_DEPLOYMENT_TARGET@") foreach(_file IN LISTS _candidates) if(IS_SYMLINK "${_file}" OR IS_DIRECTORY "${_file}") continue() endif() execute_process( COMMAND otool -L "${_file}" OUTPUT_VARIABLE _otool_out ERROR_VARIABLE _otool_err RESULT_VARIABLE _otool_res ) # non-Mach-O files (plists, icons, translations) simply fail the scan - skip them if(NOT _otool_res EQUAL 0 OR _otool_out MATCHES "is not an object file") continue() endif() math(EXPR _checked "${_checked} + 1") # macOS refuses to launch an app stamped above the running system - that is how 2.12.3 # became "requires macOS 15.0 or later" (issue #7732). execute_process( COMMAND otool -l "${_file}" OUTPUT_VARIABLE _otool_l_out ERROR_QUIET RESULT_VARIABLE _otool_l_res ) # MATCHALL, not MATCHES: a universal binary reports one minos per slice and any of them # can be the one that is wrong. if(_otool_l_res EQUAL 0) string(REGEX MATCHALL "minos [0-9]+(\.[0-9]+)+" _minos_all "${_otool_l_out}") foreach(_entry IN LISTS _minos_all) string(REPLACE "minos " "" _minos "${_entry}") if(_minos VERSION_GREATER _max_minos) set(_max_minos "${_minos}") endif() if(_target_minos AND _minos VERSION_GREATER _target_minos) file(RELATIVE_PATH _rel "${_app}" "${_file}") list(APPEND _toonew "${_rel}: minos ${_minos}") endif() endforeach() endif() string(REPLACE "\n" ";" _lines "${_otool_out}") foreach(_line IN LISTS _lines) string(REGEX MATCH "^\t([^ ]+)" _matched "${_line}") if(NOT _matched) continue() endif() set(_ref "${CMAKE_MATCH_1}") if(NOT _ref MATCHES "^/") continue() # @rpath / @executable_path / @loader_path resolve inside the bundle endif() if(_ref MATCHES "^/usr/lib/" OR _ref MATCHES "^/System/") continue() # shipped with macOS endif() if(_allow AND _ref MATCHES "${_allow}") continue() endif() file(RELATIVE_PATH _rel "${_app}" "${_file}") list(APPEND _offenders "${_rel}: ${_ref}") endforeach() endforeach() if(_offenders) list(REMOVE_DUPLICATES _offenders) string(REPLACE ";" "\n " _report "${_offenders}") message(FATAL_ERROR "Bundle references libraries outside itself - this .dmg would not start on another " "machine:\n ${_report}\n" "Re-run with -DCOMPANION_VERIFY_BUNDLE=OFF to package anyway.") endif() if(_toonew) list(REMOVE_DUPLICATES _toonew) string(REPLACE ";" "\n " _report "${_toonew}") message(FATAL_ERROR "Bundle contains Mach-O files built for a newer macOS than the deployment target " "(${_target_minos}) - macOS refuses to launch an app stamped above the running " "system:\n ${_report}\n" "Re-run with -DCOMPANION_VERIFY_BUNDLE=OFF to package anyway.") endif() # CPack discards message(STATUS) from install scripts, so echo via a subprocess - otherwise # the bundle's real macOS floor never reaches the packaging log. execute_process(COMMAND ${CMAKE_COMMAND} -E echo "-- Bundle verification: ${_checked} Mach-O files, no external references, minimum macOS ${_max_minos}") # The bundle seal is a separate concern from the per-Mach-O signatures: dyld does not consult # CodeResources when loading, so a stale seal does not stop the app launching. It would matter # for Gatekeeper/notarization, so report it - but do not fail a build over it. execute_process( COMMAND codesign --verify --deep --strict --verbose=2 "${_app}" ERROR_VARIABLE _codesign_err RESULT_VARIABLE _codesign_res ) if(NOT _codesign_res EQUAL 0) message(WARNING "Bundle signature is not valid (not fatal): ${_codesign_err}") endif()