# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause

include(CheckCXXCompilerFlag)

function(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")
    _qt_internal_validate_all_args_are_parsed(arg)

    if(NOT arg_ONLY_EVALUATE_FEATURES)
        if ("${arg_LIBRARY}" STREQUAL "" AND (NOT ${arg_NO_MODULE}))
            message(FATAL_ERROR
                    "qt_feature_begin_module needs a LIBRARY name! (or specify NO_MODULE)")
        endif()
        if ("${arg_PUBLIC_FILE}" STREQUAL "")
            message(FATAL_ERROR "qt_feature_begin_module needs a PUBLIC_FILE name!")
        endif()
        if ("${arg_PRIVATE_FILE}" STREQUAL "")
            message(FATAL_ERROR "qt_feature_begin_module needs a PRIVATE_FILE name!")
        endif()
        set(__QtFeature_only_evaluate_features OFF PARENT_SCOPE)
    else()
        set(__QtFeature_only_evaluate_features ON PARENT_SCOPE)
    endif()

    set(__QtFeature_library "${arg_LIBRARY}" PARENT_SCOPE)
    set(__QtFeature_public_features "" PARENT_SCOPE)
    set(__QtFeature_private_features "" PARENT_SCOPE)
    set(__QtFeature_internal_features "" PARENT_SCOPE)

    set(__QtFeature_private_file "${arg_PRIVATE_FILE}" PARENT_SCOPE)
    set(__QtFeature_public_file "${arg_PUBLIC_FILE}" PARENT_SCOPE)

    set(__QtFeature_private_extra "" PARENT_SCOPE)
    set(__QtFeature_public_extra "" PARENT_SCOPE)

    set(__QtFeature_config_definitions "" PARENT_SCOPE)

    set(__QtFeature_define_definitions "" PARENT_SCOPE)
endfunction()

function(qt_feature feature)
    set(original_name "${feature}")
    qt_feature_normalize_name("${feature}" feature)
    set_property(GLOBAL PROPERTY QT_FEATURE_ORIGINAL_NAME_${feature} "${original_name}")

    cmake_parse_arguments(PARSE_ARGV 1 arg
        "PRIVATE;PUBLIC"
        "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)

    # Register feature for future use:
    if (arg_PUBLIC)
        list(APPEND __QtFeature_public_features "${feature}")
    endif()
    if (arg_PRIVATE)
        list(APPEND __QtFeature_private_features "${feature}")
    endif()
    if (NOT arg_PUBLIC AND NOT arg_PRIVATE)
        list(APPEND __QtFeature_internal_features "${feature}")
    endif()


    set(__QtFeature_public_features ${__QtFeature_public_features} PARENT_SCOPE)
    set(__QtFeature_private_features ${__QtFeature_private_features} PARENT_SCOPE)
    set(__QtFeature_internal_features ${__QtFeature_internal_features} PARENT_SCOPE)
endfunction()

function(qt_evaluate_to_boolean expressionVar)
    if(${${expressionVar}})
        set(${expressionVar} ON PARENT_SCOPE)
    else()
        set(${expressionVar} OFF PARENT_SCOPE)
    endif()
endfunction()

function(qt_internal_evaluate_config_expression resultVar outIdx startIdx)
    set(result "")
    set(expression "${ARGN}")
    list(LENGTH expression length)

    math(EXPR memberIdx "${startIdx} - 1")
    math(EXPR length "${length}-1")
    while(memberIdx LESS ${length})
        math(EXPR memberIdx "${memberIdx} + 1")
        list(GET expression ${memberIdx} member)

        if("${member}" STREQUAL "(")
            math(EXPR memberIdx "${memberIdx} + 1")
            qt_internal_evaluate_config_expression(sub_result memberIdx ${memberIdx} ${expression})
            list(APPEND result ${sub_result})
        elseif("${member}" STREQUAL ")")
            break()
        elseif("${member}" STREQUAL "NOT")
            list(APPEND result ${member})
        elseif("${member}" STREQUAL "AND")
            qt_evaluate_to_boolean(result)
            if(NOT ${result})
                break()
            endif()
            set(result "")
        elseif("${member}" STREQUAL "OR")
            qt_evaluate_to_boolean(result)
            if(${result})
                break()
            endif()
            set(result "")
        elseif("${member}" STREQUAL "STREQUAL" AND memberIdx LESS ${length})
            # Unfortunately the semantics for STREQUAL in if() are broken when the
            # RHS is an empty string and the parameters to if are coming through a variable.
            # So we expect people to write the empty string with single quotes and then we
            # do the comparison manually here.
            list(LENGTH result lhsIndex)
            math(EXPR lhsIndex "${lhsIndex}-1")
            list(GET result ${lhsIndex} lhs)
            list(REMOVE_AT result ${lhsIndex})
            set(lhs "${${lhs}}")

            math(EXPR rhsIndex "${memberIdx}+1")
            set(memberIdx ${rhsIndex})

            list(GET expression ${rhsIndex} rhs)
            # We can't pass through an empty string with double quotes through various
            # stages of substitution, so instead it is represented using single quotes
            # and resolve here.
            string(REGEX REPLACE "'(.*)'" "\\1" rhs "${rhs}")

            string(COMPARE EQUAL "${lhs}" "${rhs}" stringCompareResult)
            list(APPEND result ${stringCompareResult})
        else()
            string(FIND "${member}" "QT_FEATURE_" idx)
            if(idx EQUAL 0)
                # Remove the QT_FEATURE_ prefix
                string(SUBSTRING "${member}" 11 -1 feature)
                qt_evaluate_feature(${feature})
            endif()

            list(APPEND result ${member})
        endif()
    endwhile()
    # The 'TARGET Gui' case is handled by qt_evaluate_to_boolean, by passing those tokens verbatim
    # to if().

    if("${result}" STREQUAL "")
        set(result ON)
    else()
        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_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")

    if(${length} LESS 0)
        return()
    endif()

    foreach(memberIdx RANGE ${length})
        if(${skipNext})
            set(skipNext FALSE)
            continue()
        endif()

        list(GET expression ${memberIdx} member)
        if(NOT "${member}" IN_LIST keywords)
            string(FIND "${member}" "QT_FEATURE_" idx)
            if(idx EQUAL 0)
                if(NOT DEFINED ${member})
                    list(APPEND dump "${member} not evaluated")
                else()
                    list(APPEND dump "${member} = \"${${member}}\"")
                endif()
            elseif(isTargetExpression)
                set(targetExpression "TARGET;${member}")
                if(${targetExpression})
                    list(APPEND dump "TARGET ${member} found")
                else()
                    list(APPEND dump "TARGET ${member} not found")
                endif()
            else()
                list(APPEND dump "${member} = \"${${member}}\"")
            endif()
            set(isTargetExpression FALSE)
            set(skipNext FALSE)
        elseif("${member}" STREQUAL "TARGET")
            set(isTargetExpression TRUE)
        elseif("${member}" STREQUAL "STREQUAL")
            set(skipNext TRUE)
        else()
            set(skipNext FALSE)
            set(isTargetExpression FALSE)
        endif()
    endforeach()
    string(JOIN "\n    " ${expression_dump} ${dump})
    set(${expression_dump} "${${expression_dump}}" PARENT_SCOPE)
endfunction()

# 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}")
        # Revisit new user provided value
        set(user_value "${FEATURE_${feature}}")
        string(TOUPPER "${user_value}" user_value_upper)
        set(result "${user_value_upper}")

        # If ${feature} depends on another dirty feature, reset the ${feature} value to
        # ${computed}.
        get_property(dirty_build GLOBAL PROPERTY _qt_dirty_build)
        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 ((result IN_LIST bool_values) OR (result GREATER_EQUAL 0))
            # All good!
        else()
            message(FATAL_ERROR
                "Sanity check failed: FEATURE_${feature} has invalid value \"${result}\"!")
        endif()

        # Fix-up user-provided values
        set("FEATURE_${feature}" "${result}" CACHE BOOL "${label}" FORCE)
    else()
        # Initial setup:
        set(result "${computed}")
        set("FEATURE_${feature}" "${result}" CACHE BOOL "${label}")
    endif()

    # Check for potential typo
    get_property(original_name GLOBAL PROPERTY "QT_FEATURE_ORIGINAL_NAME_${feature}")
    if(NOT original_name STREQUAL feature AND DEFINED "FEATURE_${original_name}")
        unset("FEATURE_${original_name}" CACHE)
        qt_configure_add_report_error(
            "FEATURE_${original_name} does not exist. Consider using: FEATURE_${feature}"
        )
    endif()

    set("${resultVar}" "${result}" PARENT_SCOPE)
endfunction()

# 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 result)
        _qt_internal_dump_expression_values(conditionDump "${conditionExpression}")
        string(JOIN " " conditionString ${conditionExpression})
        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()

    if (DEFINED "QT_FEATURE_${feature}")
        message(FATAL_ERROR "Feature ${feature} is already defined when evaluating configure.cmake features for ${target}.")
    endif()
    set(QT_FEATURE_${feature} "${result}" CACHE INTERNAL "Qt feature: ${feature}")

    # Add feature to build feature collection
    list(APPEND QT_KNOWN_FEATURES "${feature}")
    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}")
        return()
    endif()

    if(NOT DEFINED _QT_FEATURE_DEFINITION_${feature})
        qt_debug_print_variables(DEDUP MATCH "^QT_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()

    _qt_internal_parse_feature_definition("${feature}")

    if("${arg_ENABLE}" STREQUAL "")
        set(arg_ENABLE OFF)
    endif()

    if("${arg_DISABLE}" STREQUAL "")
        set(arg_DISABLE OFF)
    endif()

    if("${arg_AUTODETECT}" STREQUAL "")
        set(arg_AUTODETECT ON)
    endif()

    if("${arg_CONDITION}" STREQUAL "")
        set(condition ON)
    else()
        qt_evaluate_config_expression(condition ${arg_CONDITION})
    endif()

    qt_evaluate_config_expression(disable_result ${arg_DISABLE})
    qt_evaluate_config_expression(enable_result ${arg_ENABLE})
    qt_evaluate_config_expression(auto_detect ${arg_AUTODETECT})
    if(${disable_result})
        set(computed OFF)
    elseif(${enable_result})
        set(computed ON)
    elseif(${auto_detect})
        set(computed ${condition})
    else()
        # feature not auto-detected and not explicitly enabled
        set(computed OFF)
    endif()

    if("${arg_EMIT_IF}" STREQUAL "")
        set(emit_if ON)
    else()
        qt_evaluate_config_expression(emit_if ${arg_EMIT_IF})
    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_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)
    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}")
    set(${key_name} "FEATURE;${feature};CONFIG_VAR_NAME;${config_var_name};${ARGN}" PARENT_SCOPE)

    # Store the key for later evaluation.
    list(APPEND __QtFeature_config_definitions "${key_name}")

    set(__QtFeature_config_definitions ${__QtFeature_config_definitions} PARENT_SCOPE)
