diff options
Diffstat (limited to 'cmake/QtPublicAppleHelpers.cmake')
-rw-r--r-- | cmake/QtPublicAppleHelpers.cmake | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/cmake/QtPublicAppleHelpers.cmake b/cmake/QtPublicAppleHelpers.cmake new file mode 100644 index 0000000000..5f7a7719b5 --- /dev/null +++ b/cmake/QtPublicAppleHelpers.cmake @@ -0,0 +1,954 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +function(_qt_internal_handle_ios_launch_screen target) + # Check if user provided a launch screen path via a variable. + set(launch_screen "") + + # Check if the project provided a launch screen path via a variable. + # This variable is currently in Technical Preview. + if(QT_IOS_LAUNCH_SCREEN) + set(launch_screen "${QT_IOS_LAUNCH_SCREEN}") + endif() + + # Check if the project provided a launch screen path via a target property, it takes precedence + # over the variable. + # This property is currently in Technical Preview. + get_target_property(launch_screen_from_prop "${target}" QT_IOS_LAUNCH_SCREEN) + if(launch_screen_from_prop) + set(launch_screen "${launch_screen_from_prop}") + endif() + + # If the project hasn't provided a launch screen file path, use a copy of the template + # that qmake uses. + # It needs to be a copy because configure_file can't handle all the escaped double quotes + # present in the qmake template file. + set(is_default_launch_screen FALSE) + if(NOT launch_screen AND NOT QT_NO_SET_DEFAULT_IOS_LAUNCH_SCREEN) + set(is_default_launch_screen TRUE) + set(launch_screen + "${__qt_internal_cmake_apple_support_files_path}/LaunchScreen.storyboard") + endif() + + # Check that the launch screen exists. + if(launch_screen) + if(NOT IS_ABSOLUTE "${launch_screen}") + message(FATAL_ERROR + "Provided launch screen value should be an absolute path: '${launch_screen}'") + endif() + + if(NOT EXISTS "${launch_screen}") + message(FATAL_ERROR + "Provided launch screen file does not exist: '${launch_screen}'") + endif() + endif() + + if(launch_screen AND NOT QT_NO_ADD_IOS_LAUNCH_SCREEN_TO_BUNDLE) + get_filename_component(launch_screen_name "${launch_screen}" NAME) + + # Make a copy of the default launch screen template for this target and replace the + # label inside the template with the target name. + if(is_default_launch_screen) + # Configure our default template and place it in the build dir. + set(launch_screen_in_path "${launch_screen}") + + string(MAKE_C_IDENTIFIER "${target}" target_identifier) + set(launch_screen_out_dir + "${CMAKE_CURRENT_BINARY_DIR}/.qt/launch_screen_storyboards/${target_identifier}") + + set(launch_screen_out_path + "${launch_screen_out_dir}/${launch_screen_name}") + + file(MAKE_DIRECTORY "${launch_screen_out_dir}") + + configure_file("${launch_screen_in_path}" "${launch_screen_out_path}" COPYONLY) + + set(final_launch_screen_path "${launch_screen_out_path}") + else() + set(final_launch_screen_path "${launch_screen}") + endif() + + # Add the launch screen storyboard file as a source file, otherwise CMake doesn't consider + # it as a resource file and MACOSX_PACKAGE_LOCATION processing will be skipped. + target_sources("${target}" PRIVATE "${final_launch_screen_path}") + + # Ensure Xcode compiles the storyboard file and installs the compiled storyboard .nib files + # into the app bundle. + # We use target_sources and the MACOSX_PACKAGE_LOCATION source file property for that + # instead of the RESOURCE target property, becaues the latter could potentially end up + # needlessly installing the source storyboard file. + # + # We can't rely on policy CMP0118 since user project controls it. + set(scope_args) + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.18") + set(scope_args TARGET_DIRECTORY ${target}) + endif() + set_source_files_properties("${final_launch_screen_path}" ${scope_args} + PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + + # Save the launch screen name, so its value is added as an UILaunchStoryboardName entry + # in the Qt generated Info.plist file. + # Xcode expects an Info.plist storyboard entry without an extension. + get_filename_component(launch_screen_base_name "${launch_screen}" NAME_WE) + set_target_properties("${target}" PROPERTIES + _qt_ios_launch_screen_name "${launch_screen_name}" + _qt_ios_launch_screen_base_name "${launch_screen_base_name}" + _qt_ios_launch_screen_path "${final_launch_screen_path}") + endif() +endfunction() + +function(_qt_internal_find_ios_development_team_id out_var) + get_property(team_id GLOBAL PROPERTY _qt_internal_ios_development_team_id) + get_property(team_id_computed GLOBAL PROPERTY _qt_internal_ios_development_team_id_computed) + if(team_id_computed) + # Just in case if the value is non-empty but still booly FALSE. + if(NOT team_id) + set(team_id "") + endif() + set("${out_var}" "${team_id}" PARENT_SCOPE) + return() + endif() + + set_property(GLOBAL PROPERTY _qt_internal_ios_development_team_id_computed "TRUE") + + set(home_dir "$ENV{HOME}") + set(xcode_preferences_path "${home_dir}/Library/Preferences/com.apple.dt.Xcode.plist") + + # Extract the first account name (email) from the user's Xcode preferences + message(DEBUG "Trying to extract an Xcode development team id from '${xcode_preferences_path}'") + execute_process(COMMAND "/usr/libexec/PlistBuddy" + -x -c "print IDEProvisioningTeams" "${xcode_preferences_path}" + OUTPUT_VARIABLE teams_xml + ERROR_VARIABLE plist_error) + + # Parsing state. + set(is_free "") + set(current_team_id "") + set(parsing_is_free FALSE) + set(parsing_team_id FALSE) + set(first_team_id "") + + # Parse the xml output and return the first encountered non-free team id. If no non-free team id + # is found, return the first encountered free team id. + # If no team is found, return an empty string. + # + # Example input: + #<plist version="1.0"> + #<dict> + # <key>marty@planet.local</key> + # <array> + # <dict> + # <key>isFreeProvisioningTeam</key> + # <false/> + # <key>teamID</key> + # <string>AAA</string> + # ... + # </dict> + # <dict> + # <key>isFreeProvisioningTeam</key> + # <true/> + # <key>teamID</key> + # <string>BBB</string> + # ... + # </dict> + # </array> + #</dict> + #</plist> + if(teams_xml AND NOT plist_error) + string(REPLACE "\n" ";" teams_xml_lines "${teams_xml}") + + foreach(xml_line ${teams_xml_lines}) + string(STRIP "${xml_line}" xml_line) + if(xml_line STREQUAL "<dict>") + # Clean any previously found values when a new team dict is matched. + set(is_free "") + set(current_team_id "") + + elseif(xml_line STREQUAL "<key>isFreeProvisioningTeam</key>") + set(parsing_is_free TRUE) + + elseif(parsing_is_free) + set(parsing_is_free FALSE) + + if(xml_line MATCHES "true") + set(is_free TRUE) + else() + set(is_free FALSE) + endif() + + elseif(xml_line STREQUAL "<key>teamID</key>") + set(parsing_team_id TRUE) + + elseif(parsing_team_id) + set(parsing_team_id FALSE) + if(xml_line MATCHES "<string>([^<]+)</string>") + set(current_team_id "${CMAKE_MATCH_1}") + else() + continue() + endif() + + string(STRIP "${current_team_id}" current_team_id) + + # If this is the first team id we found so far, remember that, regardless if's free + # or not. + if(NOT first_team_id AND current_team_id) + set(first_team_id "${current_team_id}") + endif() + + # Break early if we found a non-free team id and use it, because we prefer + # a non-free team for signing, just like qmake. + if(NOT is_free AND current_team_id) + set(first_team_id "${current_team_id}") + break() + endif() + endif() + endforeach() + endif() + + if(NOT first_team_id) + message(DEBUG "Failed to extract an Xcode development team id.") + set("${out_var}" "" PARENT_SCOPE) + else() + message(DEBUG "Successfully extracted the first encountered Xcode development team id.") + set_property(GLOBAL PROPERTY _qt_internal_ios_development_team_id "${first_team_id}") + set("${out_var}" "${first_team_id}" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_get_apple_bundle_identifier_prefix out_var) + get_property(prefix GLOBAL PROPERTY _qt_internal_ios_bundle_identifier_prefix) + get_property(prefix_computed GLOBAL PROPERTY + _qt_internal_ios_bundle_identifier_prefix_computed) + if(prefix_computed) + # Just in case if the value is non-empty but still booly FALSE. + if(NOT prefix) + set(prefix "") + endif() + set("${out_var}" "${prefix}" PARENT_SCOPE) + return() + endif() + + set_property(GLOBAL PROPERTY _qt_internal_ios_bundle_identifier_prefix_computed "TRUE") + + set(home_dir "$ENV{HOME}") + set(xcode_preferences_path "${home_dir}/Library/Preferences/com.apple.dt.Xcode.plist") + + message(DEBUG "Trying to extract the default bundle identifier prefix from Xcode preferences.") + execute_process(COMMAND "/usr/libexec/PlistBuddy" + -c "print IDETemplateOptions:bundleIdentifierPrefix" + "${xcode_preferences_path}" + OUTPUT_VARIABLE prefix + ERROR_VARIABLE prefix_error) + if(prefix AND NOT prefix_error) + message(DEBUG "Successfully extracted the default bundle identifier prefix.") + string(STRIP "${prefix}" prefix) + else() + message(DEBUG "Failed to extract the default bundle identifier prefix.") + endif() + + if(prefix AND NOT prefix_error) + set_property(GLOBAL PROPERTY _qt_internal_ios_bundle_identifier_prefix "${prefix}") + set("${out_var}" "${prefix}" PARENT_SCOPE) + else() + set("${out_var}" "" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_escape_rfc_1034_identifier value out_var) + # According to https://datatracker.ietf.org/doc/html/rfc1034#section-3.5 + # we can only use letters, digits, dot (.) and hyphens (-). + # Underscores are not allowed. + string(REGEX REPLACE "[^A-Za-z0-9.]" "-" value "${value}") + + set("${out_var}" "${value}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_get_default_apple_bundle_identifier target out_var) + _qt_internal_get_apple_bundle_identifier_prefix(prefix) + if(NOT prefix) + set(prefix "com.yourcompany") + + # For a better out-of-the-box experience, try to create a unique prefix by appending + # the sha1 of the team id, if one is found. + _qt_internal_find_ios_development_team_id(team_id) + if(team_id) + string(SHA1 hash "${team_id}") + string(SUBSTRING "${hash}" 0 8 infix) + string(APPEND prefix ".${infix}") + endif() + + if(CMAKE_GENERATOR STREQUAL "Xcode") + message(WARNING + "No organization bundle identifier prefix could be retrieved from Xcode preferences. \ + This can lead to code signing issues due to a non-unique bundle \ + identifier. Please set up an organization prefix by creating a new project within \ + Xcode, or consider providing a custom bundle identifier by specifying the \ + MACOSX_BUNDLE_GUI_IDENTIFIER or XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER property." + ) + endif() + endif() + + # Escape the prefix according to rfc 1034, it's important for code-signing. If an invalid + # identifier is used, calling xcodebuild on the command line says that no provisioning profile + # could be found, with no additional error message. If one opens the generated project with + # Xcode and clicks on 'Try again' to get a new profile, it shows a semi-useful error message + # that the identifier is invalid. + _qt_internal_escape_rfc_1034_identifier("${prefix}" prefix) + + if(CMAKE_GENERATOR STREQUAL "Xcode") + set(identifier "${prefix}.$(PRODUCT_NAME:rfc1034identifier)") + else() + set(identifier "${prefix}.${target}") + endif() + + set("${out_var}" "${identifier}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_set_placeholder_apple_bundle_version target) + # If user hasn't provided neither a bundle version nor a bundle short version string for the + # app, set a placeholder value for both which will add them to the generated Info.plist file. + # This is required so that the app launches in the simulator (but apparently not for running + # on-device). + get_target_property(bundle_version "${target}" MACOSX_BUNDLE_BUNDLE_VERSION) + get_target_property(bundle_short_version "${target}" MACOSX_BUNDLE_SHORT_VERSION_STRING) + + if(NOT MACOSX_BUNDLE_BUNDLE_VERSION AND + NOT MACOSX_BUNDLE_SHORT_VERSION_STRING AND + NOT bundle_version AND + NOT bundle_short_version AND + NOT QT_NO_SET_XCODE_BUNDLE_VERSION + ) + get_target_property(version "${target}" VERSION) + if(NOT version) + set(version "${PROJECT_VERSION}") + if(NOT version) + set(version "1.0.0") + endif() + endif() + + # Use x.y for short version and x.y.z for full version + # Any versions longer than this will fail App Store + # submission. + string(REPLACE "." ";" version_list ${version}) + list(LENGTH version_list version_list_length) + list(GET version_list 0 version_major) + set(bundle_short_version "${version_major}") + if(version_list_length GREATER 1) + list(GET version_list 1 version_minor) + string(APPEND bundle_short_version ".${version_minor}") + endif() + set(bundle_version "${bundle_short_version}") + if(version_list_length GREATER 2) + list(GET version_list 2 version_patch) + string(APPEND bundle_version ".${version_patch}") + endif() + + + if(NOT CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION + AND NOT QT_NO_SET_XCODE_ATTRIBUTE_MARKETING_VERSION + AND NOT CMAKE_XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION + AND NOT QT_NO_SET_XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION + AND CMAKE_GENERATOR STREQUAL "Xcode") + get_target_property(marketing_version "${target}" + XCODE_ATTRIBUTE_MARKETING_VERSION) + get_target_property(current_project_version "${target}" + XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION) + if(NOT marketing_version AND NOT current_project_version) + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${bundle_version}" + XCODE_ATTRIBUTE_MARKETING_VERSION "${bundle_short_version}" + ) + set(bundle_version "$(CURRENT_PROJECT_VERSION)") + set(bundle_short_version "$(MARKETING_VERSION)") + endif() + endif() + + set_target_properties("${target}" + PROPERTIES + MACOSX_BUNDLE_BUNDLE_VERSION "${bundle_version}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${bundle_short_version}" + ) + endif() +endfunction() + +function(_qt_internal_set_xcode_development_team_id target) + # If user hasn't provided a development team id, try to find the first one specified + # in the Xcode preferences. + if(NOT CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM AND NOT QT_NO_SET_XCODE_DEVELOPMENT_TEAM_ID) + get_target_property(existing_team_id "${target}" XCODE_ATTRIBUTE_DEVELOPMENT_TEAM) + if(NOT existing_team_id) + _qt_internal_find_ios_development_team_id(team_id) + set_target_properties("${target}" + PROPERTIES XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${team_id}") + endif() + endif() +endfunction() + +function(_qt_internal_set_apple_bundle_identifier target) + # Skip all logic if requested. + if(QT_NO_SET_XCODE_BUNDLE_IDENTIFIER) + return() + endif() + + # There are two fields to consider: the CFBundleIdentifier key (ie., cmake_bundle_identifier) + # to be written to Info.plist and the PRODUCT_BUNDLE_IDENTIFIER (ie., xcode_bundle_identifier) + # property to set in the Xcode project. The `cmake_bundle_identifier` set by + # MACOSX_BUNDLE_GUI_IDENTIFIER applies to both Xcode, and other generators, while + # `xcode_bundle_identifier` set by XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER is + # Xcode specific. + # + # If Ninja is the generator, we set the value of `MACOSX_BUNDLE_GUI_IDENTIFIER` + # and don't touch the `XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER`. + # If Xcode is the generator, we set the value of `XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER`, + # and additionally, to silence a Xcode's warning, we set the `MACOSX_BUNDLE_GUI_IDENTIFIER` to + # `${PRODUCT_BUNDLE_IDENTIFIER}` so that Xcode could sort it out. + + get_target_property(existing_cmake_bundle_identifier "${target}" + MACOSX_BUNDLE_GUI_IDENTIFIER) + get_target_property(existing_xcode_bundle_identifier "${target}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER) + + set(is_cmake_bundle_identifier_given FALSE) + if(existing_cmake_bundle_identifier) + set(is_cmake_bundle_identifier_given TRUE) + elseif(MACOSX_BUNDLE_GUI_IDENTIFIER) + set(is_cmake_bundle_identifier_given TRUE) + set(existing_cmake_bundle_identifier ${MACOSX_BUNDLE_GUI_IDENTIFIER}) + endif() + + set(is_xcode_bundle_identifier_given FALSE) + if(existing_xcode_bundle_identifier) + set(is_xcode_bundle_identifier_given TRUE) + elseif(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER) + set(is_xcode_bundle_identifier_given TRUE) + set(existing_xcode_bundle_identifier ${CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER}) + endif() + + if(is_cmake_bundle_identifier_given + AND is_xcode_bundle_identifier_given + AND NOT existing_cmake_bundle_identifier STREQUAL existing_xcode_bundle_identifier) + message(WARNING + "MACOSX_BUNDLE_GUI_IDENTIFIER and XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER " + "are set to different values. You only need to set one of them. ") + endif() + + if(NOT is_xcode_bundle_identifier_given + AND NOT is_cmake_bundle_identifier_given) + _qt_internal_get_default_apple_bundle_identifier("${target}" bundle_id) + elseif(is_cmake_bundle_identifier_given) + set(bundle_id ${existing_cmake_bundle_identifier}) + elseif(is_xcode_bundle_identifier_given) + set(bundle_id ${existing_xcode_bundle_identifier}) + endif() + + if(CMAKE_GENERATOR STREQUAL "Xcode") + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${bundle_id}" + MACOSX_BUNDLE_GUI_IDENTIFIER "$(PRODUCT_BUNDLE_IDENTIFIER)") + else() + set_target_properties("${target}" + PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER "${bundle_id}") + endif() +endfunction() + +function(_qt_internal_set_xcode_targeted_device_family target) + if(NOT CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY + AND NOT QT_NO_SET_XCODE_TARGETED_DEVICE_FAMILY) + get_target_property(existing_device_family + "${target}" XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY) + if(NOT existing_device_family) + set(device_family_iphone_and_ipad "1,2") + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY + "${device_family_iphone_and_ipad}") + endif() + endif() +endfunction() + +function(_qt_internal_set_xcode_code_sign_style target) + if(NOT CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE + AND NOT QT_NO_SET_XCODE_CODE_SIGN_STYLE) + get_target_property(existing_code_style + "${target}" XCODE_ATTRIBUTE_CODE_SIGN_STYLE) + if(NOT existing_code_style) + set(existing_code_style "Automatic") + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_STYLE + "${existing_code_style}") + endif() + endif() +endfunction() + +# Workaround for https://gitlab.kitware.com/cmake/cmake/-/issues/15183 +function(_qt_internal_set_xcode_install_path target) + if(NOT CMAKE_XCODE_ATTRIBUTE_INSTALL_PATH + AND NOT QT_NO_SET_XCODE_INSTALL_PATH) + get_target_property(existing_install_path + "${target}" XCODE_ATTRIBUTE_INSTALL_PATH) + if(NOT existing_install_path) + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_INSTALL_PATH + "$(inherited)") + endif() + endif() +endfunction() + +function(_qt_internal_set_xcode_bundle_display_name target) + # We want the value of CFBundleDisplayName to be ${PRODUCT_NAME}, but we can't put that + # into the Info.plist.in template file directly, because the implicit configure_file(Info.plist) + # done by CMake is not using the @ONLY option, so CMake would treat the assignment as + # variable expansion. Escaping using backslashes does not help. + # Work around it by assigning the dollar char to a separate cache var, and expand it, so that + # the final value in the file will be ${PRODUCT_NAME}, to be evaluated at build time by Xcode. + set(QT_INTERNAL_DOLLAR_VAR "$" CACHE STRING "") +endfunction() + +# Adds ${PRODUCT_NAME} to the Info.plist file, which is then evaluated by Xcode itself. +function(_qt_internal_set_xcode_bundle_name target) + if(QT_NO_SET_XCODE_BUNDLE_NAME) + return() + endif() + + get_target_property(existing_bundle_name "${target}" MACOSX_BUNDLE_BUNDLE_NAME) + if(NOT MACOSX_BUNDLE_BUNDLE_NAME AND NOT existing_bundle_name) + if(CMAKE_GENERATOR STREQUAL Xcode) + set_target_properties("${target}" + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "$(PRODUCT_NAME)") + else() + set_target_properties("${target}" + PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "${target}") + endif() + endif() +endfunction() + +function(_qt_internal_set_xcode_bitcode_enablement target) + if(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE + OR QT_NO_SET_XCODE_ENABLE_BITCODE) + return() + endif() + + get_target_property(existing_bitcode_enablement + "${target}" XCODE_ATTRIBUTE_ENABLE_BITCODE) + if(NOT existing_bitcode_enablement MATCHES "-NOTFOUND") + return() + endif() + + # Disable bitcode to match Xcode 14's new default + set_target_properties("${target}" + PROPERTIES + XCODE_ATTRIBUTE_ENABLE_BITCODE + "NO") +endfunction() + +function(_qt_internal_copy_info_plist target) + # If the project already specifies a custom file, we don't override it. + get_target_property(info_plist_in "${target}" MACOSX_BUNDLE_INFO_PLIST) + if(NOT info_plist_in) + set(info_plist_in "${__qt_internal_cmake_apple_support_files_path}/Info.plist.app.in") + endif() + + string(MAKE_C_IDENTIFIER "${target}" target_identifier) + set(info_plist_out_dir + "${CMAKE_CURRENT_BINARY_DIR}/.qt/info_plist/${target_identifier}") + set(info_plist_out "${info_plist_out_dir}/Info.plist") + + # Check if we need to specify a custom launch screen storyboard entry. + get_target_property(launch_screen_base_name "${target}" _qt_ios_launch_screen_base_name) + if(launch_screen_base_name) + set(qt_ios_launch_screen_plist_entry "${launch_screen_base_name}") + endif() + + # Call configure_file to substitute Qt-specific @FOO@ values, not ${FOO} values. + # + # The output file will be another template file to be fed to CMake via the + # MACOSX_BUNDLE_INFO_PLIST property. CMake will then call configure_file on it to provide + # content for regular entries like CFBundleName, etc. + # + # We require this extra configure_file call so we can create unique Info.plist files for each + # target in a project, while also providing a way to add Qt specific entries that CMake + # does not support out of the box (e.g. a launch screen name). + configure_file( + "${info_plist_in}" + "${info_plist_out}" + @ONLY + ) + + set_target_properties("${target}" PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${info_plist_out}") +endfunction() + +function(_qt_internal_plist_buddy plist_file) + cmake_parse_arguments(PARSE_ARGV 1 arg + "" "OUTPUT_VARIABLE;ERROR_VARIABLE" "COMMANDS") + foreach(command ${arg_COMMANDS}) + execute_process(COMMAND "/usr/libexec/PlistBuddy" + -c "${command}" "${plist_file}" + OUTPUT_VARIABLE plist_buddy_output + ERROR_VARIABLE plist_buddy_error) + string(STRIP "${plist_buddy_output}" plist_buddy_output) + if(arg_OUTPUT_VARIABLE) + list(APPEND ${arg_OUTPUT_VARIABLE} ${plist_buddy_output}) + set(${arg_OUTPUT_VARIABLE} ${${arg_OUTPUT_VARIABLE}} PARENT_SCOPE) + endif() + if(arg_ERROR_VARIABLE) + list(APPEND ${arg_ERROR_VARIABLE} ${plist_buddy_error}) + set(${arg_ERROR_VARIABLE} ${${arg_ERROR_VARIABLE}} PARENT_SCOPE) + endif() + if(plist_buddy_error) + return() + endif() + endforeach() +endfunction() + +function(_qt_internal_set_apple_localizations target) + if(QT_NO_SET_PLIST_LOCALIZATIONS) + return() + endif() + + set(supported_languages "${QT_I18N_TRANSLATED_LANGUAGES}") + if("${QT_I18N_TRANSLATED_LANGUAGES}" STREQUAL "") + get_target_property(supported_languages "${target}" _qt_apple_supported_languages) + if("${supported_languages}" STREQUAL "supported_languages-NOTFOUND") + return() + endif() + endif() + get_target_property(plist_file "${target}" MACOSX_BUNDLE_INFO_PLIST) + if (NOT plist_file) + return() + endif() + + _qt_internal_plist_buddy("${plist_file}" + COMMANDS "print CFBundleLocalizations" + OUTPUT_VARIABLE existing_localizations + ) + if(existing_localizations) + return() + endif() + + list(TRANSFORM supported_languages PREPEND + "Add CFBundleLocalizations: string ") + + _qt_internal_plist_buddy("${plist_file}" + COMMANDS + "Add CFBundleLocalizations array" + ${supported_languages} + "Delete CFBundleAllowMixedLocalizations" + ) +endfunction() + +function(_qt_internal_set_ios_simulator_arch target) + if(CMAKE_XCODE_ATTRIBUTE_ARCHS + OR QT_NO_SET_XCODE_ARCHS) + return() + endif() + + get_target_property(existing_archs + "${target}" XCODE_ATTRIBUTE_ARCHS) + if(NOT existing_archs MATCHES "-NOTFOUND") + return() + endif() + + if(NOT x86_64 IN_LIST QT_OSX_ARCHITECTURES) + return() + endif() + + if(CMAKE_OSX_ARCHITECTURES AND NOT x86_64 IN_LIST CMAKE_OSX_ARCHITECTURES) + return() + endif() + + set_target_properties("${target}" + PROPERTIES + "XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*]" + "x86_64") +endfunction() + +# Export Apple platform sdk and xcode version requirements to Qt6ConfigExtras.cmake. +function(_qt_internal_export_apple_sdk_and_xcode_version_requirements out_var) + if(NOT APPLE) + return() + endif() + + if(IOS) + set(vars_to_assign + QT_SUPPORTED_MIN_IOS_SDK_VERSION + QT_SUPPORTED_MAX_IOS_SDK_VERSION + QT_SUPPORTED_MIN_IOS_XCODE_VERSION + ) + elseif(VISIONOS) + set(vars_to_assign + QT_SUPPORTED_MIN_VISIONOS_SDK_VERSION + QT_SUPPORTED_MAX_VISIONOS_SDK_VERSION + QT_SUPPORTED_MIN_VISIONOS_XCODE_VERSION + ) + else() + set(vars_to_assign + QT_SUPPORTED_MIN_MACOS_SDK_VERSION + QT_SUPPORTED_MAX_MACOS_SDK_VERSION + QT_SUPPORTED_MIN_MACOS_XCODE_VERSION + ) + endif() + + set(assignments "") + foreach(var IN LISTS vars_to_assign) + set(value "${${var}}") + list(APPEND assignments + " +if(NOT ${var}) + set(${var} \"${value}\") +endif()") + endforeach() + + list(JOIN assignments "\n" assignments) + set(${out_var} "${assignments}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_get_apple_sdk_version out_var) + if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL iOS) + set(sdk_name "iphoneos") + elseif(CMAKE_SYSTEM_NAME STREQUAL visionOS) + set(sdk_name "xros") + else() + # Default to macOS + set(sdk_name "macosx") + endif() + set(xcrun_version_arg "--show-sdk-version") + execute_process(COMMAND /usr/bin/xcrun --sdk ${sdk_name} ${xcrun_version_arg} + OUTPUT_VARIABLE sdk_version + ERROR_VARIABLE xcrun_error) + if(NOT sdk_version) + message(FATAL_ERROR + "Can't determine darwin ${sdk_name} SDK version. Error: ${xcrun_error}") + endif() + string(STRIP "${sdk_version}" sdk_version) + set(${out_var} "${sdk_version}" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_get_xcode_version_raw out_var) + if(APPLE) + execute_process(COMMAND /usr/bin/xcrun xcodebuild -version + OUTPUT_VARIABLE xcode_version + ERROR_VARIABLE xcrun_error) + string(REPLACE "\n" " " xcode_version "${xcode_version}") + string(STRIP "${xcode_version}" xcode_version) + set(${out_var} "${xcode_version}" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_get_xcode_version out_var) + if(APPLE) + _qt_internal_get_xcode_version_raw(xcode_version_raw) + + # The raw output is something like after the newlines are replaced with spaces: + # Xcode 14.3 Build version 14E222b + # We want only the '14.3' part. We could be more specific with the regex to match only + # digits separated by dots, but you never know how Apple might change the format. + string(REGEX REPLACE "Xcode (([^ ])+)" "\\2" xcode_version "${xcode_version_raw}") + if(xcode_version_raw MATCHES "Xcode ([^ ]+)") + set(xcode_version "${CMAKE_MATCH_1}") + else() + message(DEBUG "Failed to extract Xcode version from '${xcode_version_raw}'") + set(xcode_version "${xcode_version_raw}") + endif() + + set(${out_var} "${xcode_version}" PARENT_SCOPE) + endif() +endfunction() + +function(_qt_internal_get_cached_apple_sdk_version out_var) + if(QT_INTERNAL_APPLE_SDK_VERSION) + set(sdk_version "${QT_INTERNAL_APPLE_SDK_VERSION}") + else() + _qt_internal_get_apple_sdk_version(sdk_version) + set(QT_INTERNAL_APPLE_SDK_VERSION "${sdk_version}" CACHE STRING "Apple SDK version") + endif() + + set(${out_var} "${sdk_version}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_get_cached_xcode_version out_var) + if(QT_INTERNAL_XCODE_VERSION) + set(xcode_version "${QT_INTERNAL_XCODE_VERSION}") + else() + _qt_internal_get_xcode_version(xcode_version) + set(QT_INTERNAL_XCODE_VERSION "${xcode_version}" CACHE STRING "Xcode version") + endif() + + set(${out_var} "${xcode_version}" PARENT_SCOPE) +endfunction() + +# Warn when the platform SDK or Xcode version are not supported. +# +# The warnings are currently only shown when building Qt, not when building user projects +# with CMake. +# The warnings ARE shown for qmake user projects. +# +# The qmake equivalent for user projects is in mkspecs/features/mac/default_post.prf. +function(_qt_internal_check_apple_sdk_and_xcode_versions) + if(NOT APPLE) + return() + endif() + + if(QT_NO_APPLE_SDK_AND_XCODE_CHECK) + return() + endif() + + # Only run the check once in a top-level build. + get_property(check_done GLOBAL PROPERTY _qt_internal_apple_sdk_and_xcode_check_done) + if(check_done) + return() + endif() + set_property(GLOBAL PROPERTY _qt_internal_apple_sdk_and_xcode_check_done "TRUE") + + if(IOS) + set(min_sdk_version "${QT_SUPPORTED_MIN_IOS_SDK_VERSION}") + set(max_sdk_version "${QT_SUPPORTED_MAX_IOS_SDK_VERSION}") + set(min_xcode_version "${QT_SUPPORTED_MIN_IOS_XCODE_VERSION}") + elseif(VISIONOS) + set(min_sdk_version "${QT_SUPPORTED_MIN_VISIONOS_SDK_VERSION}") + set(max_sdk_version "${QT_SUPPORTED_MAX_VISIONOS_SDK_VERSION}") + set(min_xcode_version "${QT_SUPPORTED_MIN_VISIONOS_XCODE_VERSION}") + else() + set(min_sdk_version "${QT_SUPPORTED_MIN_MACOS_SDK_VERSION}") + set(max_sdk_version "${QT_SUPPORTED_MAX_MACOS_SDK_VERSION}") + set(min_xcode_version "${QT_SUPPORTED_MIN_MACOS_XCODE_VERSION}") + endif() + + _qt_internal_get_cached_apple_sdk_version(sdk_version) + _qt_internal_get_cached_xcode_version(xcode_version) + + if(NOT max_sdk_version MATCHES "^[0-9]+$") + message(FATAL_ERROR + "Invalid max SDK version: ${max_sdk_version} " + "It should be a major version number, without minor or patch version components.") + endif() + + # The default differs in different branches. + set(failed_check_should_error FALSE) + + if(failed_check_should_error) + # Allow downgrading the error into a warning. + if(QT_FORCE_WARN_APPLE_SDK_AND_XCODE_CHECK) + set(message_type WARNING) + set(extra_message " Due to QT_FORCE_WARN_APPLE_SDK_AND_XCODE_CHECK being ON " + "the build will continue, but it will likely fail. Use at your own risk.") + else() + set(message_type FATAL_ERROR) + set(extra_message " You can turn this error into a warning by configuring with " + "-DQT_FORCE_WARN_APPLE_SDK_AND_XCODE_CHECK=ON, but the build will likely fail. " + "Use at your own risk.") + endif() + else() + # Allow upgrading the warning into an error. + if(QT_FORCE_FATAL_APPLE_SDK_AND_XCODE_CHECK) + set(message_type FATAL_ERROR) + set(extra_message " Erroring out due to QT_FORCE_FATAL_APPLE_SDK_AND_XCODE_CHECK " + "being ON.") + else() + set(message_type WARNING) + set(extra_message " You can turn this warning into an error by configuring with " + "-DQT_FORCE_FATAL_APPLE_SDK_AND_XCODE_CHECK=ON. ") + endif() + endif() + + if(sdk_version VERSION_LESS min_sdk_version AND NOT QT_NO_APPLE_SDK_MIN_VERSION_CHECK) + message(${message_type} + "Qt requires at least version ${min_sdk_version} of the platform SDK, " + "you're building against version ${sdk_version}. Please upgrade." + ${extra_message} + ) + endif() + + if(xcode_version VERSION_LESS min_xcode_version AND NOT QT_NO_XCODE_MIN_VERSION_CHECK) + message(${message_type} + "Qt requires at least version ${min_xcode_version} of Xcode, " + "you're building against version ${xcode_version}. Please upgrade." + ${extra_message} + ) + endif() + + if(QT_NO_APPLE_SDK_MAX_VERSION_CHECK) + return() + endif() + + # Make sure we warn only when the current version is greater than the max supported version. + math(EXPR next_after_max_sdk_version "${max_sdk_version} + 1") + if(sdk_version VERSION_GREATER_EQUAL next_after_max_sdk_version) + message(WARNING + "Qt has only been tested with version ${max_sdk_version} " + "of the platform SDK, you're using ${sdk_version}. " + "This is an unsupported configuration. You may experience build issues, " + "and by using " + "the ${sdk_version} SDK you are opting in to new features " + "that Qt has not been prepared for. " + "Please downgrade the SDK you use to build your app to version " + "${max_sdk_version}, or configure " + "with -DQT_NO_APPLE_SDK_MAX_VERSION_CHECK=ON to silence this warning." + ) + endif() +endfunction() + +function(_qt_internal_finalize_apple_app target) + # Shared between macOS and UIKit apps + + _qt_internal_copy_info_plist("${target}") + _qt_internal_set_apple_localizations("${target}") + + # Only set the various properties if targeting the Xcode generator, otherwise the various + # Xcode tokens are embedded as-is instead of being dynamically evaluated. + # This affects things like the version number or application name as reported by Qt API. + if(CMAKE_GENERATOR STREQUAL "Xcode") + _qt_internal_set_xcode_development_team_id("${target}") + _qt_internal_set_xcode_code_sign_style("${target}") + _qt_internal_set_xcode_bundle_display_name("${target}") + _qt_internal_set_xcode_install_path("${target}") + endif() + + _qt_internal_set_xcode_bundle_name("${target}") + _qt_internal_set_apple_bundle_identifier("${target}") + _qt_internal_set_placeholder_apple_bundle_version("${target}") +endfunction() + +function(_qt_internal_finalize_uikit_app target) + if(CMAKE_SYSTEM_NAME STREQUAL iOS) + _qt_internal_finalize_ios_app("${target}") + else() + _qt_internal_finalize_apple_app("${target}") + endif() +endfunction() + +function(_qt_internal_finalize_ios_app target) + # Must be called before we generate the Info.plist + _qt_internal_handle_ios_launch_screen("${target}") + + _qt_internal_finalize_apple_app("${target}") + _qt_internal_set_xcode_targeted_device_family("${target}") + _qt_internal_set_xcode_bitcode_enablement("${target}") + _qt_internal_set_ios_simulator_arch("${target}") +endfunction() + +function(_qt_internal_finalize_macos_app target) + get_target_property(is_bundle ${target} MACOSX_BUNDLE) + if(NOT is_bundle) + return() + endif() + + _qt_internal_finalize_apple_app("${target}") + + # Make sure the install rpath has at least the minimum needed if the app + # has any non-static frameworks. We can't rigorously know if the app will + # have any, even with a static Qt, so always add this. If there are no + # frameworks, it won't do any harm. + get_property(install_rpath TARGET ${target} PROPERTY INSTALL_RPATH) + list(APPEND install_rpath "@executable_path/../Frameworks") + list(REMOVE_DUPLICATES install_rpath) + set_property(TARGET ${target} PROPERTY INSTALL_RPATH "${install_rpath}") +endfunction() |