diff options
Diffstat (limited to 'cmake/QtFeature.cmake')
-rw-r--r-- | cmake/QtFeature.cmake | 678 |
1 files changed, 537 insertions, 141 deletions
diff --git a/cmake/QtFeature.cmake b/cmake/QtFeature.cmake index f38d464221..96cb308b2c 100644 --- a/cmake/QtFeature.cmake +++ b/cmake/QtFeature.cmake @@ -1,10 +1,14 @@ -include(QtFeatureCommon) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + include(CheckCXXCompilerFlag) function(qt_feature_module_begin) - qt_parse_all_arguments(arg "qt_feature_module_begin" + cmake_parse_arguments(PARSE_ARGV 0 arg "NO_MODULE;ONLY_EVALUATE_FEATURES" - "LIBRARY;PRIVATE_FILE;PUBLIC_FILE" "PUBLIC_DEPENDENCIES;PRIVATE_DEPENDENCIES" ${ARGN}) + "LIBRARY;PRIVATE_FILE;PUBLIC_FILE" + "PUBLIC_DEPENDENCIES;PRIVATE_DEPENDENCIES") + _qt_internal_validate_all_args_are_parsed(arg) if(NOT arg_ONLY_EVALUATE_FEATURES) if ("${arg_LIBRARY}" STREQUAL "" AND (NOT ${arg_NO_MODULE})) @@ -43,9 +47,11 @@ function(qt_feature feature) qt_feature_normalize_name("${feature}" feature) set_property(GLOBAL PROPERTY QT_FEATURE_ORIGINAL_NAME_${feature} "${original_name}") - qt_parse_all_arguments(arg "qt_feature" + cmake_parse_arguments(PARSE_ARGV 1 arg "PRIVATE;PUBLIC" - "LABEL;PURPOSE;SECTION;" "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" ${ARGN}) + "LABEL;PURPOSE;SECTION" + "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF") + _qt_internal_validate_all_args_are_parsed(arg) set(_QT_FEATURE_DEFINITION_${feature} ${ARGN} PARENT_SCOPE) @@ -74,45 +80,25 @@ function(qt_evaluate_to_boolean expressionVar) endif() endfunction() -function(qt_evaluate_config_expression resultVar) +function(qt_internal_evaluate_config_expression resultVar outIdx startIdx) set(result "") - set(nestingLevel 0) - set(skipNext OFF) set(expression "${ARGN}") list(LENGTH expression length) + math(EXPR memberIdx "${startIdx} - 1") math(EXPR length "${length}-1") - foreach(memberIdx RANGE ${length}) - if(${skipNext}) - set(skipNext OFF) - continue() - endif() - + while(memberIdx LESS ${length}) + math(EXPR memberIdx "${memberIdx} + 1") list(GET expression ${memberIdx} member) if("${member}" STREQUAL "(") - if(${nestingLevel} GREATER 0) - list(APPEND result ${member}) - endif() - math(EXPR nestingLevel "${nestingLevel} + 1") - continue() + math(EXPR memberIdx "${memberIdx} + 1") + qt_internal_evaluate_config_expression(sub_result memberIdx ${memberIdx} ${expression}) + list(APPEND result ${sub_result}) elseif("${member}" STREQUAL ")") - math(EXPR nestingLevel "${nestingLevel} - 1") - if(nestingLevel LESS 0) - break() - endif() - if(${nestingLevel} EQUAL 0) - qt_evaluate_config_expression(result ${result}) - else() - list(APPEND result ${member}) - endif() - continue() - elseif(${nestingLevel} GREATER 0) - list(APPEND result ${member}) - continue() + break() elseif("${member}" STREQUAL "NOT") list(APPEND result ${member}) - continue() elseif("${member}" STREQUAL "AND") qt_evaluate_to_boolean(result) if(NOT ${result}) @@ -137,7 +123,7 @@ function(qt_evaluate_config_expression resultVar) set(lhs "${${lhs}}") math(EXPR rhsIndex "${memberIdx}+1") - set(skipNext ON) + set(memberIdx ${rhsIndex}) list(GET expression ${rhsIndex} rhs) # We can't pass through an empty string with double quotes through various @@ -157,7 +143,7 @@ function(qt_evaluate_config_expression resultVar) list(APPEND result ${member}) endif() - endforeach() + endwhile() # The 'TARGET Gui' case is handled by qt_evaluate_to_boolean, by passing those tokens verbatim # to if(). @@ -167,18 +153,48 @@ function(qt_evaluate_config_expression resultVar) qt_evaluate_to_boolean(result) endif() + # When in recursion, we must skip to the next closing parenthesis on nesting level 0. The outIdx + # must point to the matching closing parenthesis, and that's not the case if we're early exiting + # in AND/OR. + if(startIdx GREATER 0) + set(nestingLevel 1) + while(TRUE) + list(GET expression ${memberIdx} member) + if("${member}" STREQUAL ")") + math(EXPR nestingLevel "${nestingLevel} - 1") + if(nestingLevel EQUAL 0) + break() + endif() + elseif("${member}" STREQUAL "(") + math(EXPR nestingLevel "${nestingLevel} + 1") + endif() + math(EXPR memberIdx "${memberIdx} + 1") + endwhile() + endif() + + set(${outIdx} ${memberIdx} PARENT_SCOPE) set(${resultVar} ${result} PARENT_SCOPE) endfunction() -function(_qt_internal_dump_expression_values expression_dump expression) - set(dump "") - set(skipNext FALSE) - set(isTargetExpression FALSE) +function(qt_evaluate_config_expression resultVar) + qt_internal_evaluate_config_expression(result unused 0 ${ARGN}) + set("${resultVar}" "${result}" PARENT_SCOPE) +endfunction() +function(_qt_internal_get_feature_condition_keywords out_var) set(keywords "EQUAL" "LESS" "LESS_EQUAL" "GREATER" "GREATER_EQUAL" "STREQUAL" "STRLESS" "STRLESS_EQUAL" "STRGREATER" "STRGREATER_EQUAL" "VERSION_EQUAL" "VERSION_LESS" "VERSION_LESS_EQUAL" "VERSION_GREATER" "VERSION_GREATER_EQUAL" "MATCHES" "EXISTS" "COMMAND" "DEFINED" "NOT" "AND" "OR" "TARGET" "EXISTS" "IN_LIST" "(" ")") + set(${out_var} "${keywords}" PARENT_SCOPE) +endfunction() + +function(_qt_internal_dump_expression_values expression_dump expression) + set(dump "") + set(skipNext FALSE) + set(isTargetExpression FALSE) + + _qt_internal_get_feature_condition_keywords(keywords) list(LENGTH expression length) math(EXPR length "${length}-1") @@ -227,54 +243,83 @@ function(_qt_internal_dump_expression_values expression_dump expression) set(${expression_dump} "${${expression_dump}}" PARENT_SCOPE) endfunction() -function(qt_feature_set_cache_value resultVar feature emit_if condition calculated label) +# Stores the user provided value to FEATURE_${feature} if provided. +# If not provided, stores ${computed} instead. +# ${computed} is also stored when reconfiguring and the condition does not align with the user +# provided value. +# +function(qt_feature_check_and_save_user_provided_value + resultVar feature condition condition_expression computed label) if (DEFINED "FEATURE_${feature}") - # Must set up the cache - if (NOT (emit_if)) - message(FATAL_ERROR "Sanity check failed: FEATURE_${feature} that was not emitted was found in the CMakeCache.") - endif() + # Revisit new user provided value + set(user_value "${FEATURE_${feature}}") + string(TOUPPER "${user_value}" user_value_upper) + set(result "${user_value_upper}") - # Revisit value: - set(cache "${FEATURE_${feature}}") - - # If the build is marked as dirty and the cache value doesn't meet the new condition, - # reset it to the calculated one. + # If ${feature} depends on another dirty feature, reset the ${feature} value to + # ${computed}. get_property(dirty_build GLOBAL PROPERTY _qt_dirty_build) - if(NOT condition AND cache AND dirty_build) - set(cache "${calculated}") - message(WARNING "Reset FEATURE_${feature} value to ${calculated}, because it doesn't \ -meet its condition after reconfiguration.") + if(dirty_build) + _qt_internal_feature_compute_feature_dependencies(deps "${feature}") + if(deps) + get_property(dirty_features GLOBAL PROPERTY _qt_dirty_features) + foreach(dirty_feature ${dirty_features}) + if(dirty_feature IN_LIST deps AND NOT "${result}" STREQUAL "${computed}") + set(result "${computed}") + message(WARNING + "Auto-resetting 'FEATURE_${feature}' from '${user_value_upper}' to " + "'${computed}', " + "because the dependent feature '${dirty_feature}' was marked dirty.") + + # Append ${feature} as a new dirty feature. + set_property(GLOBAL APPEND PROPERTY _qt_dirty_features "${feature}") + break() + endif() + endforeach() + endif() + + # If the build is marked as dirty and the feature doesn't meet its condition, + # reset its value to the computed one, which is likely OFF. + if(NOT condition AND result) + set(result "${computed}") + message(WARNING "Resetting 'FEATURE_${feature}' from '${user_value_upper}' to " + "'${computed}' because it doesn't meet its condition after reconfiguration. " + "Condition expression is: '${condition_expression}'") + endif() endif() set(bool_values OFF NO FALSE N ON YES TRUE Y) - if ((cache IN_LIST bool_values) OR (cache GREATER_EQUAL 0)) - set(result "${cache}") + if ((result IN_LIST bool_values) OR (result GREATER_EQUAL 0)) + # All good! else() - message(FATAL_ERROR "Sanity check failed: FEATURE_${feature} has invalid value \"${cache}\"!") + message(FATAL_ERROR + "Sanity check failed: FEATURE_${feature} has invalid value \"${result}\"!") endif() # Fix-up user-provided values - set("FEATURE_${feature}" "${cache}" CACHE BOOL "${label}" FORCE) + set("FEATURE_${feature}" "${result}" CACHE BOOL "${label}" FORCE) else() # Initial setup: - if (emit_if) - set("FEATURE_${feature}" "${calculated}" CACHE BOOL "${label}") - set(result "${calculated}") - else() - set(result OFF) - endif() + set(result "${computed}") + set("FEATURE_${feature}" "${result}" CACHE BOOL "${label}") endif() set("${resultVar}" "${result}" PARENT_SCOPE) endfunction() -macro(qt_feature_set_value feature cache condition label conditionExpression) - set(result "${cache}") +# Saves the final user value to QT_FEATURE_${feature}, after checking that the condition is met. +macro(qt_feature_check_and_save_internal_value + feature saved_user_value condition label conditionExpression) + if(${saved_user_value}) + set(result ON) + else() + set(result OFF) + endif() - if (NOT (condition) AND (cache)) + if ((NOT condition) AND result) _qt_internal_dump_expression_values(conditionDump "${conditionExpression}") string(JOIN " " conditionString ${conditionExpression}) - qt_configure_add_report_error("Feature \"${feature}\": Forcing to \"${cache}\" breaks its \ + qt_configure_add_report_error("Feature \"${feature}\": Forcing to \"${result}\" breaks its \ condition:\n ${conditionString}\nCondition values dump:\n ${conditionDump}\n" RECORD_ON_FEATURE_EVALUATION) endif() @@ -288,6 +333,38 @@ condition:\n ${conditionString}\nCondition values dump:\n ${conditionDump} set(QT_KNOWN_FEATURES "${QT_KNOWN_FEATURES}" CACHE INTERNAL "" FORCE) endmacro() +macro(_qt_internal_parse_feature_definition feature) + cmake_parse_arguments(arg + "PRIVATE;PUBLIC" + "LABEL;PURPOSE;SECTION;" + "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" + ${_QT_FEATURE_DEFINITION_${feature}}) +endmacro() + + +# The build system stores 2 CMake cache variables for each feature, to allow detecting value changes +# during subsequent reconfigurations. +# +# +# `FEATURE_foo` stores the user provided feature value for the current configuration run. +# It can be set directly by the user. +# +# If a value is not provided on initial configuration, the value will be auto-computed based on the +# various conditions of the feature. +# TODO: Document the various conditions and how they relate to each other. +# +# +# `QT_FEATURE_foo` stores the value of the feature from the previous configuration run. +# Its value is updated once with the newest user provided value after some checks are performed. +# +# This variable also serves as the main source of truth throughout the build system code to check +# if the feature is enabled, e.g. if(QT_FEATURE_foo) +# +# It is not meant to be set by the user. It is only modified by the build system. +# +# Comparing the values of QT_FEATURE_foo and FEATURE_foo, the build system can detect whether +# the user changed the value for a feature and thus recompute any dependent features. +# function(qt_evaluate_feature feature) # If the feature was already evaluated as dependency nothing to do here. if(DEFINED "QT_FEATURE_${feature}") @@ -299,9 +376,7 @@ function(qt_evaluate_feature feature) message(FATAL_ERROR "Attempting to evaluate feature ${feature} but its definition is missing. Either the feature does not exist or a dependency to the module that defines it is missing") endif() - cmake_parse_arguments(arg - "PRIVATE;PUBLIC" - "LABEL;PURPOSE;SECTION;" "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" ${_QT_FEATURE_DEFINITION_${feature}}) + _qt_internal_parse_feature_definition("${feature}") if("${arg_ENABLE}" STREQUAL "") set(arg_ENABLE OFF) @@ -325,12 +400,12 @@ function(qt_evaluate_feature feature) qt_evaluate_config_expression(enable_result ${arg_ENABLE}) qt_evaluate_config_expression(auto_detect ${arg_AUTODETECT}) if(${disable_result}) - set(result OFF) + set(computed OFF) elseif((${enable_result}) OR (${auto_detect})) - set(result ${condition}) + set(computed ${condition}) else() # feature not auto-detected and not explicitly enabled - set(result OFF) + set(computed OFF) endif() if("${arg_EMIT_IF}" STREQUAL "") @@ -339,29 +414,97 @@ function(qt_evaluate_feature feature) qt_evaluate_config_expression(emit_if ${arg_EMIT_IF}) endif() - # If FEATURE_ is not defined trying to use INPUT_ variable to enable/disable feature. - if ((NOT DEFINED "FEATURE_${feature}") AND (DEFINED "INPUT_${feature}") - AND (NOT "${INPUT_${feature}}" STREQUAL "undefined") - AND (NOT "${INPUT_${feature}}" STREQUAL "")) - if(INPUT_${feature}) - set(FEATURE_${feature} ON) - else() - set(FEATURE_${feature} OFF) - endif() + # Warn about a feature which is not emitted, but the user explicitly provided a value for it. + if(NOT emit_if AND DEFINED FEATURE_${feature}) + set(msg "") + string(APPEND msg + "Feature ${feature} is insignificant in this configuration, " + "ignoring related command line option(s).") + qt_configure_add_report_entry(TYPE WARNING MESSAGE "${msg}") + + # Remove the cache entry so that the warning is not persisted and shown on every + # reconfiguration. + unset(FEATURE_${feature} CACHE) + endif() + + # Only save the user provided value if the feature was emitted. + if(emit_if) + qt_feature_check_and_save_user_provided_value( + saved_user_value + "${feature}" "${condition}" "${arg_CONDITION}" "${computed}" "${arg_LABEL}") + else() + # Make sure the feature internal value is OFF if not emitted. + set(saved_user_value OFF) endif() - qt_feature_set_cache_value(cache "${feature}" "${emit_if}" "${condition}" "${result}" - "${arg_LABEL}") - qt_feature_set_value("${feature}" "${cache}" "${condition}" "${arg_LABEL}" - "${arg_CONDITION}") + qt_feature_check_and_save_internal_value( + "${feature}" "${saved_user_value}" "${condition}" "${arg_LABEL}" "${arg_CONDITION}") # Store each feature's label for summary info. set(QT_FEATURE_LABEL_${feature} "${arg_LABEL}" CACHE INTERNAL "") endfunction() +# Collect feature names that ${feature} depends on, by inspecting the given expression. +function(_qt_internal_feature_extract_feature_dependencies_from_expression out_var expression) + list(LENGTH expression length) + math(EXPR length "${length}-1") + + if(length LESS 0) + set(${out_var} "" PARENT_SCOPE) + return() + endif() + + set(deps "") + + foreach(memberIdx RANGE ${length}) + list(GET expression ${memberIdx} member) + if(member MATCHES "^QT_FEATURE_(.+)") + list(APPEND deps "${CMAKE_MATCH_1}") + endif() + endforeach() + set(${out_var} "${deps}" PARENT_SCOPE) +endfunction() + +# Collect feature names that ${feature} depends on, based on feature names that appear +# in the ${feature}'s condition expressions. +function(_qt_internal_feature_compute_feature_dependencies out_var feature) + # Only compute the deps once per feature. + get_property(deps_computed GLOBAL PROPERTY _qt_feature_deps_computed_${feature}) + if(deps_computed) + get_property(deps GLOBAL PROPERTY _qt_feature_deps_${feature}) + set(${out_var} "${deps}" PARENT_SCOPE) + return() + endif() + + _qt_internal_parse_feature_definition("${feature}") + + set(options_to_check AUTODETECT CONDITION ENABLE DISABLE EMIT_IF) + set(deps "") + + # Go through each option that takes condition expressions and collect the feature names. + foreach(option ${options_to_check}) + set(option_value "${arg_${option}}") + if(option_value) + _qt_internal_feature_extract_feature_dependencies_from_expression( + option_deps "${option_value}") + if(option_deps) + list(APPEND deps ${option_deps}) + endif() + endif() + endforeach() + + set_property(GLOBAL PROPERTY _qt_feature_deps_computed_${feature} TRUE) + set_property(GLOBAL PROPERTY _qt_feature_deps_${feature} "${deps}") + set(${out_var} "${deps}" PARENT_SCOPE) +endfunction() + function(qt_feature_config feature config_var_name) qt_feature_normalize_name("${feature}" feature) - qt_parse_all_arguments(arg "qt_feature_config" "NEGATE" "NAME" "" ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 2 arg + "NEGATE" + "NAME" + "") + _qt_internal_validate_all_args_are_parsed(arg) # Store all the config related info in a unique variable key. set(key_name "_QT_FEATURE_CONFIG_DEFINITION_${feature}_${config_var_name}") @@ -421,7 +564,11 @@ endfunction() function(qt_feature_definition feature name) qt_feature_normalize_name("${feature}" feature) - qt_parse_all_arguments(arg "qt_feature_definition" "NEGATE" "VALUE;PREREQUISITE" "" ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 2 arg + "NEGATE" + "VALUE;PREREQUISITE" + "") + _qt_internal_validate_all_args_are_parsed(arg) # Store all the define related info in a unique variable key. set(key_name "_QT_FEATURE_DEFINE_DEFINITION_${feature}_${name}") @@ -448,9 +595,14 @@ function(qt_evaluate_feature_definition key) set(expected OFF) endif() + set(actual OFF) + if(QT_FEATURE_${arg_FEATURE}) + set(actual ON) + endif() + set(msg "") - if(QT_FEATURE_${arg_FEATURE} STREQUAL expected) + if(actual STREQUAL expected) set(indent "") if(arg_PREREQUISITE) string(APPEND msg "#if ${arg_PREREQUISITE}\n") @@ -472,7 +624,11 @@ function(qt_evaluate_feature_definition key) endfunction() function(qt_extra_definition name value) - qt_parse_all_arguments(arg "qt_extra_definition" "PUBLIC;PRIVATE" "" "" ${ARGN}) + cmake_parse_arguments(PARSE_ARGV 2 arg + "PUBLIC;PRIVATE" + "" + "") + _qt_internal_validate_all_args_are_parsed(arg) if (arg_PUBLIC) string(APPEND __QtFeature_public_extra "\n#define ${name} ${value}\n") @@ -522,6 +678,17 @@ function(qt_feature_evaluate_features list_of_paths) qt_feature_module_end(ONLY_EVALUATE_FEATURES) endfunction() +function(qt_feature_record_summary_entries list_of_paths) + # Clean up any stale state just in case. + qt_feature_unset_state_vars() + + set(__QtFeature_only_record_summary_entries TRUE) + foreach(path ${list_of_paths}) + include("${path}") + endforeach() + qt_feature_unset_state_vars() +endfunction() + function(qt_feature_module_end) set(flags ONLY_EVALUATE_FEATURES) set(options OUT_VAR_PREFIX) @@ -544,10 +711,10 @@ function(qt_feature_module_end) # Evaluate custom cache assignments. foreach(cache_var_name ${__QtFeature_custom_enabled_cache_variables}) - set(${cache_var_name} ON CACHE BOOL "Force enabled by platform." FORCE) + set(${cache_var_name} ON CACHE BOOL "Force enabled by platform requirements." FORCE) endforeach() foreach(cache_var_name ${__QtFeature_custom_disabled_cache_variables}) - set(${cache_var_name} OFF CACHE BOOL "Force disabled by platform." FORCE) + set(${cache_var_name} OFF CACHE BOOL "Force disabled by platform requirements." FORCE) endforeach() set(enabled_public_features "") @@ -595,29 +762,13 @@ function(qt_feature_module_end) ) endif() - # Extra header injections which have to have forwarding headers created by - # qt_install_injections. - # Skip creating forwarding headers if qt_feature_module_begin was called with NO_MODULE, aka - # there is no include/<module_name> so there's no place to put the forwarding headers. - if(__QtFeature_library) - set(injections "") - qt_compute_injection_forwarding_header("${__QtFeature_library}" - SOURCE "${__QtFeature_public_file}" - OUT_VAR injections) - qt_compute_injection_forwarding_header("${__QtFeature_library}" - SOURCE "${__QtFeature_private_file}" PRIVATE - OUT_VAR injections) - - set(${arg_OUT_VAR_PREFIX}extra_library_injections ${injections} PARENT_SCOPE) - endif() - if (NOT ("${target}" STREQUAL "NO_MODULE") AND NOT arg_ONLY_EVALUATE_FEATURES) get_target_property(targetType "${target}" TYPE) if("${targetType}" STREQUAL "INTERFACE_LIBRARY") set(propertyPrefix "INTERFACE_") else() set(propertyPrefix "") - set_property(TARGET "${target}" APPEND PROPERTY EXPORT_PROPERTIES "QT_ENABLED_PUBLIC_FEATURES;QT_DISABLED_PUBLIC_FEATURES;QT_ENABLED_PRIVATE_FEATURES;QT_DISABLED_PRIVATE_FEATURES;MODULE_PLUGIN_TYPES;QT_QMAKE_PUBLIC_CONFIG;QT_QMAKE_PRIVATE_CONFIG;QT_QMAKE_PUBLIC_QT_CONFIG") + set_property(TARGET "${target}" APPEND PROPERTY EXPORT_PROPERTIES "QT_ENABLED_PUBLIC_FEATURES;QT_DISABLED_PUBLIC_FEATURES;QT_ENABLED_PRIVATE_FEATURES;QT_DISABLED_PRIVATE_FEATURES;QT_QMAKE_PUBLIC_CONFIG;QT_QMAKE_PRIVATE_CONFIG;QT_QMAKE_PUBLIC_QT_CONFIG") endif() foreach(visibility public private) string(TOUPPER "${visibility}" capitalVisibility) @@ -660,6 +811,10 @@ function(qt_feature_module_end) qt_feature_copy_global_config_features_to_core(${target}) endif() + qt_feature_unset_state_vars() +endfunction() + +macro(qt_feature_unset_state_vars) unset(__QtFeature_library PARENT_SCOPE) unset(__QtFeature_public_features PARENT_SCOPE) unset(__QtFeature_private_features PARENT_SCOPE) @@ -675,7 +830,8 @@ function(qt_feature_module_end) unset(__QtFeature_custom_enabled_features PARENT_SCOPE) unset(__QtFeature_custom_disabled_features PARENT_SCOPE) unset(__QtFeature_only_evaluate_features PARENT_SCOPE) -endfunction() + unset(__QtFeature_only_record_summary_entries PARENT_SCOPE) +endmacro() function(qt_feature_copy_global_config_features_to_core target) # CMake doesn't support setting custom properties on exported INTERFACE libraries @@ -714,13 +870,47 @@ function(qt_feature_copy_global_config_features_to_core target) endif() endfunction() +function(qt_internal_detect_dirty_features) + # We need to clean up QT_FEATURE_*, but only once per configuration cycle + get_property(qt_feature_clean GLOBAL PROPERTY _qt_feature_clean) + if(NOT qt_feature_clean AND NOT QT_NO_FEATURE_AUTO_RESET) + message(STATUS "Checking for feature set changes") + set_property(GLOBAL PROPERTY _qt_feature_clean TRUE) + foreach(feature ${QT_KNOWN_FEATURES}) + if(DEFINED "FEATURE_${feature}" AND + NOT "${QT_FEATURE_${feature}}" STREQUAL "${FEATURE_${feature}}") + message(" '${feature}' was changed from ${QT_FEATURE_${feature}} " + "to ${FEATURE_${feature}}") + set(dirty_build TRUE) + set_property(GLOBAL APPEND PROPERTY _qt_dirty_features "${feature}") + endif() + unset("QT_FEATURE_${feature}" CACHE) + endforeach() + + set(QT_KNOWN_FEATURES "" CACHE INTERNAL "" FORCE) + + if(dirty_build) + set_property(GLOBAL PROPERTY _qt_dirty_build TRUE) + message(WARNING + "Due to detected feature set changes, dependent features " + "will be re-computed automatically. This might cause a lot of files to be rebuilt. " + "To disable this behavior, configure with -DQT_NO_FEATURE_AUTO_RESET=ON") + endif() + endif() +endfunction() + +# Builds either a string of source code or a whole project to determine whether the build is +# successful. +# +# Sets a TEST_${name}_OUTPUT variable with the build output, to the scope of the calling function. +# Sets a TEST_${name} cache variable to either TRUE or FALSE if the build is successful or not. function(qt_config_compile_test name) if(DEFINED "TEST_${name}") return() endif() cmake_parse_arguments(arg "" "LABEL;PROJECT_PATH;C_STANDARD;CXX_STANDARD" - "COMPILE_OPTIONS;LIBRARIES;CODE;PACKAGES" ${ARGN}) + "COMPILE_OPTIONS;LIBRARIES;CODE;PACKAGES;CMAKE_FLAGS" ${ARGN}) if(arg_PROJECT_PATH) message(STATUS "Performing Test ${arg_LABEL}") @@ -732,7 +922,19 @@ function(qt_config_compile_test name) # If the repo has its own cmake modules, include those in the module path, so that various # find_package calls work. if(EXISTS "${PROJECT_SOURCE_DIR}/cmake") - list(APPEND flags "-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/cmake") + set(must_append_module_path_flag TRUE) + set(flags_copy "${flags}") + set(flags) + foreach(flag IN LISTS flags_copy) + if(flag MATCHES "^-DCMAKE_MODULE_PATH:STRING=") + set(must_append_module_path_flag FALSE) + set(flag "${flag}\\;${PROJECT_SOURCE_DIR}/cmake") + endif() + list(APPEND flags "${flag}") + endforeach() + if(must_append_module_path_flag) + list(APPEND flags "-DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/cmake") + endif() endif() # Pass which packages need to be found. @@ -799,8 +1001,36 @@ function(qt_config_compile_test name) endif() endif() - try_compile(HAVE_${name} "${CMAKE_BINARY_DIR}/config.tests/${name}" "${arg_PROJECT_PATH}" - "${name}" CMAKE_FLAGS ${flags}) + # Pass override values for CMAKE_SYSTEM_{PREFIX|FRAMEWORK}_PATH. + if(DEFINED QT_CMAKE_SYSTEM_PREFIX_PATH_BACKUP) + set(path_list ${CMAKE_SYSTEM_PREFIX_PATH}) + string(REPLACE ";" "\\;" path_list "${path_list}") + list(APPEND flags "-DQT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_PREFIX_PATH=${path_list}") + endif() + if(DEFINED QT_CMAKE_SYSTEM_FRAMEWORK_PATH_BACKUP) + set(path_list ${CMAKE_SYSTEM_FRAMEWORK_PATH}) + string(REPLACE ";" "\\;" path_list "${path_list}") + list(APPEND flags "-DQT_CONFIG_COMPILE_TEST_CMAKE_SYSTEM_FRAMEWORK_PATH=${path_list}") + endif() + + if(NOT arg_CMAKE_FLAGS) + set(arg_CMAKE_FLAGS "") + endif() + + # CI passes the project dir of the Qt repository as absolute path without drive letter: + # \Users\qt\work\qt\qtbase + # Ensure that arg_PROJECT_PATH is an absolute path with drive letter: + # C:/Users/qt/work/qt/qtbase + # This works around CMake upstream issue #22534. + if(CMAKE_HOST_WIN32) + get_filename_component(arg_PROJECT_PATH "${arg_PROJECT_PATH}" REALPATH) + endif() + + try_compile( + HAVE_${name} "${CMAKE_BINARY_DIR}/config.tests/${name}" "${arg_PROJECT_PATH}" "${name}" + CMAKE_FLAGS ${flags} ${arg_CMAKE_FLAGS} + OUTPUT_VARIABLE try_compile_output + ) if(${HAVE_${name}}) set(status_label "Success") @@ -815,6 +1045,7 @@ function(qt_config_compile_test name) # fail instead of cmake abort later via CMAKE_REQUIRED_LIBRARIES. string(FIND "${library}" "::" cmake_target_namespace_separator) if(NOT cmake_target_namespace_separator EQUAL -1) + message(STATUS "Performing Test ${arg_LABEL} - Failed because ${library} not found") set(HAVE_${name} FALSE) break() endif() @@ -823,19 +1054,31 @@ function(qt_config_compile_test name) if(NOT DEFINED HAVE_${name}) set(_save_CMAKE_C_STANDARD "${CMAKE_C_STANDARD}") + set(_save_CMAKE_C_STANDARD_REQUIRED "${CMAKE_C_STANDARD_REQUIRED}") set(_save_CMAKE_CXX_STANDARD "${CMAKE_CXX_STANDARD}") + set(_save_CMAKE_CXX_STANDARD_REQUIRED "${CMAKE_CXX_STANDARD_REQUIRED}") set(_save_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(_save_CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}") if(arg_C_STANDARD) set(CMAKE_C_STANDARD "${arg_C_STANDARD}") + set(CMAKE_C_STANDARD_REQUIRED OFF) endif() if(arg_CXX_STANDARD) - set(CMAKE_CXX_STANDARD "${arg_CXX_STANDARD}") + if(${arg_CXX_STANDARD} LESS 23 OR ${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20") + set(CMAKE_CXX_STANDARD "${arg_CXX_STANDARD}") + set(CMAKE_CXX_STANDARD_REQUIRED OFF) + endif() endif() set(CMAKE_REQUIRED_FLAGS ${arg_COMPILE_OPTIONS}) + # Pass -stdlib=libc++ on if necessary + if (QT_FEATURE_stdlib_libcpp) + list(APPEND CMAKE_REQUIRED_FLAGS "-stdlib=libc++") + endif() + # For MSVC we need to explicitly pass -Zc:__cplusplus to get correct __cplusplus # define values. According to common/msvc-version.conf the flag is supported starting # with 1913. @@ -846,17 +1089,38 @@ function(qt_config_compile_test name) list(APPEND CMAKE_REQUIRED_FLAGS "-Zc:__cplusplus") endif() + # Let CMake load our custom platform modules. + if(NOT QT_AVOID_CUSTOM_PLATFORM_MODULES) + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CMAKE_MODULE_PATH) + endif() + set(_save_CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}") set(CMAKE_REQUIRED_LIBRARIES "${arg_LIBRARIES}") - check_cxx_source_compiles("${arg_UNPARSED_ARGUMENTS} ${arg_CODE}" HAVE_${name}) + + # OUTPUT_VARIABLE is an internal undocumented variable of check_cxx_source_compiles + # since 3.23. Allow an opt out in case this breaks in the future. + set(try_compile_output "") + set(output_var "") + if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.23" + AND NOT QT_INTERNAL_NO_TRY_COMPILE_OUTPUT_VARIABLE) + set(output_var OUTPUT_VARIABLE try_compile_output) + endif() + + check_cxx_source_compiles( + "${arg_UNPARSED_ARGUMENTS} ${arg_CODE}" HAVE_${name} ${output_var} + ) set(CMAKE_REQUIRED_LIBRARIES "${_save_CMAKE_REQUIRED_LIBRARIES}") set(CMAKE_C_STANDARD "${_save_CMAKE_C_STANDARD}") + set(CMAKE_C_STANDARD_REQUIRED "${_save_CMAKE_C_STANDARD_REQUIRED}") set(CMAKE_CXX_STANDARD "${_save_CMAKE_CXX_STANDARD}") + set(CMAKE_CXX_STANDARD_REQUIRED "${_save_CMAKE_CXX_STANDARD_REQUIRED}") set(CMAKE_REQUIRED_FLAGS "${_save_CMAKE_REQUIRED_FLAGS}") + set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES "${_save_CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}") endif() endif() + set(TEST_${name}_OUTPUT "${try_compile_output}" PARENT_SCOPE) set(TEST_${name} "${HAVE_${name}}" CACHE INTERNAL "${arg_LABEL}") endfunction() @@ -867,6 +1131,18 @@ function(qt_get_platform_try_compile_vars out_var) # Use the regular variables that are used for source-based try_compile() calls. set(flags "${CMAKE_TRY_COMPILE_PLATFORM_VARIABLES}") + # Pass custom flags. + list(APPEND flags "CMAKE_C_FLAGS") + list(APPEND flags "CMAKE_C_FLAGS_DEBUG") + list(APPEND flags "CMAKE_C_FLAGS_RELEASE") + list(APPEND flags "CMAKE_C_FLAGS_RELWITHDEBINFO") + list(APPEND flags "CMAKE_CXX_FLAGS") + list(APPEND flags "CMAKE_CXX_FLAGS_DEBUG") + list(APPEND flags "CMAKE_CXX_FLAGS_RELEASE") + list(APPEND flags "CMAKE_CXX_FLAGS_RELWITHDEBINFO") + list(APPEND flags "CMAKE_OBJCOPY") + list(APPEND flags "CMAKE_EXE_LINKER_FLAGS") + # Pass toolchain files. if(CMAKE_TOOLCHAIN_FILE) list(APPEND flags "CMAKE_TOOLCHAIN_FILE") @@ -877,7 +1153,18 @@ function(qt_get_platform_try_compile_vars out_var) # Pass language standard flags. list(APPEND flags "CMAKE_C_STANDARD") + list(APPEND flags "CMAKE_C_STANDARD_REQUIRED") list(APPEND flags "CMAKE_CXX_STANDARD") + list(APPEND flags "CMAKE_CXX_STANDARD_REQUIRED") + + # Pass -stdlib=libc++ on if necessary + if (QT_FEATURE_stdlib_libcpp) + if(CMAKE_CXX_FLAGS) + string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++") + else() + set(CMAKE_CXX_FLAGS "-stdlib=libc++") + endif() + endif() # Assemble the list with regular options. set(flags_cmd_line "") @@ -887,12 +1174,16 @@ function(qt_get_platform_try_compile_vars out_var) endif() endforeach() + # Let CMake load our custom platform modules. + if(NOT QT_AVOID_CUSTOM_PLATFORM_MODULES) + list(APPEND flags_cmd_line "-DCMAKE_MODULE_PATH:STRING=${QT_CMAKE_DIR}/platforms") + endif() + # Pass darwin specific options. # The architectures need to be passed explicitly to project-based try_compile calls even on # macOS, so that arm64 compilation works on Apple silicon. - if(CMAKE_OSX_ARCHITECTURES) - list(GET CMAKE_OSX_ARCHITECTURES 0 osx_first_arch) - + qt_internal_get_first_osx_arch(osx_first_arch) + if(osx_first_arch) # Do what qmake does, aka when doing a simulator_and_device build, build the # target architecture test only with the first given architecture, which should be the # device architecture, aka some variation of "arm" (armv7, arm64). @@ -901,14 +1192,27 @@ function(qt_get_platform_try_compile_vars out_var) if(UIKIT) # Specify the sysroot, but only if not doing a simulator_and_device build. # So keep the sysroot empty for simulator_and_device builds. - if(QT_UIKIT_SDK) - list(APPEND flags_cmd_line "-DCMAKE_OSX_SYSROOT:STRING=${QT_UIKIT_SDK}") + if(QT_APPLE_SDK) + list(APPEND flags_cmd_line "-DCMAKE_OSX_SYSROOT:STRING=${QT_APPLE_SDK}") endif() endif() + if(QT_NO_USE_FIND_PACKAGE_SYSTEM_ENVIRONMENT_PATH) + list(APPEND flags_cmd_line "-DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH:BOOL=OFF") + endif() set("${out_var}" "${flags_cmd_line}" PARENT_SCOPE) endfunction() +# Set out_var to the first value of CMAKE_OSX_ARCHITECTURES. +# Sets an empty string if no architecture is present. +function(qt_internal_get_first_osx_arch out_var) + set(value "") + if(CMAKE_OSX_ARCHITECTURES) + list(GET CMAKE_OSX_ARCHITECTURES 0 value) + endif() + set(${out_var} "${value}" PARENT_SCOPE) +endfunction() + function(qt_config_compile_test_x86simd extension label) if (DEFINED TEST_X86SIMD_${extension}) return() @@ -919,7 +1223,7 @@ function(qt_config_compile_test_x86simd extension label) qt_get_platform_try_compile_vars(platform_try_compile_vars) list(APPEND flags ${platform_try_compile_vars}) - message(STATUS "Performing SIMD Test ${label}") + message(STATUS "Performing Test ${label} intrinsics") try_compile("TEST_X86SIMD_${extension}" "${CMAKE_CURRENT_BINARY_DIR}/config.tests/x86_simd_${extension}" "${CMAKE_CURRENT_SOURCE_DIR}/config.tests/x86_simd" @@ -930,12 +1234,12 @@ function(qt_config_compile_test_x86simd extension label) else() set(status_label "Failed") endif() - message(STATUS "Performing SIMD Test ${label} - ${status_label}") + message(STATUS "Performing Test ${label} intrinsics - ${status_label}") set(TEST_subarch_${extension} "${TEST_X86SIMD_${extension}}" CACHE INTERNAL "${label}") endfunction() function(qt_config_compile_test_machine_tuple label) - if(DEFINED TEST_MACHINE_TUPLE OR NOT LINUX OR ANDROID) + if(DEFINED TEST_MACHINE_TUPLE OR NOT (LINUX OR HURD) OR ANDROID) return() endif() @@ -963,27 +1267,113 @@ function(qt_config_compiler_supports_flag_test name) set(TEST_${name} "${TEST_${name}}" CACHE INTERNAL "${label}") endfunction() -function(qt_config_linker_supports_flag_test name) +# gcc expects -fuse-ld=mold (no absolute path can be given) (gcc >= 12.1) +# or an 'ld' symlink to 'mold' in a dir that is passed via -B flag (gcc < 12.1) +# +# clang expects -fuse-ld=mold +# or -fuse-ld=<mold-abs-path> +# or --ldpath=<mold-abs-path> (clang >= 12) +# https://github.com/rui314/mold/#how-to-use +# TODO: In the gcc < 12.1 case, the qt_internal_check_if_linker_is_available(mold) check will +# always return TRUE because gcc will not error out if it is given a -B flag pointing to an +# invalid dir, as well as when the the symlink to the linker in the -B dir is not actually +# a valid linker. +# It would be nice to handle that case in a better way, but it's not that important +# given that gcc > 12.1 now supports -fuse-ld=mold +# NOTE: In comparison to clang, in the gcc < 12.1 case, we pass the full path to where mold is +# and that is recorded in PlatformCommonInternal's INTERFACE_LINK_OPTIONS target. +# Moving such a Qt to a different machine and trying to build another repo won't +# work because the recorded path will be invalid. This is not a problem with +# the gcc >= 12.1 case +function(qt_internal_get_mold_linker_flags out_var) + cmake_parse_arguments(PARSE_ARGV 1 arg "ERROR_IF_EMPTY" "" "") + + find_program(QT_INTERNAL_LINKER_MOLD mold) + + set(flag "") + if(QT_INTERNAL_LINKER_MOLD) + if(GCC) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1") + set(flag "-fuse-ld=mold") + else() + set(mold_linker_dir "${CMAKE_CURRENT_BINARY_DIR}/.qt_linker") + set(mold_linker_path "${mold_linker_dir}/ld") + if(NOT EXISTS "${mold_linker_dir}") + file(MAKE_DIRECTORY "${mold_linker_dir}") + endif() + if(NOT EXISTS "${mold_linker_path}") + file(CREATE_LINK + "${QT_INTERNAL_LINKER_MOLD}" + "${mold_linker_path}" + SYMBOLIC) + endif() + set(flag "-B${mold_linker_dir}") + endif() + elseif(CLANG) + if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12") + set(flag "--ld-path=mold") + else() + set(flag "-fuse-ld=mold") + endif() + endif() + endif() + if(arg_ERROR_IS_EMPTY AND NOT flag) + message(FATAL_ERROR "Could not determine the flags to use the mold linker.") + endif() + set(${out_var} "${flag}" PARENT_SCOPE) +endfunction() + +function(qt_internal_get_active_linker_flags out_var) + set(flags "") + if(GCC OR CLANG) + if(QT_FEATURE_use_gold_linker) + list(APPEND flags "-fuse-ld=gold") + elseif(QT_FEATURE_use_bfd_linker) + list(APPEND flags "-fuse-ld=bfd") + elseif(QT_FEATURE_use_lld_linker) + list(APPEND flags "-fuse-ld=lld") + elseif(QT_FEATURE_use_mold_linker) + qt_internal_get_mold_linker_flags(mold_flags ERROR_IF_EMPTY) + list(APPEND flags "${mold_flags}") + endif() + endif() + set(${out_var} "${flags}" PARENT_SCOPE) +endfunction() + +function(qt_internal_check_if_linker_is_available name) if(DEFINED "TEST_${name}") return() endif() cmake_parse_arguments(arg "" "LABEL;FLAG" "" ${ARGN}) - set(flags "-Wl,${arg_FLAG}") + set(flags "${arg_FLAG}") + + set(CMAKE_REQUIRED_LINK_OPTIONS ${flags}) + check_cxx_source_compiles("int main() { return 0; }" TEST_${name}) + set(TEST_${name} "${TEST_${name}}" CACHE INTERNAL "${label}") +endfunction() + +function(qt_config_linker_supports_flag_test name) + if(DEFINED "TEST_${name}") + return() + endif() - # Select the right linker. + cmake_parse_arguments(arg "" "LABEL;FLAG" "" ${ARGN}) if(GCC OR CLANG) - # TODO: This works for now but is... suboptimal. Once - # QTBUG-86186 is resolved, we should check the *features* - # QT_FEATURE_use_gold_linker etc. instead of trying to - # replicate the feature conditions. - if(QT_FEATURE_use_gold_linker_alias OR INPUT_linker STREQUAL "gold") - list(PREPEND flags "-fuse-ld=gold") - elseif(INPUT_linker STREQUAL "bfd") - list(PREPEND flags "-fuse-ld=bfd") - elseif(INPUT_linker STREQUAL "lld") - list(PREPEND flags "-fuse-ld=lld") - endif() + set(flags "-Wl,--fatal-warnings,${arg_FLAG}") + elseif(MSVC) + set(flags "${arg_FLAG}") + else() + # We don't know how to pass linker options in a way that + # it reliably fails, so assume the detection failed. + set(TEST_${name} "0" CACHE INTERNAL "${label}") + return() + endif() + + # Pass the linker that the main project uses to the compile test. + qt_internal_get_active_linker_flags(linker_flags) + if(linker_flags) + list(PREPEND flags ${linker_flags}) endif() set(CMAKE_REQUIRED_LINK_OPTIONS ${flags}) @@ -1014,7 +1404,15 @@ function(qt_make_features_available target) endif() foreach(feature IN ITEMS ${features}) if (DEFINED "QT_FEATURE_${feature}" AND NOT "${QT_FEATURE_${feature}}" STREQUAL "${value}") - message(FATAL_ERROR "Feature ${feature} is already defined and has a different value when importing features from ${target}.") + message(WARNING + "This project was initially configured with the Qt feature \"${feature}\" " + "set to \"${QT_FEATURE_${feature}}\". While loading the " + "\"${target}\" package, the value of the feature " + "has changed to \"${value}\". That might cause a project rebuild due to " + "updated C++ headers. \n" + "In case of build issues, consider removing the CMakeCache.txt file and " + "reconfiguring the project." + ) endif() set(QT_FEATURE_${feature} "${value}" CACHE INTERNAL "Qt feature: ${feature} (from target ${target})") endforeach() @@ -1022,5 +1420,3 @@ function(qt_make_features_available target) endforeach() endforeach() endfunction() - - |