endfunction()

function(qt_evaluate_qmake_config_values key)
    if(NOT DEFINED ${key})
        qt_debug_print_variables(DEDUP MATCH "^_QT_FEATURE_CONFIG_DEFINITION")
        message(FATAL_ERROR
                "Attempting to evaluate feature config ${key} but its definition is missing. ")
    endif()

    cmake_parse_arguments(arg
        "NEGATE"
        "FEATURE;NAME;CONFIG_VAR_NAME"
        "" ${${key}})

    # If no custom name is specified, then the config value is the same as the feature name.
    if(NOT arg_NAME)
        set(arg_NAME "${arg_FEATURE}")
    endif()

    set(expected "NOT")
    if (arg_NEGATE)
        set(expected "")
        if(arg_NAME MATCHES "^no_(.*)")
            set(arg_NAME "${CMAKE_MATCH_1}")
        else()
            string(PREPEND arg_NAME "no_")
        endif()
    endif()

    # The feature condition is false, there is no need to export any config values.
    if(${expected} ${QT_FEATURE_${arg_FEATURE}})
        return()
    endif()

    if(arg_CONFIG_VAR_NAME STREQUAL "QMAKE_PUBLIC_CONFIG")
        list(APPEND __QtFeature_qmake_public_config "${arg_NAME}")
        set(__QtFeature_qmake_public_config "${__QtFeature_qmake_public_config}" PARENT_SCOPE)
    endif()
    if(arg_CONFIG_VAR_NAME STREQUAL "QMAKE_PRIVATE_CONFIG")
        list(APPEND __QtFeature_qmake_private_config "${arg_NAME}")
        set(__QtFeature_qmake_private_config "${__QtFeature_qmake_private_config}" PARENT_SCOPE)
    endif()
    if(arg_CONFIG_VAR_NAME STREQUAL "QMAKE_PUBLIC_QT_CONFIG")
        list(APPEND __QtFeature_qmake_public_qt_config "${arg_NAME}")
        set(__QtFeature_qmake_public_qt_config "${__QtFeature_qmake_public_qt_config}" PARENT_SCOPE)
    endif()
