From 19784355f935df229feda068c4197ccaf62729ee Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:09:50 -0800 Subject: [PATCH] build: Improve support for Windows cross-compilation. (#7389) * build: Improve support for Windows cross-compilation. * build: Move linuxdeploy download to bundle target execution time. --- CMakeLists.txt | 25 ++- CMakeModules/BundleTarget.cmake | 243 +++++++++++++++++++-------- CMakeModules/DownloadExternals.cmake | 131 +++++++++++---- externals/CMakeLists.txt | 18 ++ externals/cryptopp-cmake | 2 +- externals/dynarmic | 2 +- externals/oaknut | 2 +- 7 files changed, 308 insertions(+), 115 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ceaac9a78..87a485e5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,8 +85,6 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON) option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) -CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF) - # Compile options CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) @@ -249,6 +247,26 @@ if (ENABLE_QT) if (ENABLE_QT_TRANSLATION) find_package(Qt6 REQUIRED COMPONENTS LinguistTools) endif() + + if (NOT DEFINED QT_TARGET_PATH) + # Determine the location of the compile target's Qt. + get_target_property(qtcore_path Qt6::Core LOCATION_Release) + string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE) + string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE) + if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos) + string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH) + else() + string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH) + endif() + endif() + + if (NOT DEFINED QT_HOST_PATH) + # Use the same for host Qt if none is defined. + set(QT_HOST_PATH "${QT_TARGET_PATH}") + endif() + + message(STATUS "Using target Qt at ${QT_TARGET_PATH}") + message(STATUS "Using host Qt at ${QT_HOST_PATH}") endif() # Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic) @@ -424,7 +442,8 @@ else() endif() # Create target for outputting distributable bundles. -if (CITRA_ENABLE_BUNDLE_TARGET) +# Not supported for mobile platforms as distributables are built differently. +if (NOT ANDROID AND NOT IOS) include(BundleTarget) if (ENABLE_SDL2_FRONTEND) bundle_target(citra) diff --git a/CMakeModules/BundleTarget.cmake b/CMakeModules/BundleTarget.cmake index f5b1c06f4..bac86093d 100644 --- a/CMakeModules/BundleTarget.cmake +++ b/CMakeModules/BundleTarget.cmake @@ -2,37 +2,104 @@ if (BUNDLE_TARGET_EXECUTE) # --- Bundling method logic --- + function(symlink_safe_copy from to) + if (WIN32) + # Use cmake copy for maximum compatibility. + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${to}" + RESULT_VARIABLE cp_result) + else() + # Use native copy to turn symlinks into normal files. + execute_process(COMMAND cp -L "${from}" "${to}" + RESULT_VARIABLE cp_result) + endif() + if (NOT cp_result EQUAL "0") + message(FATAL_ERROR "cp \"${from}\" \"${to}\" failed: ${cp_result}") + endif() + endfunction() + function(bundle_qt executable_path) if (WIN32) + # Perform standalone bundling first to copy over all used libraries, as windeployqt does not do this. + bundle_standalone("${executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}") + 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 = .") + file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nPrefix = .") + + find_program(windeployqt_executable windeployqt6 PATHS "${QT_HOST_PATH}/bin") + find_program(qtpaths_executable qtpaths6 PATHS "${QT_HOST_PATH}/bin") + + # TODO: Hack around windeployqt's poor cross-compilation support by + # TODO: making a local copy with a prefix pointing to the target Qt. + if (NOT "${QT_HOST_PATH}" STREQUAL "${QT_TARGET_PATH}") + set(windeployqt_dir "${BINARY_PATH}/windeployqt_copy") + file(MAKE_DIRECTORY "${windeployqt_dir}") + symlink_safe_copy("${windeployqt_executable}" "${windeployqt_dir}/windeployqt.exe") + symlink_safe_copy("${qtpaths_executable}" "${windeployqt_dir}/qtpaths.exe") + symlink_safe_copy("${QT_HOST_PATH}/bin/Qt6Core.dll" "${windeployqt_dir}") + + if (EXISTS "${QT_TARGET_PATH}/share") + # Unix-style Qt; we need to wire up the paths manually. + file(WRITE "${windeployqt_dir}/qt.conf" "\ + [Paths]\n + Prefix = ${QT_TARGET_PATH}\n \ + ArchData = ${QT_TARGET_PATH}/share/qt6\n \ + Binaries = ${QT_TARGET_PATH}/bin\n \ + Data = ${QT_TARGET_PATH}/share/qt6\n \ + Documentation = ${QT_TARGET_PATH}/share/qt6/doc\n \ + Headers = ${QT_TARGET_PATH}/include/qt6\n \ + Libraries = ${QT_TARGET_PATH}/lib\n \ + LibraryExecutables = ${QT_TARGET_PATH}/share/qt6/bin\n \ + Plugins = ${QT_TARGET_PATH}/share/qt6/plugins\n \ + QmlImports = ${QT_TARGET_PATH}/share/qt6/qml\n \ + Translations = ${QT_TARGET_PATH}/share/qt6/translations\n \ + ") + else() + # Windows-style Qt; the defaults should suffice. + file(WRITE "${windeployqt_dir}/qt.conf" "[Paths]\nPrefix = ${QT_TARGET_PATH}") + endif() + + set(windeployqt_executable "${windeployqt_dir}/windeployqt.exe") + set(qtpaths_executable "${windeployqt_dir}/qtpaths.exe") + endif() message(STATUS "Executing windeployqt for executable ${executable_path}") execute_process(COMMAND "${windeployqt_executable}" "${executable_path}" + --qtpaths "${qtpaths_executable}" --no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations - --plugindir "${executable_parent_dir}/plugins") + --plugindir "${executable_parent_dir}/plugins" + RESULT_VARIABLE windeployqt_result) + if (NOT windeployqt_result EQUAL "0") + message(FATAL_ERROR "windeployqt failed: ${windeployqt_result}") + endif() # 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) + find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin") - message(STATUS "Executing macdeployqt for executable ${executable_path}") + message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"") execute_process( - COMMAND "${MACDEPLOYQT_EXECUTABLE}" + COMMAND "${macdeployqt_executable}" "${executable_path}" "-executable=${executable_path}/Contents/MacOS/${executable_name}" - -always-overwrite) + -always-overwrite + RESULT_VARIABLE macdeployqt_result) + if (NOT macdeployqt_result EQUAL "0") + message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}") + endif() # 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}") + execute_process(COMMAND codesign --deep -fs - "${executable_path}" + RESULT_VARIABLE codesign_result) + if (NOT codesign_result EQUAL "0") + message(FATAL_ERROR "codesign failed: ${codesign_result}") + endif() else() message(FATAL_ERROR "Unsupported OS for Qt bundling.") endif() @@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE) if (enable_qt) # Find qmake to make sure the plugin uses the right version of Qt. - find_program(QMAKE_EXECUTABLE qmake6) + find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin") - set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}") + set(extra_linuxdeploy_env "QMAKE=${qmake_executable}") set(extra_linuxdeploy_args --plugin qt) endif() @@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE) --executable "${executable_path}" --icon-file "${source_path}/dist/citra.svg" --desktop-file "${source_path}/dist/${executable_name}.desktop" - --appdir "${appdir_path}") + --appdir "${appdir_path}" + RESULT_VARIABLE linuxdeploy_appdir_result) + if (NOT linuxdeploy_appdir_result EQUAL "0") + message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}") + endif() if (enable_qt) set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh") @@ -82,7 +153,11 @@ if (BUNDLE_TARGET_EXECUTE) "OUTPUT=${bundle_dir}/${executable_name}.AppImage" "${linuxdeploy_executable}" --output appimage - --appdir "${appdir_path}") + --appdir "${appdir_path}" + RESULT_VARIABLE linuxdeploy_appimage_result) + if (NOT linuxdeploy_appimage_result EQUAL "0") + message(FATAL_ERROR "linuxdeploy failed to create AppImage: ${linuxdeploy_appimage_result}") + endif() endfunction() function(bundle_standalone executable_path original_executable_path bundle_library_paths) @@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE) 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}") + symlink_safe_copy("${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}") + execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}" + RESULT_VARIABLE install_name_tool_result) + if (NOT install_name_tool_result EQUAL "0") + message(FATAL_ERROR "install_name_tool failed: ${install_name_tool_result}") + endif() elseif (UNIX) - execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}") + execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}" + RESULT_VARIABLE patchelf_result) + if (NOT patchelf_result EQUAL "0") + message(FATAL_ERROR "patchelf failed: ${patchelf_result}") + endif() endif() endfunction() @@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE) set(bundle_dir ${BINARY_PATH}/bundle) # On Linux, always bundle an AppImage. - if (DEFINED LINUXDEPLOY) + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (IN_PLACE) message(FATAL_ERROR "Cannot bundle for Linux in-place.") endif() @@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE) if (BUNDLE_QT) bundle_qt("${bundled_executable_path}") - endif() - - if (WIN32 OR NOT BUNDLE_QT) + else() bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}") endif() endif() -else() - # --- Bundling target creation logic --- +elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY) + # --- linuxdeploy download logic --- # Downloads and extracts a linuxdeploy component. function(download_linuxdeploy_component base_dir name executable_name) @@ -161,7 +241,7 @@ else() if (NOT EXISTS "${executable_file}") message(STATUS "Downloading ${executable_name}") file(DOWNLOAD - "https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}" + "https://github.com/${name}/releases/download/continuous/${executable_name}" "${executable_file}" SHOW_PROGRESS) file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) @@ -170,7 +250,11 @@ else() message(STATUS "Extracting ${executable_name}") execute_process( COMMAND "${executable_file}" --appimage-extract - WORKING_DIRECTORY "${base_dir}") + WORKING_DIRECTORY "${base_dir}" + RESULT_VARIABLE extract_result) + if (NOT extract_result EQUAL "0") + message(FATAL_ERROR "AppImage extract failed: ${extract_result}") + endif() else() message(STATUS "Copying ${executable_name}") file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/") @@ -178,89 +262,102 @@ else() endif() endfunction() + # Download plugins first so they don't overwrite linuxdeploy's AppRun file. + download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage") + download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "darealshinji/linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt.sh") + download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy" "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage") +else() + # --- Bundling target creation logic --- + + # Creates the base bundle target with common files and pre-bundle steps. + function(create_base_bundle_target) + 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") + + # On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs. + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") + add_custom_command( + TARGET bundle + COMMAND ${CMAKE_COMMAND} + "-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1" + "-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy" + "-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}" + -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") + 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") + create_base_bundle_target() endif() - set(BUNDLE_EXECUTABLE_PATH "$") + set(bundle_executable_path "$") if (target_name MATCHES ".*qt") - set(BUNDLE_QT ON) + set(bundle_qt ON) if (APPLE) # For Qt targets on Apple, expect an app bundle. - set(BUNDLE_EXECUTABLE_PATH "$") + set(bundle_executable_path "$") endif() else() - set(BUNDLE_QT OFF) + 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) + foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH) if (WIN32) - list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin") + list(APPEND bundle_library_paths "${prefix_path}/bin") endif() - list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib") + 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}") + 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}) + set(dest_target ${target_name}) else() message(STATUS "Adding ${target_name} to bundle target") - set(DEST_TARGET bundle) + set(dest_target bundle) add_dependencies(bundle ${target_name}) endif() - add_custom_command(TARGET ${DEST_TARGET} POST_BUILD + add_custom_command(TARGET ${dest_target} POST_BUILD COMMAND ${CMAKE_COMMAND} - "-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\"" + "-DQT_HOST_PATH=\"${QT_HOST_PATH}\"" + "-DQT_TARGET_PATH=\"${QT_TARGET_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}" + "-DEXECUTABLE_PATH=${bundle_executable_path}" + "-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\"" + "-DBUNDLE_QT=${bundle_qt}" "-DIN_PLACE=${in_place}" - ${EXTRA_BUNDLE_ARGS} + "-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun" -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") endfunction() diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index 19b8f477c..dfdc803a4 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -1,21 +1,20 @@ set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) -# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH. -# Params: -# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool. -function(download_qt target) +# Determines parameters based on the host and target for downloading the right Qt binaries. +function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out) if (target MATCHES "tools_.*") - set(DOWNLOAD_QT_TOOL ON) + set(tool ON) else() - set(DOWNLOAD_QT_TOOL OFF) + set(tool OFF) endif() # Determine installation parameters for OS, architecture, and compiler if (WIN32) set(host "windows") set(type "desktop") - if (NOT DOWNLOAD_QT_TOOL) + + if (NOT tool) if (MINGW) set(arch "win64_mingw") set(arch_path "mingw_64") @@ -28,21 +27,35 @@ function(download_qt target) message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.") endif() set(arch "win64_${arch_path}") + + # In case we're cross-compiling, prepare to also fetch the correct host Qt tools. + if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") + set(host_arch_path "msvc2019_64") + elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") + # TODO: msvc2019_arm64 doesn't include some of the required tools for some reason, + # TODO: so until it does, just use msvc2019_64 under x86_64 emulation. + # set(host_arch_path "msvc2019_arm64") + set(host_arch_path "msvc2019_64") + endif() + set(host_arch "win64_${host_arch_path}") else() message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.") endif() endif() elseif (APPLE) set(host "mac") - if (IOS AND NOT DOWNLOAD_QT_TOOL) + set(type "desktop") + set(arch "clang_64") + set(arch_path "macos") + + if (IOS AND NOT tool) + set(host_type "${type}") + set(host_arch "${arch}") + set(host_arch_path "${arch_path}") + set(type "ios") set(arch "ios") set(arch_path "ios") - set(host_arch_path "macos") - else() - set(type "desktop") - set(arch "clang_64") - set(arch_path "macos") endif() else() set(host "linux") @@ -51,38 +64,64 @@ function(download_qt target) set(arch_path "linux") endif() - get_external_prefix(qt base_path) - file(MAKE_DIRECTORY "${base_path}") + set(${host_out} "${host}" PARENT_SCOPE) + set(${type_out} "${type}" PARENT_SCOPE) + set(${arch_out} "${arch}" PARENT_SCOPE) + set(${arch_path_out} "${arch_path}" PARENT_SCOPE) + if (DEFINED host_type) + set(${host_type_out} "${host_type}" PARENT_SCOPE) + else() + set(${host_type_out} "${type}" PARENT_SCOPE) + endif() + if (DEFINED host_arch) + set(${host_arch_out} "${host_arch}" PARENT_SCOPE) + else() + set(${host_arch_out} "${arch}" PARENT_SCOPE) + endif() + if (DEFINED host_arch_path) + set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE) + else() + set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE) + endif() +endfunction() + +# Download Qt binaries for a specifc configuration. +function(download_qt_configuration prefix_out target host type arch arch_path base_path) + if (target MATCHES "tools_.*") + set(tool ON) + else() + set(tool OFF) + endif() set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini") - if (DOWNLOAD_QT_TOOL) + if (tool) set(prefix "${base_path}/Tools") set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target}) else() set(prefix "${base_path}/${target}/${arch_path}") - if (host_arch_path) - set(host_flag "--autodesktop") - set(host_prefix "${base_path}/${target}/${host_arch_path}") - endif() - set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag} - -m qtmultimedia --archives qttranslations qttools qtsvg qtbase) + set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} + -m qtmultimedia --archives qttranslations qttools qtsvg qtbase) endif() if (NOT EXISTS "${prefix}") - message(STATUS "Downloading binaries for Qt...") + message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9") if (WIN32) set(aqt_path "${base_path}/aqt.exe") - file(DOWNLOAD - ${AQT_PREBUILD_BASE_URL}/aqt.exe - ${aqt_path} SHOW_PROGRESS) + if (NOT EXISTS "${aqt_path}") + file(DOWNLOAD + ${AQT_PREBUILD_BASE_URL}/aqt.exe + ${aqt_path} SHOW_PROGRESS) + endif() execute_process(COMMAND ${aqt_path} ${install_args} WORKING_DIRECTORY ${base_path}) elseif (APPLE) set(aqt_path "${base_path}/aqt-macos") - file(DOWNLOAD - ${AQT_PREBUILD_BASE_URL}/aqt-macos - ${aqt_path} SHOW_PROGRESS) + if (NOT EXISTS "${aqt_path}") + file(DOWNLOAD + ${AQT_PREBUILD_BASE_URL}/aqt-macos + ${aqt_path} SHOW_PROGRESS) + endif() execute_process(COMMAND chmod +x ${aqt_path}) execute_process(COMMAND ${aqt_path} ${install_args} WORKING_DIRECTORY ${base_path}) @@ -96,18 +135,38 @@ function(download_qt target) execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args} WORKING_DIRECTORY ${base_path}) endif() + + message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}") endif() - message(STATUS "Using downloaded Qt binaries at ${prefix}") + set(${prefix_out} "${prefix}" PARENT_SCOPE) +endfunction() - # Add the Qt prefix path so CMake can locate it. +# This function downloads Qt using aqt. +# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH. +# QT_TARGET_PATH is set to the Qt for the compile target platform. +# QT_HOST_PATH is set to a host-compatible Qt, for running tools. +# Params: +# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool. +function(download_qt target) + determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path) + + get_external_prefix(qt base_path) + file(MAKE_DIRECTORY "${base_path}") + + download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}") + if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}") + download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}") + else() + set(host_prefix "${prefix}") + endif() + + set(QT_TARGET_PATH "${prefix}" CACHE STRING "") + set(QT_HOST_PATH "${host_prefix}" CACHE STRING "") + + # Add the target Qt prefix path so CMake can locate it. list(APPEND CMAKE_PREFIX_PATH "${prefix}") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) - - if (DEFINED host_prefix) - message(STATUS "Using downloaded host Qt binaries at ${host_prefix}") - set(QT_HOST_PATH "${host_prefix}" CACHE STRING "") - endif() endfunction() function(download_moltenvk) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 878528e68..5f4c23ad3 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP) add_library(cryptopp INTERFACE) target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) else() + if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE) + # TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC. + # TODO: See https://github.com/weidai11/cryptopp/issues/1260 + set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "") + endif() + set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "") set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(CRYPTOPP_INSTALL OFF CACHE BOOL "") @@ -235,6 +241,18 @@ endif() # DiscordRPC if (USE_DISCORD_PRESENCE) + # rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms. + include(TestBigEndian) + test_big_endian(RAPIDJSON_BIG_ENDIAN) + if(RAPIDJSON_BIG_ENDIAN) + add_compile_definitions(RAPIDJSON_ENDIAN=1) + else() + add_compile_definitions(RAPIDJSON_ENDIAN=0) + endif() + + # Apply a dummy CLANG_FORMAT_SUFFIX to disable discord-rpc's unnecessary automatic clang-format. + set(CLANG_FORMAT_SUFFIX "dummy") + add_subdirectory(discord-rpc EXCLUDE_FROM_ALL) target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) endif() diff --git a/externals/cryptopp-cmake b/externals/cryptopp-cmake index 9327192b0..a99c80c26 160000 --- a/externals/cryptopp-cmake +++ b/externals/cryptopp-cmake @@ -1 +1 @@ -Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48 +Subproject commit a99c80c26686e44eddf0432140ae397f3efbd0b3 diff --git a/externals/dynarmic b/externals/dynarmic index ca0e264f4..30f1a3c62 160000 --- a/externals/dynarmic +++ b/externals/dynarmic @@ -1 +1 @@ -Subproject commit ca0e264f4f962e29baa23a3282ce484625866b98 +Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c diff --git a/externals/oaknut b/externals/oaknut index 9d091109d..6b1d57ea7 160000 --- a/externals/oaknut +++ b/externals/oaknut @@ -1 +1 @@ -Subproject commit 9d091109deb445bc6e9289c6195a282b7c993d49 +Subproject commit 6b1d57ea7ed4882d32a91eeaa6557b0ecb4da152