CMake: Port most of the configure summary support

Teaches configurejson2cmake about summaries / reports, so things
like enabled features, configure sections, notes, etc.

Add relevant CMake API for adding summary sections and entries,
as well as configure reports. The commands record the passed data,
and the data is later evaluated when the summary needs to be printed.
This is needed, to ensure that all features are evaluated by the
time the summary is printed.

Some report and summary entries are not generated if they mention a
feature that is explicitly exclduded by configurejson2cmake's feature
mapping dictionary. This is to prevent CMake from failing at configure
time when trying to evaluate an unknown feature. We should re-enable
these in the future.

A few custom report types are skipped by configurejson2cmake (like
values of qmake CONFIG or buildParts).
These will have to be addressed a case-by-case basis if still needed.

Change-Id: I95d74ce34734d347681905f15a781f64b5bd5edc
Reviewed-by: Leander Beernaert <leander.beernaert@qt.io>
Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
This commit is contained in:
Alexandru Croitor 2020-03-02 18:06:46 +01:00
parent a64f4b4052
commit 95e1469eb8
6 changed files with 460 additions and 5 deletions

View File

@ -82,7 +82,7 @@ function(qt_run_config_test_architecture)
set(QT_BASE_CONFIGURE_TESTS_VARS_TO_EXPORT ${QT_BASE_CONFIGURE_TESTS_VARS_TO_EXPORT} CACHE INTERNAL "Test variables that should be exported")
list(JOIN _sub_architecture " " subarch_summary)
message(STATUS "Building for: ${QT_QMAKE_TARGET_MKSPEC} (${TEST_architecture_arch}, CPU features: ${subarch_summary})")
set_property(GLOBAL PROPERTY qt_configure_subarch_summary "${subarch_summary}")
endfunction()

View File