endfunction()

function(qt_feature_definition feature name)
    qt_feature_normalize_name("${feature}" feature)
    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}")
    set(${key_name} "FEATURE;${feature};NAME;${name};${ARGN}" PARENT_SCOPE)

    # Store the key for later evaluation and subsequent define generation:
    list(APPEND __QtFeature_define_definitions "${key_name}")

    set(__QtFeature_define_definitions ${__QtFeature_define_definitions} PARENT_SCOPE)
endfunction()

function(qt_evaluate_feature_definition key)
    if(NOT DEFINED ${key})
        qt_debug_print_variables(DEDUP MATCH "^_QT_FEATURE_DEFINE_DEFINITION")
        message(FATAL_ERROR "Attempting to evaluate feature define ${key} but its definition is missing. ")
    endif()

    cmake_parse_arguments(arg
        "NEGATE;"
        "FEATURE;NAME;VALUE;PREREQUISITE" "" ${${key}})

    set(expected ON)
    if (arg_NEGATE)
        set(expected OFF)
    endif()

    set(actual OFF)
    if(QT_FEATURE_${arg_FEATURE})
        set(actual ON)
    endif()

    set(msg "")

    if(actual STREQUAL expected)
        set(indent "")
        if(arg_PREREQUISITE)
            string(APPEND msg "#if ${arg_PREREQUISITE}\n")
            set(indent "  ")
        endif()
        if (arg_VALUE)
            string(APPEND msg "${indent}#define ${arg_NAME} ${arg_VALUE}\n")
        else()
            string(APPEND msg "${indent}#define ${arg_NAME}\n")
        endif()
        if(arg_PREREQUISITE)
            string(APPEND msg "#endif\n")
        endif()

        string(APPEND __QtFeature_public_extra "${msg}")
    endif()

    set(__QtFeature_public_extra ${__QtFeature_public_extra} PARENT_SCOPE)
