if (BUNDLE_TARGET_EXECUTE) # --- Bundling method logic --- function(bundle_qt executable_path) if (WIN32) get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY) find_program(windeployqt_executable windeployqt6) # Create a qt.conf file pointing to the app directory. # This ensures Qt can find its plugins. file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nprefix = .") message(STATUS "Executing windeployqt for executable ${executable_path}") execute_process(COMMAND "${windeployqt_executable}" "${executable_path}" --no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations --plugindir "${executable_parent_dir}/plugins") # Remove the FFmpeg multimedia plugin as we don't include FFmpeg. # We want to use the Windows media plugin instead, which is also included. file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll") elseif (APPLE) get_filename_component(executable_name "${executable_path}" NAME_WE) find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6) message(STATUS "Executing macdeployqt for executable ${executable_path}") execute_process( COMMAND "${MACDEPLOYQT_EXECUTABLE}" "${executable_path}" "-executable=${executable_path}/Contents/MacOS/${executable_name}" -always-overwrite) # Bundling libraries can rewrite path information and break code signatures of system libraries. # Perform an ad-hoc re-signing on the whole app bundle to fix this. execute_process(COMMAND codesign --deep -fs - "${executable_path}") else() message(FATAL_ERROR "Unsupported OS for Qt bundling.") endif() endfunction() function(bundle_appimage bundle_dir executable_path source_path binary_path linuxdeploy_executable enable_qt) get_filename_component(executable_name "${executable_path}" NAME_WE) set(appdir_path "${binary_path}/AppDir-${executable_name}") if (enable_qt) # Find qmake to make sure the plugin uses the right version of Qt. find_program(QMAKE_EXECUTABLE qmake6) set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}") set(extra_linuxdeploy_args --plugin qt) endif() message(STATUS "Creating AppDir for executable ${executable_path}") execute_process(COMMAND ${CMAKE_COMMAND} -E env ${extra_linuxdeploy_env} "${linuxdeploy_executable}" ${extra_linuxdeploy_args} --plugin checkrt --executable "${executable_path}" --icon-file "${source_path}/dist/citra.svg" --desktop-file "${source_path}/dist/${executable_name}.desktop" --appdir "${appdir_path}") if (enable_qt) set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh") file(READ "${qt_hook_file}" qt_hook_contents) # Add Cinnamon to list of DEs for GTK3 theming. string(REPLACE "*XFCE*" "*X-Cinnamon*|*XFCE*" qt_hook_contents "${qt_hook_contents}") # Wayland backend crashes due to changed schemas in Gnome 40. string(REPLACE "export QT_QPA_PLATFORMTHEME=gtk3" "export QT_QPA_PLATFORMTHEME=gtk3; export GDK_BACKEND=x11" qt_hook_contents "${qt_hook_contents}") file(WRITE "${qt_hook_file}" "${qt_hook_contents}") endif() message(STATUS "Creating AppImage for executable ${executable_path}") execute_process(COMMAND ${CMAKE_COMMAND} -E env "OUTPUT=${bundle_dir}/${executable_name}.AppImage" "${linuxdeploy_executable}" --output appimage --appdir "${appdir_path}") endfunction() function(bundle_standalone executable_path original_executable_path bundle_library_paths) get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY) # Resolve dependent library files if they were not passed in. message(STATUS "Determining runtime dependencies of ${executable_path} using library paths ${bundle_library_paths}") file(GET_RUNTIME_DEPENDENCIES EXECUTABLES ${original_executable_path} RESOLVED_DEPENDENCIES_VAR resolved_deps UNRESOLVED_DEPENDENCIES_VAR unresolved_deps DIRECTORIES ${bundle_library_paths} POST_EXCLUDE_REGEXES ".*system32.*") if (WIN32) # Same directory since we don't have rpath. set(lib_dir "${executable_parent_dir}") else() set(lib_dir "${executable_parent_dir}/libs") endif() # Copy files to bundled output. if (resolved_deps) file(MAKE_DIRECTORY ${lib_dir}) foreach (lib_file IN LISTS resolved_deps) message(STATUS "Bundling library ${lib_file}") # Use native copy to turn symlinks into normal files. execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}") endforeach() endif() # Add libs directory to executable rpath where applicable. if (APPLE) execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}") elseif (UNIX) execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}") endif() endfunction() # --- Root bundling logic --- set(bundle_dir ${BINARY_PATH}/bundle) # On Linux, always bundle an AppImage. if (DEFINED LINUXDEPLOY) if (IN_PLACE) message(FATAL_ERROR "Cannot bundle for Linux in-place.") endif() bundle_appimage("${bundle_dir}" "${EXECUTABLE_PATH}" "${SOURCE_PATH}" "${BINARY_PATH}" "${LINUXDEPLOY}" ${BUNDLE_QT}) else() if (IN_PLACE) message(STATUS "Bundling dependencies in-place") set(bundled_executable_path "${EXECUTABLE_PATH}") else() message(STATUS "Copying base executable ${EXECUTABLE_PATH} to output directory ${bundle_dir}") file(COPY ${EXECUTABLE_PATH} DESTINATION ${bundle_dir}) get_filename_component(bundled_executable_name "${EXECUTABLE_PATH}" NAME) set(bundled_executable_path "${bundle_dir}/${bundled_executable_name}") endif() if (BUNDLE_QT) bundle_qt("${bundled_executable_path}") endif() if (WIN32 OR NOT BUNDLE_QT) bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}") endif() endif() else() # --- Bundling target creation logic --- # Downloads and extracts a linuxdeploy component. function(download_linuxdeploy_component base_dir name executable_name) set(executable_file "${base_dir}/${executable_name}") if (NOT EXISTS "${executable_file}") message(STATUS "Downloading ${executable_name}") file(DOWNLOAD "https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}" "${executable_file}" SHOW_PROGRESS) file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) get_filename_component(executable_ext "${executable_file}" LAST_EXT) if (executable_ext STREQUAL ".AppImage") message(STATUS "Extracting ${executable_name}") execute_process( COMMAND "${executable_file}" --appimage-extract WORKING_DIRECTORY "${base_dir}") else() message(STATUS "Copying ${executable_name}") file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/") endif() endif() endfunction() # Adds a target to the bundle target, packing in required libraries. # If in_place is true, the bundling will be done in-place as part of the specified target. function(bundle_target_internal target_name in_place) # Create base bundle target if it does not exist. if (NOT in_place AND NOT TARGET bundle) message(STATUS "Creating base bundle target") add_custom_target(bundle) add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/") add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/") add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png") add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/") add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/") add_custom_command( TARGET bundle COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting") endif() set(BUNDLE_EXECUTABLE_PATH "$") if (target_name MATCHES ".*qt") set(BUNDLE_QT ON) if (APPLE) # For Qt targets on Apple, expect an app bundle. set(BUNDLE_EXECUTABLE_PATH "$") endif() else() set(BUNDLE_QT OFF) endif() # Build a list of library search paths from prefix paths. foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH) if (WIN32) list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin") endif() list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib") endforeach() foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH) list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}") endforeach() # On Linux, prepare linuxdeploy and any required plugins. if (CMAKE_SYSTEM_NAME STREQUAL "Linux") set(LINUXDEPLOY_BASE "${CMAKE_BINARY_DIR}/externals/linuxdeploy") # Download plugins first so they don't overwrite linuxdeploy's AppRun file. download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-x86_64.AppImage") download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt-x86_64.sh") download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy" "linuxdeploy-x86_64.AppImage") set(EXTRA_BUNDLE_ARGS "-DLINUXDEPLOY=${LINUXDEPLOY_BASE}/squashfs-root/AppRun") endif() if (in_place) message(STATUS "Adding in-place bundling to ${target_name}") set(DEST_TARGET ${target_name}) else() message(STATUS "Adding ${target_name} to bundle target") set(DEST_TARGET bundle) add_dependencies(bundle ${target_name}) endif() add_custom_command(TARGET ${DEST_TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} "-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\"" "-DBUNDLE_TARGET_EXECUTE=1" "-DTARGET=${target_name}" "-DSOURCE_PATH=${CMAKE_SOURCE_DIR}" "-DBINARY_PATH=${CMAKE_BINARY_DIR}" "-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}" "-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\"" "-DBUNDLE_QT=${BUNDLE_QT}" "-DIN_PLACE=${in_place}" ${EXTRA_BUNDLE_ARGS} -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") endfunction() # Adds a target to the bundle target, packing in required libraries. function(bundle_target target_name) bundle_target_internal("${target_name}" OFF) endfunction() # Bundles the target in-place, packing in required libraries. function(bundle_target_in_place target_name) bundle_target_internal("${target_name}" ON) endfunction() endif()