function(qt_feature_module_begin) qt_parse_all_arguments(arg "qt_feature_module_begin" "NO_MODULE" "LIBRARY;PRIVATE_FILE;PUBLIC_FILE" "PUBLIC_DEPENDENCIES;PRIVATE_DEPENDENCIES" ${ARGN}) 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_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_define_definitions "" PARENT_SCOPE) endfunction() function(qt_feature feature) qt_parse_all_arguments(arg "qt_feature" "PRIVATE;PUBLIC" "LABEL;PURPOSE;SECTION;" "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" ${ARGN}) 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_evaluate_config_expression resultVar) set(result "") set(nestingLevel 0) set(skipNext OFF) set(expression "${ARGN}") list(LENGTH expression length) math(EXPR length "${length}-1") foreach(memberIdx RANGE ${length}) if(${skipNext}) set(skipNext OFF) continue() endif() list(GET expression ${memberIdx} member) if("${member}" STREQUAL "(") if(${nestingLevel} GREATER 0) list(APPEND result ${member}) endif() math(EXPR nestingLevel "${nestingLevel} + 1") continue() 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() elseif("${member}" STREQUAL "NOT") list(APPEND result ${member}) continue() 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(skipNext ON) 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() endforeach() # 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() set(${resultVar} ${result} PARENT_SCOPE) endfunction() function(qt_feature_set_cache_value resultVar feature emit_if calculated 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 value: set(cache "${FEATURE_${feature}}") set(booly_values OFF NO FALSE N ON YES TRUE Y) if ((cache IN_LIST booly_values) OR (cache GREATER_EQUAL 0)) set(result "${cache}") else() message(FATAL_ERROR "Sanity check failed: FEATURE_${feature} has invalid value \"${cache}\"!") endif() # Fix-up user-provided values set("FEATURE_${feature}" "${cache}" CACHE BOOL "${label}") else() # Initial setup: if (emit_if) set("FEATURE_${feature}" "${calculated}" CACHE BOOL "${label}") set(result "${calculated}") else() set(result OFF) endif() endif() set("${resultVar}" "${result}" PARENT_SCOPE) endfunction() macro(qt_feature_set_value feature cache emit_if condition label) set(result "${cache}") if (NOT (condition) AND (cache)) message(SEND_ERROR "Feature \"${feature}\": Forcing to \"${cache}\" breaks its condition.") 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}") endmacro() function(qt_evaluate_feature feature) # If the feature was set explicitly by the user to be on or off, in the cache, then # there's nothing for us to do. 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() cmake_parse_arguments(arg "PRIVATE;PUBLIC" "LABEL;PURPOSE;SECTION;" "AUTODETECT;CONDITION;ENABLE;DISABLE;EMIT_IF" ${_QT_FEATURE_DEFINITION_${feature}}) if(DEFINED QT_FEATURE_${feature}) return() endif() 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}) if(${disable_result}) set(result OFF) elseif((${enable_result}) OR (${arg_AUTODETECT})) set(result ${condition}) else() # feature not auto-detected and not explicitly enabled set(result OFF) endif() if("${arg_EMIT_IF}" STREQUAL "") set(emit_if ON) else() qt_evaluate_config_expression(emit_if ${arg_EMIT_IF}) endif() if (NOT (condition) AND (calculated)) message(FATAL_ERROR "Sanity check failed: Feature ${feature} is enabled but condition does not hold true.") endif() qt_feature_set_cache_value(cache "${feature}" "${emit_if}" "${result}" "${arg_LABEL}") qt_feature_set_value("${feature}" "${cache}" "${emit_if}" "${condition}" "${arg_LABEL}") endfunction() function(qt_feature_definition feature name) qt_parse_all_arguments(arg "qt_feature_definition" "NEGATE" "VALUE" "" ${ARGN}) # 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;" "" ${${key}}) set(expected ON) if (arg_NEGATE) set(expected OFF) endif() set(msg "") if(QT_FEATURE_${arg_FEATURE} STREQUAL expected) if (arg_VALUE) string(APPEND msg "#define ${arg_NAME} ${arg_VALUE}\n") else() string(APPEND msg "#define ${arg_NAME}\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) qt_parse_all_arguments(arg "qt_extra_definition" "PUBLIC;PRIVATE" "" "" ${ARGN}) 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) if (QT_FEATURE_${feature} STREQUAL "ON") set(line "#define QT_FEATURE_${feature} 1\n\n" PARENT_SCOPE) elseif(QT_FEATURE_${feature} STREQUAL "OFF") set(line "#define QT_FEATURE_${feature} -1\n\n" PARENT_SCOPE) elseif(QT_FEATURE_${feature} STREQUAL "UNSET") set(line "#define QT_FEATURE_${feature} 0\n\n" PARENT_SCOPE) else() message(FATAL_ERROR "${feature} has unexpected value \"${QT_FEATURE_${feature}}\"!") endif() endfunction() function(qt_internal_feature_write_file file features extra) message("Generating file ${file}.") 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() function(qt_feature_module_end) set(flags) 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() 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_define_definitions}) qt_evaluate_feature_definition(${key}) unset(${key} PARENT_SCOPE) endforeach() foreach(feature ${all_features}) unset(_QT_FEATURE_DEFINITION_${feature} PARENT_SCOPE) endforeach() 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}" ) # 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/ 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")) get_target_property(targetType "${target}" TYPE) if("${targetType}" STREQUAL "INTERFACE_LIBRARY") set(propertyPrefix "INTERFACE_") else() set(propertyPrefix "") set_target_properties("${target}" PROPERTIES EXPORT_PROPERTIES "QT_ENABLED_PUBLIC_FEATURES;QT_DISABLED_PUBLIC_FEATURES;QT_ENABLED_PRIVATE_FEATURES;QT_DISABLED_PRIVATE_FEATURES;MODULE_PLUGIN_TYPES;QT_PLUGINS") 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() qt_feature_copy_global_config_features_to_core(${target}) endif() 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) endfunction() 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() endif() endfunction() function(qt_config_compile_test name) cmake_parse_arguments(arg "" "LABEL;PROJECT_PATH;C_STANDARD;CXX_STANDARD" "LIBRARIES;CODE" ${ARGN}) if(arg_PROJECT_PATH) message(STATUS "Performing Test ${arg_LABEL}") try_compile(HAVE_${name} "${CMAKE_BINARY_DIR}/config.tests/${name}" "${arg_PROJECT_PATH}" "${name}") 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) set(HAVE_${name} FALSE) break() endif() endif() endforeach() if(NOT DEFINED HAVE_${name}) set(_save_CMAKE_C_STANDARD "${CMAKE_C_STANDARD}") set(_save_CMAKE_CXX_STANDARD "${CMAKE_CXX_STANDARD}") if(arg_C_STANDARD) set(CMAKE_C_STANDARD "${arg_C_STANDARD}") endif() if(arg_CXX_STANDARD) set(CMAKE_CXX_STANDARD "${arg_CXX_STANDARD}") 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}) set(CMAKE_REQUIRED_LIBRARIES "${_save_CMAKE_REQUIRED_LIBRARIES}") set(CMAKE_C_STANDARD "${_save_CMAKE_C_STANDARD}") set(CMAKE_CXX_STANDARD "${_save_CMAKE_CXX_STANDARD}") endif() endif() set(TEST_${name} "${HAVE_${name}}" CACHE INTERNAL "${arg_LABEL}") endfunction() function(qt_config_compile_test_x86simd extension label) if (DEFINED TEST_X86SIMD_${extension}) return() endif() message(STATUS "Performing SIMD Test ${label}") 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 "-DSIMD:string=${extension}") if(${TEST_X86SIMD_${extension}}) set(status_label "Success") else() set(status_label "Failed") endif() message(STATUS "Performing SIMD Test ${label} - ${status_label}") set(TEST_subarch_${extension} "${TEST_X86SIMD_${extension}}" CACHE INTERNAL "${label}") endfunction() function(qt_make_features_available target) if(NOT "${target}" MATCHES "^${QT_CMAKE_EXPORT_NAMESPACE}::[a-zA-z][a-zA-Z0-9_-]*$") message(FATAL_ERROR "${target} does not match ${QT_CMAKE_EXPORT_NAMESPACE}::[a-zA-z][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(FATAL_ERROR "Feature ${feature} is already defined and has a different value when importing features from ${target}.") endif() set(QT_FEATURE_${feature} "${value}" CACHE INTERNAL "Qt feature: ${feature} (from target ${target})") endforeach() set(value OFF) endforeach() endforeach() endfunction()