endfunction()

function(qt_extra_definition name value)
    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")
    elseif(arg_PRIVATE)
        string(APPEND __QtFeature_private_extra "\n#define ${name} ${value}\n")
    endif()

    set(__QtFeature_public_extra ${__QtFeature_public_extra} PARENT_SCOPE)
    set(__QtFeature_private_extra ${__QtFeature_private_extra} PARENT_SCOPE)
endfunction()

function(qt_internal_generate_feature_line line feature)
    string(TOUPPER "${QT_FEATURE_${feature}}" value)
    if (value STREQUAL "ON")
        set(line "#define QT_FEATURE_${feature} 1\n\n" PARENT_SCOPE)
    elseif(value STREQUAL "OFF")
        set(line "#define QT_FEATURE_${feature} -1\n\n" PARENT_SCOPE)
    elseif(value STREQUAL "UNSET")
        set(line "#define QT_FEATURE_${feature} 0\n\n" PARENT_SCOPE)
    else()
        message(FATAL_ERROR "${feature} has unexpected value \"${QT_FEATURE_${feature}}\"! "
            "Valid values are ON, OFF and UNSET.")
    endif()
endfunction()

function(qt_internal_feature_write_file file features extra)
    set(contents "")
    foreach(it ${features})
        qt_internal_generate_feature_line(line "${it}")
        string(APPEND contents "${line}")
    endforeach()
    string(APPEND contents "${extra}")

    file(GENERATE OUTPUT "${file}" CONTENT "${contents}")