@ -1,11 +1,13 @@
function(qt_print_feature_summary)
include(FeatureSummary)
# Show which packages were found.
feature_summary(WHAT PACKAGES_FOUND
REQUIRED_PACKAGES_NOT_FOUND
RECOMMENDED_PACKAGES_NOT_FOUND
OPTIONAL_PACKAGES_NOT_FOUND
RUNTIME_PACKAGES_NOT_FOUND
FATAL_ON_MISSING_REQUIRED_PACKAGES)
qt_configure_print_summary()
endfunction()
function(qt_print_build_instructions)
@ -33,3 +35,328 @@ function(qt_print_build_instructions)
message("\nIf reconfiguration fails for some reason, try to remove 'CMakeCache.txt' \
from the build directory \n")
endfunction()
function(qt_configure_print_summary)
# Evaluate all recorded commands.
qt_configure_eval_commands()
# Show Qt-specific configure summary and any notes, wranings, etc.
if(__qt_configure_reports)
message("Configure summary:\n${__qt_configure_reports}")
endif()
if(__qt_configure_notes)
message("${__qt_configure_notes}")
endif()
if(__qt_configure_warnings)
message("${__qt_configure_warnings}")
endif()
if(__qt_configure_errors)
message("${__qt_configure_errors}")
endif()
message("")
if(__qt_configure_an_error_occurred)
message(FATAL_ERROR "Check the configuration messages for an error that has occurred.")
endif()
endfunction()
# Takes a list of arguments, and saves them to be evaluated at the end of the configuration
# phase when the configuration summary is shown.
function(qt_configure_record_command)
# Don't record commands when only evaluating features of a configure.cmake file.
if(__QtFeature_only_evaluate_features)
return()
endif()
get_property(command_count GLOBAL PROPERTY qt_configure_command_count)
if(NOT DEFINED command_count)
set(command_count 0)
else()
math(EXPR command_count "${command_count}+1")
endif()
set_property(GLOBAL PROPERTY qt_configure_command_${command_count} "${ARGV}")
set_property(GLOBAL PROPERTY qt_configure_command_count "${command_count}")
endfunction()
function(qt_configure_eval_commands)
get_property(command_count GLOBAL PROPERTY qt_configure_command_count)
if(NOT command_count)
set(command_count 0)
endif()
set(command_index 0)
while(command_index LESS command_count)
get_property(command_args GLOBAL PROPERTY qt_configure_command_${command_index})
if(NOT command_args)
message(FATAL_ERROR
"Empty arguments encountered while processing qt configure reports.")
endif()
list(POP_FRONT command_args command_name)
if(command_name STREQUAL ADD_SUMMARY_SECTION)
qt_configure_process_add_summary_section(${command_args})
elseif(command_name STREQUAL END_SUMMARY_SECTION)
qt_configure_process_end_summary_section(${command_args})
elseif(command_name STREQUAL ADD_REPORT_ENTRY)
qt_configure_process_add_report_entry(${command_args})
elseif(command_name STREQUAL ADD_SUMMARY_ENTRY)
qt_configure_process_add_summary_entry(${command_args})
elseif(command_name STREQUAL ADD_BUILD_TYPE_AND_CONFIG)
qt_configure_process_add_summary_build_type_and_config(${command_args})
elseif(command_name STREQUAL ADD_BUILD_MODE)
qt_configure_process_add_summary_build_mode(${command_args})
endif()
math(EXPR command_index "${command_index}+1")
endwhile()
# Propagate content to parent.
set(__qt_configure_reports "${__qt_configure_reports}" PARENT_SCOPE)
set(__qt_configure_notes "${__qt_configure_notes}" PARENT_SCOPE)
set(__qt_configure_warnings "${__qt_configure_warnings}" PARENT_SCOPE)
set(__qt_configure_errors "${__qt_configure_errors}" PARENT_SCOPE)
set(__qt_configure_an_error_occurred "${__qt_configure_an_error_occurred}" PARENT_SCOPE)
endfunction()
macro(qt_configure_add_report message)
string(APPEND __qt_configure_reports "\n${message}")
set(__qt_configure_reports "${__qt_configure_reports}" PARENT_SCOPE)
endmacro()
macro(qt_configure_add_report_padded label message)
qt_configure_get_padded_string("${__qt_configure_indent}${label}" "${message}" padded_message)
string(APPEND __qt_configure_reports "\n${padded_message}")
set(__qt_configure_reports "${__qt_configure_reports}" PARENT_SCOPE)
endmacro()
function(qt_configure_get_padded_string label value out_var)
set(pad_string "........................................")
string(LENGTH "${label}" label_len)
string(LENGTH "${pad_string}" pad_len)
math(EXPR pad_len "${pad_len}-${label_len}")
if(pad_len LESS "0")
set(pad_len "0")
endif()
string(SUBSTRING "${pad_string}" 0 "${pad_len}" pad_string)
set(output "${label} ${pad_string} ${value}")
set("${out_var}" "${output}" PARENT_SCOPE)
endfunction()
function(qt_configure_add_summary_entry)
qt_configure_record_command(ADD_SUMMARY_ENTRY ${ARGV})
endfunction()
function(qt_configure_process_add_summary_entry)
qt_parse_all_arguments(arg "qt_configure_add_summary_entry"
""
"ARGS;TYPE;MESSAGE" "CONDITION" ${ARGN})
if(NOT arg_TYPE)
set(arg_TYPE "feature")
endif()
if(NOT "${arg_CONDITION}" STREQUAL "")
qt_evaluate_config_expression(condition_result ${arg_CONDITION})
if(NOT condition_result)
return()
endif()
endif()
if(arg_TYPE STREQUAL "firstAvailableFeature")
set(first_feature_found FALSE)
set(message "")
string(REPLACE " " ";" args_list "${arg_ARGS}")
foreach(feature ${args_list})
qt_feature_normalize_name("${feature}" feature)
if(NOT DEFINED QT_FEATURE_${feature})
message(FATAL_ERROR "Asking for a report on undefined feature ${feature}.")
endif()
if(QT_FEATURE_${feature})
set(first_feature_found TRUE)
set(message "${QT_FEATURE_LABEL_${feature}}")
break()
endif()
endforeach()
if(NOT first_feature_found)
set(message "<none>")
endif()
qt_configure_add_report_padded("${arg_MESSAGE}" "${message}")
elseif(arg_TYPE STREQUAL "featureList")
set(available_features "")
string(REPLACE " " ";" args_list "${arg_ARGS}")
foreach(feature ${args_list})
qt_feature_normalize_name("${feature}" feature)
if(NOT DEFINED QT_FEATURE_${feature})
message(FATAL_ERROR "Asking for a report on undefined feature ${feature}.")
endif()
if(QT_FEATURE_${feature})
list(APPEND available_features "${QT_FEATURE_LABEL_${feature}}")
endif()
endforeach()
if(NOT available_features)
set(message "<none>")
else()
list(JOIN available_features " " message)
endif()
qt_configure_add_report_padded("${arg_MESSAGE}" "${message}")
elseif(arg_TYPE STREQUAL "feature")
qt_feature_normalize_name("${arg_ARGS}" feature)
set(label "${QT_FEATURE_LABEL_${feature}}")
if(NOT label)
set(label "${feature}")
endif()
if(QT_FEATURE_${feature})
set(value "yes")
else()
set(value "no")
endif()
qt_configure_add_report_padded("${label}" "${value}")
endif()
endfunction()
function(qt_configure_add_summary_build_type_and_config)
qt_configure_record_command(ADD_BUILD_TYPE_AND_CONFIG ${ARGV})
endfunction()
function(qt_configure_process_add_summary_build_type_and_config)
get_property(subarch_summary GLOBAL PROPERTY qt_configure_subarch_summary)
set(message
"Building for: ${QT_QMAKE_TARGET_MKSPEC} (${TEST_architecture_arch}, CPU features: ${subarch_summary})")
qt_configure_add_report("${message}")
set(message "Compiler: ")
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
string(APPEND message "clang (Apple)")
elseif(CLANG)
string(APPEND message "clang")
elseif(ICC)
string(APPEND message "intel_icc")
elseif(QCC)
string(APPEND message "rim_qcc")
elseif(GCC)
string(APPEND message "gcc")
elseif(MSVC)
string(APPEND message "msvc")
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GHS")
string(APPEND message "ghs")
else()
string(APPEND message "unknown (${CMAKE_CXX_COMPILER_ID})")
endif()
string(APPEND message " ${CMAKE_CXX_COMPILER_VERSION}")
qt_configure_add_report("${message}")
endfunction()
function(qt_configure_add_summary_build_mode)
qt_configure_record_command(ADD_BUILD_MODE ${ARGV})
endfunction()
function(qt_configure_process_add_summary_build_mode label)
set(message "")
if(DEFINED CMAKE_BUILD_TYPE)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
string(APPEND message "debug")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
string(APPEND message "release")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
string(APPEND message "release (with debug info)")
else()
string(APPEND message "${CMAKE_BUILD_TYPE}")
endif()
elseif(DEFINED CMAKE_CONFIGURATION_TYPES)
if("Release" IN_LIST CMAKE_CONFIGURATION_TYPES
AND "Debug" IN_LIST CMAKE_CONFIGURATION_TYPES)
string(APPEND message "debug and release")
elseif("RelWithDebInfo" IN_LIST CMAKE_CONFIGURATION_TYPES
AND "Debug" IN_LIST CMAKE_CONFIGURATION_TYPES)
string(APPEND message "debug and release (with debug info)")
else()
string(APPEND message "${CMAKE_CONFIGURATION_TYPES}")
endif()
endif()
qt_configure_add_report_padded("${label}" "${message}")
endfunction()
function(qt_configure_add_summary_section)
qt_configure_record_command(ADD_SUMMARY_SECTION ${ARGV})
endfunction()
function(qt_configure_process_add_summary_section)
qt_parse_all_arguments(arg "qt_configure_add_summary_section"
"" "NAME" "" ${ARGN})
qt_configure_add_report("${__qt_configure_indent}${arg_NAME}:")
if(NOT DEFINED __qt_configure_indent)
set(__qt_configure_indent " " PARENT_SCOPE)
else()
set(__qt_configure_indent "${__qt_configure_indent} " PARENT_SCOPE)
endif()
endfunction()
function(qt_configure_end_summary_section)
qt_configure_record_command(END_SUMMARY_SECTION ${ARGV})
endfunction()
function(qt_configure_process_end_summary_section)
string(LENGTH "${__qt_configure_indent}" indent_len)
if(indent_len GREATER_EQUAL 2)
string(SUBSTRING "${__qt_configure_indent}" 2 -1 __qt_configure_indent)
set(__qt_configure_indent "${__qt_configure_indent}" PARENT_SCOPE)
endif()
endfunction()
function(qt_configure_add_report_entry)
qt_configure_record_command(ADD_REPORT_ENTRY ${ARGV})
endfunction()
function(qt_configure_process_add_report_entry)
qt_parse_all_arguments(arg "qt_configure_add_report_entry"
""
"TYPE;MESSAGE" "CONDITION" ${ARGN})
set(possible_types NOTE WARNING ERROR FATAL_ERROR)
if(NOT "${arg_TYPE}" IN_LIST possible_types)
message(FATAL_ERROR "qt_configure_add_report_entry: '${arg_TYPE}' is not a valid type.")
endif()
if(NOT arg_MESSAGE)
message(FATAL_ERROR "qt_configure_add_report_entry: Empty message given.")
endif()
if(arg_TYPE STREQUAL "NOTE")
set(contents_var "__qt_configure_notes")
set(prefix "Note: ")
elseif(arg_TYPE STREQUAL "WARNING")
set(contents_var "__qt_configure_warnings")
set(prefix "WARNING: ")
elseif(arg_TYPE STREQUAL "ERROR")
set(contents_var "__qt_configure_errors")
set(prefix "ERROR: ")
elseif(arg_TYPE STREQUAL "FATAL_ERROR")
set(contents_var "__qt_configure_errors")
set(prefix "FATAL ERROR: ")
endif()
if(NOT "${arg_CONDITION}" STREQUAL "")
qt_evaluate_config_expression(condition_result ${arg_CONDITION})
endif()
if("${arg_CONDITION}" STREQUAL "" OR condition_result)
set(new_report "${prefix}${arg_MESSAGE}")
string(APPEND "${contents_var}" "\n${new_report}")
if(arg_TYPE STREQUAL "ERROR" OR arg_TYPE STREQUAL "FATAL_ERROR")
set(__qt_configure_an_error_occurred "TRUE" PARENT_SCOPE)
endif()
endif()
set("${contents_var}" "${${contents_var}}" PARENT_SCOPE)
endfunction()

