cmake_minimum_required(VERSION 3.15) # Fix up a macOS .app bundle by copying non-Qt dylibs into # Contents/Frameworks and rewriting install names to use @rpath/@loader_path. # # Usage: # cmake -DAPP_BUNDLE=/path/to/kge.app -P cmake/fix_bundle.cmake if (NOT APP_BUNDLE) message(FATAL_ERROR "APP_BUNDLE not set. Invoke with -DAPP_BUNDLE=/path/to/App.app") endif () get_filename_component(APP_DIR "${APP_BUNDLE}" ABSOLUTE) set(EXECUTABLE "${APP_DIR}/Contents/MacOS/kge") if (NOT EXISTS "${EXECUTABLE}") message(FATAL_ERROR "Executable not found at: ${EXECUTABLE}") endif () include(BundleUtilities) # Directories to search when resolving prerequisites. We include Homebrew so that # if any deps are currently resolved from there, fixup_bundle will copy them into # the bundle and rewrite install names to be self-contained. set(DIRS "/usr/local/lib" "/opt/homebrew/lib" "/opt/homebrew/opt" ) # Note: We pass empty plugin list so fixup_bundle scans the executable and all # libs it references recursively. Qt frameworks already live in the bundle after # macdeployqt; this step is primarily for non-Qt dylibs (glib, icu, pcre2, zstd, # dbus, etc.). # fixup_bundle often fails if copied libraries are read-only. # We also try to use the system install_name_tool and otool to avoid issues with Anaconda's version. # Note: BundleUtilities uses find_program(gp_otool "otool") internally, so we might need to set it differently. set(gp_otool "/usr/bin/otool") set(CMAKE_INSTALL_NAME_TOOL "/usr/bin/install_name_tool") set(CMAKE_OTOOL "/usr/bin/otool") set(ENV{PATH} "/usr/bin:/bin:/usr/sbin:/sbin") execute_process(COMMAND chmod -R u+w "${APP_DIR}/Contents/Frameworks") fixup_bundle("${APP_DIR}" "" "${DIRS}") # Homebrew's sdl2 is sdl2-compat, which dlopen()s SDL3 at startup instead of # linking it, so fixup_bundle never sees it. Without SDL3 in the bundle kge # stops at launch with "Failed loading SDL3 library". sdl2-compat looks for # @loader_path/libSDL3.dylib first; put SDL3 there. set(SDL2_IN_BUNDLE "${APP_DIR}/Contents/Frameworks/libSDL2-2.0.0.dylib") if (EXISTS "${SDL2_IN_BUNDLE}") file(STRINGS "${SDL2_IN_BUNDLE}" SDL2_COMPAT_REFS REGEX "libSDL3\\.dylib") if (SDL2_COMPAT_REFS) find_file(SDL3_DYLIB NAMES libSDL3.0.dylib libSDL3.dylib PATHS "/opt/homebrew/opt/sdl3/lib" "/opt/homebrew/lib" "/usr/local/opt/sdl3/lib" "/usr/local/lib" NO_DEFAULT_PATH) if (NOT SDL3_DYLIB) message(FATAL_ERROR "libSDL2 in the bundle is sdl2-compat, which needs SDL3 at run time, " "but libSDL3 was not found (brew install sdl3)") endif () file(REAL_PATH "${SDL3_DYLIB}" SDL3_REAL) set(SDL3_IN_BUNDLE "${APP_DIR}/Contents/Frameworks/libSDL3.dylib") message(STATUS "Bundling SDL3 for sdl2-compat: ${SDL3_REAL}") file(COPY_FILE "${SDL3_REAL}" "${SDL3_IN_BUNDLE}") execute_process(COMMAND chmod u+w "${SDL3_IN_BUNDLE}") execute_process(COMMAND /usr/bin/install_name_tool -id "@loader_path/libSDL3.dylib" "${SDL3_IN_BUNDLE}" RESULT_VARIABLE SDL3_ID_RESULT) if (NOT SDL3_ID_RESULT EQUAL 0) message(FATAL_ERROR "install_name_tool -id failed on ${SDL3_IN_BUNDLE}") endif () endif () endif () # On Apple Silicon (and modern macOS in general), modifications by fixup_bundle # invalidate code signatures. We must re-sign the bundle (at least ad-hoc) # for it to be allowed to run. # We sign deep, but sometimes explicit signing of components is more reliable. message(STATUS "Re-signing ${APP_DIR} after fixup...") # 1. Sign dylibs in Frameworks file(GLOB_RECURSE DYLIBS "${APP_DIR}/Contents/Frameworks/*.dylib") foreach (DYLIB ${DYLIBS}) message(STATUS "Signing ${DYLIB}...") execute_process(COMMAND /usr/bin/codesign --force --sign - "${DYLIB}") endforeach () # 2. Sign nested executables message(STATUS "Signing nested kte...") execute_process(COMMAND /usr/bin/codesign --force --sign - "${APP_DIR}/Contents/MacOS/kte") # 3. Sign the main executable explicitly message(STATUS "Signing main kge...") execute_process(COMMAND /usr/bin/codesign --force --sign - "${APP_DIR}/Contents/MacOS/kge") # 4. Sign the main bundle execute_process( COMMAND /usr/bin/codesign --force --deep --sign - "${APP_DIR}" RESULT_VARIABLE CODESIGN_RESULT ) if (NOT CODESIGN_RESULT EQUAL 0) message(FATAL_ERROR "Codesign failed with error: ${CODESIGN_RESULT}") endif () message(STATUS "fix_bundle.cmake completed for ${APP_DIR}")