endfunction()

# Helper function which evaluates features from a given list of configure.cmake paths
# and creates the feature cache entries.
# Should not be used directly, unless features need to be available in a directory scope before the
# associated module evaluates the features.
# E.g. qtbase/src.pro needs access to Core features before src/corelib/CMakeLists.txt is parsed.
function(qt_feature_evaluate_features list_of_paths)
    qt_feature_module_begin(ONLY_EVALUATE_FEATURES)
    foreach(path ${list_of_paths})
        include("${path}")
    endforeach()
    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)
    set(multiopts)
    cmake_parse_arguments(arg "${flags}" "${options}" "${multiopts}" ${ARGN})
    set(target ${arg_UNPARSED_ARGUMENTS})

    # The value of OUT_VAR_PREFIX is used as a prefix for output variables that should be
    # set in the parent scope.
    if(NOT arg_OUT_VAR_PREFIX)
        set(arg_OUT_VAR_PREFIX "")
    endif()

    set(all_features ${__QtFeature_public_features} ${__QtFeature_private_features} ${__QtFeature_internal_features})
    list(REMOVE_DUPLICATES all_features)

    foreach(feature ${all_features})
        qt_evaluate_feature(${feature})
    endforeach()

    # Evaluate custom cache assignments.
    foreach(cache_var_name ${__QtFeature_custom_enabled_cache_variables})
        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 requirements." FORCE)
    endforeach()

    set(enabled_public_features "")
    set(disabled_public_features "")
    set(enabled_private_features "")
    set(disabled_private_features "")

    foreach(feature ${__QtFeature_public_features})
       if(QT_FEATURE_${feature})
          list(APPEND enabled_public_features ${feature})
       else()
          list(APPEND disabled_public_features ${feature})
       endif()
    endforeach()

    foreach(feature ${__QtFeature_private_features})
       if(QT_FEATURE_${feature})
          list(APPEND enabled_private_features ${feature})
       else()
          list(APPEND disabled_private_features ${feature})
       endif()
    endforeach()

    foreach(key ${__QtFeature_config_definitions})
        qt_evaluate_qmake_config_values(${key})
        unset(${key} PARENT_SCOPE)
    endforeach()

    foreach(key ${__QtFeature_define_definitions})
        qt_evaluate_feature_definition(${key})
        unset(${key} PARENT_SCOPE)
    endforeach()

    foreach(feature ${all_features})
        unset(_QT_FEATURE_DEFINITION_${feature} PARENT_SCOPE)
    endforeach()

    if(NOT arg_ONLY_EVALUATE_FEATURES)
        qt_internal_feature_write_file("${CMAKE_CURRENT_BINARY_DIR}/${__QtFeature_private_file}"
            "${__QtFeature_private_features}" "${__QtFeature_private_extra}"
        )

        qt_internal_feature_write_file("${CMAKE_CURRENT_BINARY_DIR}/${__QtFeature_public_file}"
            "${__QtFeature_public_features}" "${__QtFeature_public_extra}"
        )
    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;QT_QMAKE_PUBLIC_CONFIG;QT_QMAKE_PRIVATE_CONFIG;QT_QMAKE_PUBLIC_QT_CONFIG")
        endif()
        foreach(visibility public private)
            string(TOUPPER "${visibility}" capitalVisibility)
            foreach(state enabled disabled)
                string(TOUPPER "${state}" capitalState)

                set_property(TARGET "${target}" PROPERTY ${propertyPrefix}QT_${capitalState}_${capitalVisibility}_FEATURES "${${state}_${visibility}_features}")
            endforeach()
        endforeach()

        set_property(TARGET "${target}"
                    PROPERTY ${propertyPrefix}QT_QMAKE_PUBLIC_CONFIG
                    "${__QtFeature_qmake_public_config}")
        set_property(TARGET "${target}"
                     PROPERTY ${propertyPrefix}QT_QMAKE_PRIVATE_CONFIG
                     "${__QtFeature_qmake_private_config}")
        set_property(TARGET "${target}"
                     PROPERTY ${propertyPrefix}QT_QMAKE_PUBLIC_QT_CONFIG
                     "${__QtFeature_qmake_public_qt_config}")

        # Config values were the old-school features before actual configure.json features were
        # implemented. Therefore "CONFIG+=foo" values should be considered features as well,
        # so that CMake can find them when building qtmultimedia for example.
        if(__QtFeature_qmake_public_config)
            set_property(TARGET "${target}"
                         APPEND PROPERTY ${propertyPrefix}QT_ENABLED_PUBLIC_FEATURES
                         ${__QtFeature_qmake_public_config})
        endif()
        if(__QtFeature_qmake_private_config)
            set_property(TARGET "${target}"
                         APPEND PROPERTY ${propertyPrefix}QT_ENABLED_PRIVATE_FEATURES
                         ${__QtFeature_qmake_private_config})
        endif()
        if(__QtFeature_qmake_public_qt_config)
            set_property(TARGET "${target}"
                         APPEND PROPERTY ${propertyPrefix}QT_ENABLED_PUBLIC_FEATURES
                         ${__QtFeature_qmake_public_qt_config})
        endif()

        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)
    unset(__QtFeature_internal_features PARENT_SCOPE)

    unset(__QtFeature_private_file PARENT_SCOPE)
    unset(__QtFeature_public_file PARENT_SCOPE)

    unset(__QtFeature_private_extra PARENT_SCOPE)
    unset(__QtFeature_public_extra PARENT_SCOPE)

    unset(__QtFeature_define_definitions PARENT_SCOPE)
    unset(__QtFeature_custom_enabled_features PARENT_SCOPE)
    unset(__QtFeature_custom_disabled_features PARENT_SCOPE)
    unset(__QtFeature_only_evaluate_features PARENT_SCOPE)
    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
    # See https://gitlab.kitware.com/cmake/cmake/issues/19261.
    # To circumvent that, copy the properties from GlobalConfig to Core target.
    # This way the global features actually get set in the generated CoreTargets.cmake file.
    if(target STREQUAL Core)
        foreach(visibility public private)
            string(TOUPPER "${visibility}" capitalVisibility)
            foreach(state enabled disabled)
                string(TOUPPER "${state}" capitalState)

                set(core_property_name "QT_${capitalState}_${capitalVisibility}_FEATURES")
                set(global_property_name "INTERFACE_${core_property_name}")

                get_property(core_values TARGET Core PROPERTY ${core_property_name})
                get_property(global_values TARGET GlobalConfig PROPERTY ${global_property_name})

                set(total_values ${core_values} ${global_values})
                set_property(TARGET Core PROPERTY ${core_property_name} ${total_values})
            endforeach()
        endforeach()

        set(config_property_names
            QT_QMAKE_PUBLIC_CONFIG QT_QMAKE_PRIVATE_CONFIG QT_QMAKE_PUBLIC_QT_CONFIG )
        foreach(property_name ${config_property_names})
            set(core_property_name "${property_name}")
            set(global_property_name "INTERFACE_${core_property_name}")

            get_property(core_values TARGET Core PROPERTY ${core_property_name})
            get_property(global_values TARGET GlobalConfig PROPERTY ${global_property_name})

            set(total_values ${core_values} ${global_values})
            set_property(TARGET Core PROPERTY ${core_property_name} ${total_values})
        endforeach()
    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;CMAKE_FLAGS" ${ARGN})

    if(arg_PROJECT_PATH)
        message(STATUS "Performing Test ${arg_LABEL}")

        set(flags "")
        qt_get_platform_try_compile_vars(platform_try_compile_vars)
        list(APPEND flags ${platform_try_compile_vars})

        # 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")
            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.
        if(arg_PACKAGES)
            set(packages_list "")

            # Parse the package names, version, etc. An example would be:
            # PACKAGE Foo 6 REQUIRED
            # PACKAGE Bar 2 COMPONENTS Baz
            foreach(p ${arg_PACKAGES})
                if(p STREQUAL PACKAGE)
                    if(package_entry)
                        # Encode the ";" into "\;" to separate the arguments of a find_package call.
                        string(REPLACE ";" "\\;" package_entry_string "${package_entry}")
                        list(APPEND packages_list "${package_entry_string}")
                    endif()

                    set(package_entry "")
                else()
                    list(APPEND package_entry "${p}")
                endif()
            endforeach()
            # Parse final entry.
            if(package_entry)
                string(REPLACE ";" "\\;" package_entry_string "${package_entry}")
                list(APPEND packages_list "${package_entry_string}")
            endif()

            # Encode the ";" again.
            string(REPLACE ";" "\\;" packages_list "${packages_list}")

            # The flags are separated by ';', the find_package entries by '\;',
            # and the package parts of an entry by '\\;'.
            # Example:
            # WrapFoo\\;6\\;COMPONENTS\\;bar\;WrapBaz\\;5
            list(APPEND flags "-DQT_CONFIG_COMPILE_TEST_PACKAGES:STRING=${packages_list}")

            # Inside the project, the value of QT_CONFIG_COMPILE_TEST_PACKAGES is used in a foreach
            # loop that calls find_package() for each package entry, and thus the variable expansion
            # ends up calling something like find_package(WrapFoo;6;COMPONENTS;bar) aka
            # find_package(WrapFoo 6 COMPONENTS bar).
        endif()

        # Pass which libraries need to be linked against.
        if(arg_LIBRARIES)
            set(link_flags "")
            set(library_targets "")
            # Separate targets from link flags or paths. This is to prevent configuration failures
            # when the targets are not found due to missing packages.
            foreach(lib ${arg_LIBRARIES})
                string(FIND "${lib}" "::" is_library_target)
                if(is_library_target EQUAL -1)
                    list(APPEND link_flags "${lib}")
                else()
                    list(APPEND library_targets "${lib}")
                endif()
            endforeach()
            if(link_flags)
                list(APPEND flags "-DQT_CONFIG_COMPILE_TEST_LIBRARIES:STRING=${link_flags}")
            endif()
            if(library_targets)
                list(APPEND flags
                     "-DQT_CONFIG_COMPILE_TEST_LIBRARY_TARGETS:STRING=${library_targets}")
            endif()
        endif()

        # 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")
        else()
            set(status_label "Failed")
        endif()
        message(STATUS "Performing Test ${arg_LABEL} - ${status_label}")
    else()
        foreach(library IN ITEMS ${arg_LIBRARIES})
            if(NOT TARGET "${library}")
                # If the dependency looks like a cmake target, then make this compile test
                # 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()
            endif()
        endforeach()

        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)
                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.
            # https://developercommunity.visualstudio.com/content/problem/139261/msvc-incorrectly-defines-cplusplus.html
            # No support for the flag in upstream CMake as of 3.17.
            # https://gitlab.kitware.com/cmake/cmake/issues/18837
            if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER_EQUAL 1913)
                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}")

            # 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()