View File

@ -94,8 +94,6 @@ macro(qt_build_repo_begin)
endmacro()
macro(qt_build_repo_end)
include(QtBuildInformation)
if(NOT QT_BUILD_STANDALONE_TESTS)
# Delayed actions on some of the Qt targets:
include(QtPostProcess)

View File

@ -14,6 +14,9 @@ function(qt_feature_module_begin)
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)
@ -278,6 +281,9 @@ function(qt_evaluate_feature feature)
qt_feature_set_cache_value(cache "${feature}" "${emit_if}" "${result}" "${arg_LABEL}")
qt_feature_set_value("${feature}" "${cache}" "${emit_if}" "${condition}" "${arg_LABEL}")
# Store each feature's label for summary info.
set(QT_FEATURE_LABEL_${feature} "${arg_LABEL}" CACHE INTERNAL "")
endfunction()
function(qt_feature_config feature config_var_name)
@ -581,6 +587,7 @@ function(qt_feature_module_end)
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)
endfunction()
function(qt_feature_copy_global_config_features_to_core target)

View File

@ -148,6 +148,7 @@ endif()
include(QtBuild)
## Qt Feature support:
include(QtBuildInformation)
include(QtFeature)
## Compiler optimization flags:

View File

@ -255,7 +255,7 @@ def map_condition(condition):
mapped_features = {"gbm": "gbm_FOUND", "system-xcb": "ON"}
# Turn foo != "bar" into (NOT foo STREQUAL 'bar')
condition = re.sub(r"(.+)\s*!=\s*('.+')", "(! \\1 == \\2)", condition)
condition = re.sub(r"([^ ]+)\s*!=\s*('.*?')", "(! \\1 == \\2)", condition)
condition = condition.replace("!", "NOT ")
condition = condition.replace("&&", " AND ")
@ -753,7 +753,7 @@ def parseTest(ctx, test, data, cm_fh):
print(f" XXXX UNHANDLED TEST TYPE {data['type']} in test description")
def parseFeature(ctx, feature, data, cm_fh):
def get_feature_mapping():
# This is *before* the feature name gets normalized! So keep - and + chars, etc.
feature_mapping = {
"alloc_h": None, # handled by alloc target
@ -848,7 +848,11 @@ def parseFeature(ctx, feature, data, cm_fh):
"webp": {"condition": "QT_FEATURE_imageformatplugin AND WrapWebP_FOUND"},
"xkbcommon-system": None, # another system library, just named a bit different from the rest
}
return feature_mapping
def parseFeature(ctx, feature, data, cm_fh):
feature_mapping = get_feature_mapping()
mapping = feature_mapping.get(feature, {})
if mapping is None:
@ -1087,6 +1091,110 @@ def parseFeature(ctx, feature, data, cm_fh):
cm_fh.write(")\n")
def processSummaryHelper(ctx, entries, cm_fh):
for entry in entries:
if isinstance(entry, str):
name = entry
cm_fh.write(f'qt_configure_add_summary_entry(ARGS "{name}")\n')
elif "type" in entry \
and entry["type"] in ["feature", "firstAvailableFeature", "featureList"]:
function_args = []
entry_type = entry["type"]
if entry_type in ["firstAvailableFeature", "featureList"]:
feature_mapping = get_feature_mapping()
unhandled_feature = False
for feature_name, value in feature_mapping.items():
# Skip entries that mention a feature which is
# skipped by configurejson2cmake in the feature
# mapping. This is not ideal, but prevents errors at
# CMake configuration time.
if not value and f"{feature_name}" in entry["args"]:
unhandled_feature = True
break
if unhandled_feature:
print(f" XXXX UNHANDLED FEATURE in SUMMARY TYPE {entry}.")
continue
if entry_type != "feature":
function_args.append(lineify("TYPE", entry_type))
if "args" in entry:
args = entry["args"]
function_args.append(lineify("ARGS", args))
if "message" in entry:
message = entry["message"]
function_args.append(lineify("MESSAGE", message))
if "condition" in entry:
condition = map_condition(entry["condition"])
function_args.append(lineify("CONDITION", condition, quote=False))
entry_args_string = "".join(function_args)
cm_fh.write(f'qt_configure_add_summary_entry(\n{entry_args_string})\n')
elif "type" in entry and entry["type"] == "buildTypeAndConfig":
cm_fh.write(f'qt_configure_add_summary_build_type_and_config()\n')
elif "type" in entry and entry["type"] == "buildMode":
message = entry["message"]
cm_fh.write(f'qt_configure_add_summary_build_mode({message})\n')
elif "section" in entry:
section = entry["section"]
cm_fh.write(f'qt_configure_add_summary_section(NAME "{section}")\n')
processSummaryHelper(ctx, entry["entries"], cm_fh)
cm_fh.write(f'qt_configure_end_summary_section() # end of "{section}" section\n')
else:
print(f" XXXX UNHANDLED SUMMARY TYPE {entry}.")
def processReportHelper(ctx, entries, cm_fh):
feature_mapping = get_feature_mapping()
for entry in entries:
if isinstance(entry, dict):
entry_args = []
if "type" not in entry:
print(f" XXXX UNHANDLED REPORT TYPE missing type in {entry}.")
continue
report_type = entry["type"]
if report_type not in ["note", "warning", "error"]:
print(f" XXXX UNHANDLED REPORT TYPE unknown type in {entry}.")
continue
report_type = report_type.upper()
entry_args.append(lineify("TYPE", report_type, quote=False))
message = entry["message"]
# Replace semicolons, qt_parse_all_arguments can't handle
# them due to an escaping bug in CMake regarding escaping
# macro arguments.
# https://gitlab.kitware.com/cmake/cmake/issues/19972
message = message.replace(";", ",")
entry_args.append(lineify("MESSAGE", message))
# Need to overhaul everything to fix conditions.
if "condition" in entry:
condition = entry["condition"]
unhandled_condition = False
for feature_name, value in feature_mapping.items():
# Skip reports that mention a feature which is
# skipped by configurejson2cmake in the feature
# mapping. This is not ideal, but prevents errors at
# CMake configuration time.
if not value and f"features.{feature_name}" in condition:
unhandled_condition = True
break
if unhandled_condition:
print(f" XXXX UNHANDLED CONDITION in REPORT TYPE {entry}.")
continue
condition = map_condition(condition)
entry_args.append(lineify("CONDITION", condition, quote=False))
entry_args_string = "".join(entry_args)
cm_fh.write(f'qt_configure_add_report_entry(\n{entry_args_string})\n')
else:
print(f" XXXX UNHANDLED REPORT TYPE {entry}.")
def processInputs(ctx, data, cm_fh):
print(" inputs:")
if "commandline" not in data:
@ -1128,6 +1236,18 @@ def processLibraries(ctx, data, cm_fh):
parseLib(ctx, lib, data, cm_fh, cmake_find_packages_set)
def processReports(ctx, data, cm_fh):
if "summary" in data:
print(" summary:")
processSummaryHelper(ctx, data["summary"], cm_fh)
if "report" in data:
print(" report:")
processReportHelper(ctx, data["report"], cm_fh)
if "earlyReport" in data:
print(" earlyReport:")
processReportHelper(ctx, data["earlyReport"], cm_fh)
def processSubconfigs(path, ctx, data):
assert ctx is not None
if "subconfigs" in data:
@ -1162,6 +1282,8 @@ def processJson(path, ctx, data):
processFeatures(ctx, data, cm_fh)
processReports(ctx, data, cm_fh)
if ctx.get("module") == "global":
cm_fh.write(
'\nqt_extra_definition("QT_VERSION_STR" "\\"${PROJECT_VERSION}\\"" PUBLIC)\n'