# This function should be used for passing required try compile platform variables to the
# project-based try_compile() call.
# out_var will be a list of -Dfoo=bar strings, suitable to pass to CMAKE_FLAGS.
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")
    endif()
    if(VCPKG_CHAINLOAD_TOOLCHAIN_FILE)
        list(APPEND flags "VCPKG_CHAINLOAD_TOOLCHAIN_FILE")
    endif()

    # 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 "")
    foreach(flag ${flags})
        if(${flag})
            list(APPEND flags_cmd_line "-D${flag}=${${flag}}")
        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.
    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).
        list(APPEND flags_cmd_line "-DCMAKE_OSX_ARCHITECTURES:STRING=${osx_first_arch}")
    endif()
    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_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()
    endif()

    set(flags "-DSIMD:string=${extension}")

    qt_get_platform_try_compile_vars(platform_try_compile_vars)
    list(APPEND flags ${platform_try_compile_vars})

    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"
        x86_simd
        CMAKE_FLAGS ${flags})
    if(${TEST_X86SIMD_${extension}})
        set(status_label "Success")
    else()
        set(status_label "Failed")
    endif()
    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 HURD) OR ANDROID)
        return()
    endif()

    message(STATUS "Performing Test ${label}")
    execute_process(COMMAND "${CMAKE_CXX_COMPILER}" -dumpmachine
        OUTPUT_VARIABLE output
        OUTPUT_STRIP_TRAILING_WHITESPACE
        RESULT_VARIABLE exit_code)
    if(exit_code EQUAL 0)
        set(status_label "Success")
    else()
        set(status_label "Failed")
    endif()
    message(STATUS "Performing Test ${label} - ${status_label}")
    set(TEST_machine_tuple "${output}" CACHE INTERNAL "${label}")
endfunction()

function(qt_config_compiler_supports_flag_test name)
    if(DEFINED "TEST_${name}")
        return()
    endif()

    cmake_parse_arguments(arg "" "LABEL;FLAG" "" ${ARGN})
    check_cxx_compiler_flag("${arg_FLAG}" TEST_${name})
    set(TEST_${name} "${TEST_${name}}" CACHE INTERNAL "${label}")
endfunction()

# 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 "${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()

    cmake_parse_arguments(arg "" "LABEL;FLAG" "" ${ARGN})
    if(GCC OR CLANG)
        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})
    check_cxx_source_compiles("int main() { return 0; }" TEST_${name})
    set(TEST_${name} "${TEST_${name}}" CACHE INTERNAL "${label}")
endfunction()

function(qt_make_features_available target)
    if(NOT "${target}" MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::[a-zA-Z0-9_-]*$")
        message(FATAL_ERROR "${target} does not match ${QT_CMAKE_EXPORT_NAMESPACE}::[a-zA-Z0-9_-]*. INVALID NAME.")
    endif()
    if(NOT TARGET ${target})
        message(FATAL_ERROR "${target} not found.")
    endif()

    get_target_property(target_type "${target}" TYPE)
    if("${target_type}" STREQUAL "INTERFACE_LIBRARY")
        set(property_prefix "INTERFACE_")
    else()
        set(property_prefix "")
    endif()
    foreach(visibility IN ITEMS PUBLIC PRIVATE)
        set(value ON)
        foreach(state IN ITEMS ENABLED DISABLED)
            get_target_property(features "${target}" ${property_prefix}QT_${state}_${visibility}_FEATURES)
            if("${features}" STREQUAL "features-NOTFOUND")
                continue()
            endif()
            foreach(feature IN ITEMS ${features})
                if (DEFINED "QT_FEATURE_${feature}" AND NOT "${QT_FEATURE_${feature}}" STREQUAL "${value}")
                    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()
            set(value OFF)
        endforeach()
    endforeach()
endfunction()
