qt/qtbase: finish 6.11.0->6.11.1 vendored bump left half-done by build system

The recipe tar= and blake3 were bumped to 6.11.1 at the 0.3.1 branch cut, but
the vendored source/ tree was never propagated: it was a muddle of 6.11.0 and
6.11.1 files plus injected line-duplication corruption. Rebase the Redox port
onto a clean pristine 6.11.1 tree:
 - pristine 6.11.1 + redox.patch + in6-pktinfo/ifreq/forkfd_qt patches
 - re-apply the 6 baked-only Redox shims never captured as patches
   (forkfd waitid, qprocess vfork, posix-sem decls, qassert, qplugin, qnet)
 - qtwaylandscanner null-guard one-liner (its patch file was malformed)
 - drop the sys/ioctl.h / assert.h / QT_CONFIG(opengl) line-duplication corruption
Result differs from pristine 6.11.1 by exactly the 36-file Redox port.
This commit is contained in:
2026-07-31 20:02:10 +03:00
parent c2eb289014
commit 3a5b67ddd1
1469 changed files with 25935 additions and 21304 deletions
+5 -2
View File
@@ -4,10 +4,13 @@
# ensure that when this file is included the second time in a toplevel buld
# (directly by qtbase) we actually add the extra definitions
if (NOT DEFINED QT_SUPERBUILD OR DEFINED QT_REPO_MODULE_VERSION)
set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_LEAN_HEADERS=1")
set(QT_EXTRA_INTERNAL_TARGET_DEFINES
"QT_LEAN_HEADERS=1"
"QT_NO_SINGLE_ARGUMENT_QHASH_OVERLOAD=1"
)
endif()
set(QT_REPO_MODULE_VERSION "6.11.0")
set(QT_REPO_MODULE_VERSION "6.11.1")
set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1")
set(QT_COPYRIGHT "Copyright (C) The Qt Company Ltd. and other contributors.")
@@ -1 +1 @@
QT_PACKAGEDATE_STR=2026-03-17
QT_PACKAGEDATE_STR=2026-05-08
+1 -1
View File
@@ -1 +1 @@
8ba7ea4b77a4b8f1948760221e264917ddc9e1c8
59c81a3c2247b821b9b84b4eb8d939b77e07e276
@@ -459,6 +459,13 @@ elseif(WASM)
"${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/qt-wasmtestrunner.py" @ONLY)
qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_LIBEXECDIR}/qt-wasmtestrunner.py"
DESTINATION "${INSTALL_LIBEXECDIR}")
elseif(WIN32)
qt_copy_or_install(FILES "cmake/windows/app.exe.manifest.in"
DESTINATION "${__GlobalConfig_install_dir}/windows"
)
file(COPY "cmake/windows/app.exe.manifest.in"
DESTINATION "${__GlobalConfig_install_dir}/windows"
)
endif()
# Install CI support files to libexec.
@@ -298,11 +298,13 @@ function(qt_internal_get_qt_build_public_helpers out_var)
QtPublicPluginHelpers
QtPublicPluginHelpers_v2
QtPublicSbomAttributionHelpers
QtPublicSbomBuildToolHelpers
QtPublicSbomCommonGenerationHelpers
QtPublicSbomCpeHelpers
QtPublicSbomCycloneDXHelpers
QtPublicSbomDocumentNamespaceHelpers
QtPublicSbomDepHelpers
QtPublicSbomExternalReferenceHelpers
QtPublicSbomFileHelpers
QtPublicSbomGenerationHelpers
QtPublicSbomGenerationCycloneDXHelpers
@@ -312,6 +314,7 @@ function(qt_internal_get_qt_build_public_helpers out_var)
QtPublicSbomPurlHelpers
QtPublicSbomPythonHelpers
QtPublicSbomQtEntityHelpers
QtPublicSbomRelationshipHelpers
QtPublicSbomSystemDepHelpers
QtPublicTargetHelpers
QtPublicTestHelpers
@@ -243,6 +243,14 @@ macro(qt_internal_set_qt_apple_support_files_path)
endif()
endmacro()
macro(qt_internal_set_qt_windows_support_files_path)
# This is analogous to what we have in QtConfig.cmake.in. It's copied here so that
# tests can be built in tree.
if(WIN32)
set(__qt_internal_cmake_windows_support_files_path "${QT_CMAKE_DIR}/windows")
endif()
endmacro()
macro(qt_internal_set_qt_staging_prefix)
if(NOT "${CMAKE_STAGING_PREFIX}" STREQUAL "")
set(QT_STAGING_PREFIX "${CMAKE_STAGING_PREFIX}")
@@ -267,6 +275,8 @@ macro(qt_internal_setup_paths_and_prefixes)
qt_internal_set_qt_cmake_dir()
qt_internal_set_qt_apple_support_files_path()
qt_internal_set_qt_windows_support_files_path()
endmacro()
# Returns the prefix for the variables defined by HostInfo package. The prefix
@@ -370,10 +370,6 @@ function(qt_internal_add_example_external_project subdir)
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}")
if(QT_FEATURE_developer_build)
set(QT_LINT_EXAMPLES ON)
endif()
_qt_internal_get_build_vars_for_external_projects(
CMAKE_DIR_VAR qt_cmake_dir
PREFIXES_VAR qt_prefixes
@@ -388,10 +384,6 @@ function(qt_internal_add_example_external_project subdir)
list(APPEND var_defs
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${qt_cmake_dir}/qt.toolchain.cmake
)
if(QT_LINT_EXAMPLES)
list(APPEND var_defs -DQT_LINT_EXAMPLES=ON)
endif()
else()
list(PREPEND CMAKE_PREFIX_PATH ${qt_prefixes})
@@ -404,6 +396,16 @@ function(qt_internal_add_example_external_project subdir)
endif()
endif()
if(QT_FEATURE_developer_build)
set(QT_LINT_EXAMPLES ON)
endif()
# This might be set even without a developer build, e.g. in CI options, so it's a separate
# check.
if(QT_LINT_EXAMPLES)
list(APPEND var_defs -DQT_LINT_EXAMPLES:BOOL=ON)
endif()
# We we need to augment the CMAKE_MODULE_PATH with the current repo cmake build dir, to find
# files like FindWrapBundledFooConfigExtra.cmake.
set(module_paths "${qt_prefixes}")
@@ -376,12 +376,12 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL \"4.0\"
set(CMAKE_POLICY_VERSION_MINIMUM \"\${__qt_toolchain_cmake_policy_version_minimum}\")
message(DEBUG
\"Setting CMAKE_POLICY_VERSION_MINIMUM to \"
\"\${__qt_toolchain_cmake_policy_version_minimum}\ in toolchain file.\")
\"\${__qt_toolchain_cmake_policy_version_minimum} in toolchain file.\")
endif()
")
else()
message(FATAL_ERROR "Unknown TYPE value '${arg_TYPE}'. "
" Supported values are COMMAND_LINE and VAR_ASSIGNMENT.")
" Supported values are COMMAND_LINE and TOOLCHAIN_FILE_ASSIGNMENT.")
endif()
endif()
@@ -79,6 +79,10 @@ if(APPLE)
endif()
endif()
if(WIN32)
set(__qt_internal_cmake_windows_support_files_path "${_qt_import_prefix}/windows")
endif()
# Public helpers available to all Qt packages.
set(__qt_public_files_to_include
@QT_PUBLIC_FILES_TO_INCLUDE@
@@ -1462,8 +1462,8 @@ function(qt_internal_set_up_config_optimizations_like_in_qmake)
# Todo:
# When the new toolchain is fixed, and it doesn't fall back to the legacy
# anymore by default, then we should be able to remove this workaround.
if(ANDROID AND ANDROID_COMPILER_FLAGS MATCHES "(^| )-g")
qt_internal_remove_compiler_flags("-g")
if(ANDROID AND ANDROID_COMPILER_FLAGS MATCHES "(^| )-g( |$)")
qt_internal_remove_compiler_flags("(^| )-g( |$)" REGEX)
qt_internal_add_compiler_flags(FLAGS "-g" CONFIGS DEBUG RELWITHDEBINFO)
endif()
@@ -682,7 +682,7 @@ function(qt_internal_add_module target)
foreach(public_lib IN LISTS arg_PUBLIC_LIBRARIES)
if("${public_lib}" MATCHES "^(Qt|${QT_CMAKE_EXPORT_NAMESPACE})::(.*)Private$")
message(AUTHOR_WARNING
"${target} specfies ${public_lib} in its PUBLIC_LIBRARIES option. "
"${target} specifies ${public_lib} in its PUBLIC_LIBRARIES option. "
"Public modules should not link publicly to Private modules, because that "
"exposes private module headers to user projects without them opting into it. "
"Update the project to use the LIBRARIES keyword instead."
@@ -344,10 +344,9 @@ function(qt_internal_add_plugin target)
qt_internal_wrap_private_modules("${target}"
OUT_VAR qt_libs_private
LIBRARIES ${qt_libs_private})
list(APPEND qt_register_target_depentdencies_args PRIVATE ${qt_libs_private})
list(APPEND qt_register_target_dependencies_args PRIVATE ${qt_libs_private})
endif()
qt_internal_register_target_dependencies("${target}"
${qt_register_target_dependencies_args})
qt_internal_register_target_dependencies("${target}" ${qt_register_target_dependencies_args})
if(target_type STREQUAL STATIC_LIBRARY)
if(qt_module_target)
@@ -392,6 +391,28 @@ function(qt_internal_add_plugin target)
set(test_plugin_arg TEST_PLUGIN)
endif()
# Some plugins like the sqldriver ones can be built as part of the main repo or as a
# separate standalone project. We need to make sure we exclude loading the Qt prebuilt
# plugins when loading such a project regardless which way it is built.
if(QT_STANDALONE_PROJECT_NAME)
set(project_names_arg "${QT_STANDALONE_PROJECT_NAME}")
if(QT_PARENT_PROJECT_NAME)
list(APPEND project_names_arg "${QT_PARENT_PROJECT_NAME}")
else()
message(WARNING
"QT_STANDALONE_PROJECT_NAME is set to '${QT_STANDALONE_PROJECT_NAME}', but "
"QT_PARENT_PROJECT_NAME is not set. This can cause issues when building the "
"standalone project. Set QT_PARENT_PROJECT_NAME to the submodule's "
"root CMakeLists.txt project name to avoid this warning."
)
endif()
else()
set(project_names_arg "${PROJECT_NAME}")
endif()
list(PREPEND project_names_arg "PROJECT_NAMES")
string(REPLACE ";" " " project_names_arg "${project_names_arg}")
configure_package_config_file(
"${QT_CMAKE_DIR}/QtPluginConfig.cmake.in"
"${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}Config.cmake"
@@ -406,7 +427,7 @@ function(qt_internal_add_plugin target)
_qt_internal_should_include_targets(
TARGETS ${target}
NAMESPACE ${INSTALL_CMAKE_NAMESPACE}::
PROJECT_NAME ${PROJECT_NAME}
${project_names_arg}
OUT_VAR_SHOULD_SKIP __qt_${target}_skip_include_targets_file
${test_plugin_arg}
)
@@ -685,6 +685,30 @@ function(_qt_internal_set_apple_localizations target)
return()
endif()
get_target_property(plist_file "${target}" MACOSX_BUNDLE_INFO_PLIST)
if (NOT plist_file)
return()
endif()
if(NOT "${QT_I18N_SOURCE_LANGUAGE}" STREQUAL "")
_qt_internal_plist_buddy("${plist_file}"
COMMANDS "print CFBundleDevelopmentRegion"
OUTPUT_VARIABLE existing_dev_region
)
if(existing_dev_region)
_qt_internal_plist_buddy("${plist_file}"
COMMANDS
"Set CFBundleDevelopmentRegion ${QT_I18N_SOURCE_LANGUAGE}"
)
else()
_qt_internal_plist_buddy("${plist_file}"
COMMANDS
"Add CFBundleDevelopmentRegion string"
"Set CFBundleDevelopmentRegion ${QT_I18N_SOURCE_LANGUAGE}"
)
endif()
endif()
set(supported_languages "${QT_I18N_TRANSLATED_LANGUAGES}")
if("${QT_I18N_TRANSLATED_LANGUAGES}" STREQUAL "")
get_target_property(supported_languages "${target}" _qt_apple_supported_languages)
@@ -692,10 +716,6 @@ function(_qt_internal_set_apple_localizations target)
return()
endif()
endif()
get_target_property(plist_file "${target}" MACOSX_BUNDLE_INFO_PLIST)
if (NOT plist_file)
return()
endif()
_qt_internal_plist_buddy("${plist_file}"
COMMANDS "print CFBundleLocalizations"
@@ -712,20 +732,6 @@ function(_qt_internal_set_apple_localizations target)
"Delete CFBundleAllowMixedLocalizations"
)
endif()
if(NOT "${QT_I18N_SOURCE_LANGUAGE}" STREQUAL "")
_qt_internal_plist_buddy("${plist_file}"
COMMANDS "print CFBundleDevelopmentRegion"
OUTPUT_VARIABLE existing_dev_region
)
if(NOT existing_dev_region)
_qt_internal_plist_buddy("${plist_file}"
COMMANDS
"Add CFBundleDevelopmentRegion string"
"Set CFBundleDevelopmentRegion ${QT_I18N_SOURCE_LANGUAGE}"
)
endif()
endif()
endfunction()
function(_qt_internal_set_ios_simulator_arch target)
@@ -104,6 +104,9 @@ function(__qt_internal_get_possible_cmake_dirs out_paths prefix_path)
if(EXISTS "${prefix_path}/lib/cmake")
list(APPEND ${out_paths} "${prefix_path}/lib/cmake")
endif()
if(EXISTS "${prefix_path}/lib64/cmake")
list(APPEND ${out_paths} "${prefix_path}/lib64/cmake")
endif()
list(REMOVE_DUPLICATES ${out_paths})
set(${out_paths} "${${out_paths}}" PARENT_SCOPE)
@@ -1055,9 +1058,14 @@ endfunction()
# `OUT_VAR_SHOULD_SKIP`
# Output variable indicating if the `include(*Targets.cmake)` should be skipped
#
# `PROJECT_NAMES`
# The original project names that defined the current Config.cmake file at the time of the
# generation. For now only used for plugins. Mostly it's just the main repo project name,
# but for some projects like the sqldriver one that can be built standalone, there can be
# more than one.
#
# `PROJECT_NAME`
# The original project that defined the current Config.cmake file at the time of the
# generation. For now only used for plugins.
# Deprecated. Same as the one above, but only takes a single value. Kept for compatibility.
#
# `TEST_PLUGIN`
# If the module is a TEST_PLUGIN, we do additional checks based on
@@ -1073,10 +1081,11 @@ function(_qt_internal_should_include_targets)
set(single_args
NAMESPACE
OUT_VAR_SHOULD_SKIP
PROJECT_NAME
PROJECT_NAME # deprecated
)
set(multi_args
TARGETS
PROJECT_NAMES
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${option_args}" "${single_args}" "${multi_args}")
@@ -1124,7 +1133,15 @@ function(_qt_internal_should_include_targets)
endif()
endforeach()
set(project_names "")
if(arg_PROJECT_NAMES)
list(APPEND project_names ${arg_PROJECT_NAMES})
endif()
if(arg_PROJECT_NAME)
list(APPEND project_names ${arg_PROJECT_NAME})
endif()
if(project_names)
# Check if we are in a top-level build and which submodules will be included
if(NOT QT_INTERNAL_BUILD_STANDALONE_PARTS AND DEFINED Qt_SOURCE_DIR)
# All projects will be rebuilt so skip any include targets
@@ -1134,7 +1151,7 @@ function(_qt_internal_should_include_targets)
# Check if we are building the current project
if(NOT QT_INTERNAL_BUILD_STANDALONE_PARTS AND
PROJECT_NAME STREQUAL arg_PROJECT_NAME)
PROJECT_NAME IN_LIST project_names)
# We are currently building the project, so skip the include targets
set(${arg_OUT_VAR_SHOULD_SKIP} ON PARENT_SCOPE)
return()
@@ -1142,7 +1159,7 @@ function(_qt_internal_should_include_targets)
# Check if we are building the standalone tests
if(arg_TEST_PLUGIN AND QT_BUILD_STANDALONE_TESTS AND
PROJECT_NAME STREQUAL arg_PROJECT_NAME)
PROJECT_NAME IN_LIST project_names)
set(${arg_OUT_VAR_SHOULD_SKIP} ON PARENT_SCOPE)
return()
endif()
@@ -1,6 +1,13 @@
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Save the 'macros base dir' in a global property instead of a variable, to allow access in a
# deferred function where the variable might not be accessible by the function scope.
# We explicitly don't set CMAKE_CURRENT_FUNCTION_LIST_DIR as we do in some other cases, because
# that can be overridden by a different file depending on the inclusion order of the
# cmake files, and thus pointing to a different dir.
set_property(GLOBAL PROPERTY __qt_sbom_generation_helpers_base_dir "${CMAKE_CURRENT_LIST_DIR}")
# Helper function to get common project related variables for SBOM generation for both SPDX and
# CycloneDX formats.
function(_qt_internal_sbom_get_common_project_variables)
@@ -70,7 +77,9 @@ function(_qt_internal_sbom_get_common_project_variables)
# Prevent collision with other generated SPDXID with -[0-9]+ suffix.
string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
_qt_internal_sbom_get_spdx_id_unique_suffix(spdx_id_unique_suffix)
set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}${spdx_id_unique_suffix}")
set(${arg_OUT_VAR_PROJECT_FOR_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
@@ -105,7 +114,7 @@ function(_qt_internal_sbom_save_common_path_variables_in_global_properties)
OUTPUT
OUTPUT_RELATIVE_PATH
SBOM_DIR
PROPERTY_SUFFIX
SBOM_FORMAT
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -137,7 +146,13 @@ function(_qt_internal_sbom_save_common_path_variables_in_global_properties)
get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
set(suffix "${arg_PROPERTY_SUFFIX}")
if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
set(suffix "")
elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
set(suffix "_cydx")
else()
message(FATAL_ERROR "Unknown SBOM_FORMAT: ${arg_SBOM_FORMAT}")
endif()
set_property(GLOBAL PROPERTY _qt_sbom_build_output_path${suffix} "${build_sbom_path}")
set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext${suffix}
@@ -204,6 +219,25 @@ function(_qt_internal_sbom_get_common_path_variables_from_global_properties)
endif()
endfunction()
# Create the build directory that will contain all the intermediate generated sbom files.
function(_qt_internal_sbom_create_sbom_staging_dir)
set(opt_args "")
set(single_args
OUT_VAR_SBOM_DIR
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_OUT_VAR_SBOM_DIR)
message(FATAL_ERROR "OUT_VAR_SBOM_DIR argument is required.")
endif()
_qt_internal_get_current_project_sbom_dir(sbom_dir)
file(MAKE_DIRECTORY "${sbom_dir}")
set(${arg_OUT_VAR_SBOM_DIR} "${sbom_dir}" PARENT_SCOPE)
endfunction()
# Helper function to create a staging file for SBOM generation.
# It is the file that will be incrementally assembled by having content appended to it.
# Also creates the intro file that will add the assembled content for the SBOM project, aka for the
@@ -211,19 +245,20 @@ endfunction()
function(_qt_internal_sbom_create_sbom_staging_file)
set(opt_args "")
set(single_args
CONTENT
SBOM_FORMAT
REPO_PROJECT_NAME_LOWERCASE
OUT_VAR_CREATE_STAGING_FILE
OUT_VAR_SBOM_DIR
SBOM_DIR
)
set(multi_args
RELATIONSHIP_STRINGS
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
# Create the directory that will contain all sbom related files.
_qt_internal_get_current_project_sbom_dir(sbom_dir)
file(MAKE_DIRECTORY "${sbom_dir}")
if(NOT arg_SBOM_DIR)
message(FATAL_ERROR "SBOM_DIR argument is required")
endif()
set(sbom_dir "${arg_SBOM_DIR}")
if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
set(doc_base_name "SPDXRef-DOCUMENT")
@@ -234,6 +269,14 @@ function(_qt_internal_sbom_create_sbom_staging_file)
")
_qt_internal_get_staging_area_spdx_file_path(staging_area_file)
set(starting_message "Starting SPDX SBOM generation in build dir: ${staging_area_file}")
set(extra_intro_content "")
set(project_relationship_strings "")
if(arg_RELATIONSHIP_STRINGS)
list(JOIN arg_RELATIONSHIP_STRINGS "\n" arg_RELATIONSHIP_STRINGS)
# Prepend a newline to separate from the very first always there relationship.
string(APPEND extra_intro_content "\n${arg_RELATIONSHIP_STRINGS}")
endif()
elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
set(doc_base_name "cydx-document")
set(doc_extension "cdx.in.toml")
@@ -242,12 +285,40 @@ function(_qt_internal_sbom_create_sbom_staging_file)
_qt_internal_get_staging_area_cydx_file_path(staging_area_file)
set(starting_message
"Starting CycloneDX SBOM TOML file generation in build dir: ${staging_area_file}")
set(extra_intro_content "")
if(arg_RELATIONSHIP_STRINGS)
list(JOIN arg_RELATIONSHIP_STRINGS "\n" arg_RELATIONSHIP_STRINGS)
set(project_relationship_strings "${arg_RELATIONSHIP_STRINGS}")
else()
set(project_relationship_strings "")
endif()
endif()
# Generate project document intro spdx file.
# Assemble final intro content.
get_property(intro_content GLOBAL PROPERTY _qt_sbom_project_intro_content${suffix})
if(NOT intro_content)
message(FATAL_ERROR "Missing intro content for SBOM generation.")
endif()
if(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
string(REPLACE "<PROJECT_RELATIONSHIP_PLACEHOLDER>" "${project_relationship_strings}"
intro_content "${intro_content}")
endif()
if(extra_intro_content)
string(APPEND intro_content "${extra_intro_content}")
endif()
# Final new line to separate the main package from rest of info.
if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
string(APPEND intro_content "\n")
endif()
# Generate project document intro file.
set(document_intro_file_name
"${sbom_dir}/${doc_base_name}-${arg_REPO_PROJECT_NAME_LOWERCASE}.${doc_extension}")
file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${intro_content}")
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
@@ -256,7 +327,7 @@ function(_qt_internal_sbom_create_sbom_staging_file)
set(multi_config_suffix "")
endif()
# Create cmake file to append the document intro spdx to the staging file.
# Create cmake file to append the document intro to the staging file.
set(create_staging_file
"${sbom_dir}/append_document_to_staging${suffix}${multi_config_suffix}.cmake")
@@ -269,9 +340,7 @@ function(_qt_internal_sbom_create_sbom_staging_file)
file(WRITE \"${staging_area_file}\" \"\${content}\")
")
file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
set(${arg_OUT_VAR_CREATE_STAGING_FILE} "${create_staging_file}" PARENT_SCOPE)
set(${arg_OUT_VAR_SBOM_DIR} "${sbom_dir}" PARENT_SCOPE)
set_property(GLOBAL PROPERTY _qt_sbom_cmake_intro_file${suffix} "${create_staging_file}")
endfunction()
# Helper function to save common project info like supplier, project name, spdx id in global
@@ -285,7 +354,9 @@ function(_qt_internal_sbom_save_project_info_in_global_properties)
PROJECT
PROJECT_SPDX_ID
)
set(multi_args "")
set(multi_args
EXTERNAL_REFERENCE_SBOM_DIRS
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -295,6 +366,34 @@ function(_qt_internal_sbom_save_project_info_in_global_properties)
set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${arg_PROJECT_SPDX_ID}")
# Used for finding external reference spdx documents.
if(arg_EXTERNAL_REFERENCE_SBOM_DIRS)
set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs
${arg_EXTERNAL_REFERENCE_SBOM_DIRS})
endif()
endfunction()
# Helper function to save sbom intro content (per sbom format) in a global property.
function(_qt_internal_sbom_save_intro_content)
set(opt_args "")
set(single_args
SBOM_FORMAT
CONTENT
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
set(suffix "")
elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
set(suffix "_cydx")
else()
message(FATAL_ERROR "Unknown SBOM_FORMAT: ${arg_SBOM_FORMAT}")
endif()
set_property(GLOBAL PROPERTY _qt_sbom_project_intro_content${suffix} "${content}")
endfunction()
# Helper function to get cmake include files for SBOM generation from global properties.
@@ -320,7 +419,10 @@ function(_qt_internal_sbom_get_cmake_include_files)
_qt_internal_sbom_collect_cmake_include_files(includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_include_files${suffix} _qt_sbom_cmake_end_include_files${suffix}
PROPERTIES
_qt_sbom_cmake_intro_file${suffix}
_qt_sbom_cmake_include_files${suffix}
_qt_sbom_cmake_end_include_files${suffix}
)
# Before checksum includes are included after the verification codes have been collected
@@ -443,8 +545,13 @@ function(_qt_internal_sbom_create_build_time_sbom_targets)
set(build_sbom_install_prefix "${CMAKE_BINARY_DIR}")
endif()
get_property(macro_module_base_dir GLOBAL PROPERTY __qt_sbom_generation_helpers_base_dir)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(content "
# Include helpers functions.
include(\"${macro_module_base_dir}/QtPublicCMakeHelpers.cmake\")
include(\"${macro_module_base_dir}/QtPublicSbomExternalReferenceHelpers.cmake\")
# QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
# This allows reusing the same cmake file for both build and install.
if(NOT DEFINED QT_SBOM_BUILD_TIME)
@@ -239,12 +239,20 @@ function(_qt_internal_sbom_get_cyclone_bom_serial_number)
endif()
endfunction()
# Returns the bom version field that is part of a bom-link to refer to the target in an
# external CycloneDX document.
function(_qt_internal_sbom_get_cydx_external_urn_bom_version target out_var)
# Currently 1, but we might change it to be configurable in the future.
set(bom_version "1")
set(${out_var} "${bom_version}" PARENT_SCOPE)
endfunction()
# See https://github.com/CycloneDX/guides/blob/main/SBOM/en/0x52-Linking.md
function(_qt_internal_sbom_get_cydx_external_bom_link target out_var)
get_target_property(spdx_id "${target}" _qt_sbom_spdx_id)
get_target_property(bom_serial_number "${target}" _qt_sbom_cydx_bom_serial_number_uuid)
set(bom_version "1")
_qt_internal_sbom_get_cydx_external_urn_bom_version("${target}" bom_version)
set(bom_link "urn:cdx:${bom_serial_number}/${bom_version}#${spdx_id}")
set(${out_var} "${bom_link}" PARENT_SCOPE)
@@ -4,21 +4,20 @@
# Walks a target's direct dependencies and assembles a list of relationships between the packages
# of the target dependencies.
# Currently handles various Qt targets and system libraries.
# For SPDX documents, it collects the relationships in OUT_SPDX_RELATIONSHIPS.
# For CYDX documents, it collects the dependencies (not relationship info) in OUT_CYDX_DEPENDENCIES.
# Collects the relationships in OUT_SBOM_RELATIONSHIP_ENTRIES.
# If a CYDX dependency is in an external docuemnt, the dependency is added to
# OUT_EXTERNAL_TARGET_DEPENDENCIES instead.
# OUT_EXTERNAL_TARGET_DEPENDENCIES as well.
function(_qt_internal_sbom_handle_target_dependencies target)
set(opt_args "")
set(single_args
SPDX_ID
OUT_CYDX_DEPENDENCIES
OUT_SPDX_RELATIONSHIPS
OUT_SBOM_RELATIONSHIP_ENTRIES
OUT_EXTERNAL_TARGET_DEPENDENCIES
)
set(multi_args
LIBRARIES
PUBLIC_LIBRARIES
SBOM_DEPENDENCIES
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -81,15 +80,16 @@ function(_qt_internal_sbom_handle_target_dependencies target)
set(all_direct_libraries ${libraries} ${public_libraries} ${sbom_dependencies})
list(REMOVE_DUPLICATES all_direct_libraries)
set(regular_cydx_dependencies "")
set(regular_spdx_dependencies "")
set(external_cydx_dependencies "")
set(external_spdx_dependencies "")
set(regular_relationship_entries "")
set(external_relationship_entries "")
set(external_target_dependencies "")
# Go through each direct linked lib.
foreach(direct_lib IN LISTS all_direct_libraries)
set(libs_to_process ${all_direct_libraries})
# Go through all collected direct linked libs. More might be added during the processing
# due to handling of WrapFoo -> Qt::BundledFoo libraries.
while(libs_to_process)
list(POP_FRONT libs_to_process direct_lib)
if(NOT TARGET "${direct_lib}")
continue()
endif()
@@ -114,12 +114,13 @@ function(_qt_internal_sbom_handle_target_dependencies target)
foreach(lib_walked_target IN LISTS lib_walked_targets)
get_target_property(is_3rdparty_bundled_lib
"${lib_walked_target}" _qt_module_is_3rdparty_library)
_qt_internal_sbom_get_spdx_id_for_target("${lib_walked_target}" lib_spdx_id)
# Add a dependency on the vendored lib instead of the Wrap target.
if(is_3rdparty_bundled_lib AND lib_spdx_id)
list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
if(is_3rdparty_bundled_lib)
message(DEBUG
"Replacing SBOM dependency on '${direct_lib}' with dependency on "
"bundled target '${lib_walked_target}' for target '${target}'")
list(APPEND libs_to_process "${lib_walked_target}")
set(bundled_targets_found TRUE)
endif()
endforeach()
@@ -159,37 +160,36 @@ function(_qt_internal_sbom_handle_target_dependencies target)
if(NOT is_dependency_in_external_document)
# If the target is not in the external document, it must be one built as part of the
# current project.
list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
set(relationship_entry
SBOM_RELATIONSHIP_ENTRY
SBOM_RELATIONSHIP_FROM
"${target}"
SBOM_RELATIONSHIP_TYPE
DEPENDS_ON
SBOM_RELATIONSHIP_TO
"${direct_lib}"
)
list(APPEND regular_relationship_entries ${relationship_entry})
else()
# Refer to the package in the external document. This can be the case
# in a top-level build, where a system library is reused across repos, or for any
# regular dependency that was built as part of a different project.
_qt_internal_sbom_add_external_target_dependency("${direct_lib}"
OUT_CYDX_DEPENDENCIES extra_cydx_dependencies
OUT_SPDX_DEPENDENCIES extra_spdx_dependencies
_qt_internal_sbom_add_external_target_dependency(
TARGET "${target}"
DEPENDENCY_TARGET "${direct_lib}"
OUT_SBOM_RELATIONSHIP_ENTRIES extra_relationship_entries
OUT_TARGET_DEPENDENCIES extra_target_dependencies
)
if(extra_spdx_dependencies)
list(APPEND external_cydx_dependencies ${extra_cydx_dependencies})
list(APPEND external_spdx_dependencies ${extra_spdx_dependencies})
if(extra_relationship_entries)
list(APPEND external_relationship_entries ${extra_relationship_entries})
list(APPEND external_target_dependencies ${extra_target_dependencies})
endif()
endif()
endforeach()
endwhile()
set(spdx_relationships "")
set(all_relationship_entries ${external_relationship_entries} ${regular_relationship_entries})
# Keep the external dependencies first, so they are neatly ordered.
foreach(dep_spdx_id IN LISTS external_spdx_dependencies regular_spdx_dependencies)
set(relationship "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}")
list(APPEND spdx_relationships "${relationship}")
endforeach()
set(all_cydx_dependencies ${external_cydx_dependencies} ${regular_cydx_dependencies})
set(${arg_OUT_CYDX_DEPENDENCIES} "${all_cydx_dependencies}" PARENT_SCOPE)
set(${arg_OUT_SPDX_RELATIONSHIPS} "${spdx_relationships}" PARENT_SCOPE)
set(${arg_OUT_SBOM_RELATIONSHIP_ENTRIES} "${all_relationship_entries}" PARENT_SCOPE)
set(${arg_OUT_EXTERNAL_TARGET_DEPENDENCIES} "${external_target_dependencies}" PARENT_SCOPE)
endfunction()
@@ -223,25 +223,39 @@ function(_qt_internal_sbom_is_external_target_dependency target)
set(${arg_OUT_VAR} "${part_of_other_repo}" PARENT_SCOPE)
endfunction()
# Handles generating an external document reference SDPX element for each target package that is
# located in a different spdx document, and collects the reference in OUT_SPDX_DEPENDENCIES.
# In case of CycloneDX, we just collect the dependency spdx id (bom-ref) and the target name,
# which are added to OUT_CYDX_DEPENDENCIES and OUT_TARGET_DEPENDENCIES respectively.
function(_qt_internal_sbom_add_external_target_dependency target)
# Handles generating an external document reference SDPX element for each ${arg_DEPENDENCY_TARGET}
# package that is located in a different spdx document, and collects the relationship in
# OUT_SBOM_RELATIONSHIP_ENTRIES.
# In case of CycloneDX, we also collect the dependency target name in OUT_TARGET_DEPENDENCIES.
function(_qt_internal_sbom_add_external_target_dependency)
set(opt_args "")
set(single_args
OUT_CYDX_DEPENDENCIES
OUT_SPDX_DEPENDENCIES
TARGET
DEPENDENCY_TARGET
OUT_SBOM_RELATIONSHIP_ENTRIES
OUT_TARGET_DEPENDENCIES
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_get_spdx_id_for_target("${target}" dep_spdx_id)
if(NOT arg_TARGET)
message(FATAL_ERROR "TARGET is required.")
endif()
if(NOT arg_DEPENDENCY_TARGET)
message(FATAL_ERROR "DEPENDENCY_TARGET is required.")
endif()
if(NOT arg_OUT_SBOM_RELATIONSHIP_ENTRIES)
message(FATAL_ERROR "OUT_SBOM_RELATIONSHIP_ENTRIES is required.")
endif()
if(NOT arg_OUT_TARGET_DEPENDENCIES)
message(FATAL_ERROR "OUT_TARGET_DEPENDENCIES is required.")
endif()
_qt_internal_sbom_get_spdx_id_for_target("${arg_DEPENDENCY_TARGET}" dep_spdx_id)
if(NOT dep_spdx_id)
message(DEBUG "Could not add external target dependency on ${target} "
message(DEBUG "Could not add external target dependency on ${arg_DEPENDENCY_TARGET} "
"because no spdx id could be found")
set(${arg_OUT_CYDX_DEPENDENCIES} "" PARENT_SCOPE)
set(${arg_OUT_SPDX_DEPENDENCIES} "" PARENT_SCOPE)
@@ -249,29 +263,45 @@ function(_qt_internal_sbom_add_external_target_dependency target)
return()
endif()
set(cydx_dependencies "")
set(spdx_dependencies "")
set(relationship_entries "")
set(target_depdendencies "")
# Get the external document path and the repo it belongs to for the given target.
get_property(relative_installed_repo_document_path TARGET ${target}
# Get the external document path and the repo it belongs to for the given target,
# as well as the spdx document namespace, and cydx serial number.
get_property(relative_installed_repo_document_path TARGET "${arg_DEPENDENCY_TARGET}"
PROPERTY _qt_sbom_spdx_relative_installed_repo_document_path)
get_property(project_name_lowercase TARGET ${target}
get_property(project_name_lowercase TARGET "${arg_DEPENDENCY_TARGET}"
PROPERTY _qt_sbom_spdx_repo_project_name_lowercase)
get_property(spdx_document_namespace TARGET "${arg_DEPENDENCY_TARGET}"
PROPERTY _qt_sbom_spdx_repo_document_namespace)
get_property(bom_serial_number_uuid TARGET "${arg_DEPENDENCY_TARGET}"
PROPERTY _qt_sbom_cydx_bom_serial_number_uuid)
if(relative_installed_repo_document_path AND project_name_lowercase)
_qt_internal_sbom_get_external_document_ref_spdx_id(
"${project_name_lowercase}" external_document_ref)
_qt_internal_sbom_get_external_document_ref_spdx_id_from_sbom_target(
TARGET "${arg_DEPENDENCY_TARGET}"
OUT_VAR external_document_ref
CREATE_TEMPORARY_REF_WHEN_MISSING
)
get_cmake_property(known_external_document
_qt_known_external_documents_${external_document_ref})
set(spdx_dependency "${external_document_ref}:${dep_spdx_id}")
set(relationship_entry
SBOM_RELATIONSHIP_ENTRY
SBOM_RELATIONSHIP_FROM
"${arg_TARGET}"
SBOM_RELATIONSHIP_TYPE
DEPENDS_ON
SBOM_RELATIONSHIP_TO
"${arg_DEPENDENCY_TARGET}"
)
list(APPEND cydx_dependencies "${dep_spdx_id}")
list(APPEND spdx_dependencies "${spdx_dependency}")
list(APPEND target_depdendencies "${target}")
list(APPEND relationship_entries "${relationship_entry}")
list(APPEND target_depdendencies "${arg_DEPENDENCY_TARGET}")
# Only add a reference to the external document package, if we haven't done so already.
if(NOT known_external_document)
@@ -282,23 +312,38 @@ function(_qt_internal_sbom_add_external_target_dependency target)
set(external_document "${relative_installed_repo_document_path}")
_qt_internal_sbom_generate_add_external_reference(
EXTERNAL_DOCUMENT_FILE_PATH "${external_document}"
EXTERNAL_DOCUMENT_INSTALL_PREFIXES ${install_prefixes}
EXTERNAL_DOCUMENT_SPDX_ID "${external_document_ref}"
)
# We need to make the external document target name unique to the currently active
# project, otherwise we risk trying to override the document details of an existing
# external document target when processing multiple SBOM projects within a single
# cmake invocation.
_qt_internal_sbom_get_root_project_name_lower_case(current_project_name)
set(external_document_target
"ExternalDocumentProject-${project_name_lowercase}-for-${current_project_name}")
set_property(GLOBAL PROPERTY
_qt_known_external_documents_${external_document_ref} TRUE)
set_property(GLOBAL APPEND PROPERTY
_qt_known_external_documents "${external_document_ref}")
if(QT_SBOM_GENERATE_SPDX_V2)
_qt_internal_sbom_add_external_reference_document("${external_document_target}"
NO_SCAN_FILE_AT_CONFIGURE_TIME
SBOM_FORMAT SPDX_V2_TAG_VALUE
SPDX_V2_DOCUMENT_REF_ID "${external_document_ref}"
SPDX_V2_DOCUMENT_NAMESPACE "${spdx_document_namespace}"
EXTERNAL_DOCUMENT_FILE_PATH "${external_document}"
EXTERNAL_DOCUMENT_SEARCH_PATHS ${install_prefixes}
)
endif()
if(QT_SBOM_GENERATE_CYDX_V1_6)
_qt_internal_sbom_add_external_reference_document("${external_document_target}"
NO_SCAN_FILE_AT_CONFIGURE_TIME
SBOM_FORMAT CYDX_V1_JSON
CYDX_URN_SERIAL_NUMBER "${bom_serial_number_uuid}"
)
endif()
endif()
else()
message(AUTHOR_WARNING
"Missing spdx document path for external target dependency: ${target}")
"Missing spdx document path for external target dependency: ${arg_DEPENDENCY_TARGET}")
endif()
set(${arg_OUT_CYDX_DEPENDENCIES} "${cydx_dependencies}" PARENT_SCOPE)
set(${arg_OUT_SPDX_DEPENDENCIES} "${spdx_dependencies}" PARENT_SCOPE)
set(${arg_OUT_SBOM_RELATIONSHIP_ENTRIES} "${relationship_entries}" PARENT_SCOPE)
set(${arg_OUT_TARGET_DEPENDENCIES} "${target_depdendencies}" PARENT_SCOPE)
endfunction()
@@ -304,6 +304,74 @@ function(_qt_internal_sbom_compute_qt_uniqueish_document_namespace_infix)
endif()
endfunction()
# Computes a short unique-ish sha suffix to embed into all Qt package SPDX ids, to ensure that each
# package spdx id is distinct enough between Qt builds.
#
# The use case is referencing multiple versions of the the same target, within a project that
# mixes multiple Qt versions, like Qt for MCU.
#
# E.g. record that MyLib depends on Qt Core 6.8.3 from qtbase spdx doc1, and that MyLib2 depends
# on Qt Core 6.9.1 from qtbase spdx doc2, within the generated project doc that contains MyLib and
# MyLib2.
#
# Default hash length chosen to be 12, to have a probability of 0.001 collisions when there are
# 750000 releases of the same Qt repo, according to
# https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
function(_qt_internal_sbom_compute_uniqueish_spdx_id_suffix)
set(opt_args "")
set(single_args
SPDX_NAMESPACE
OUT_VAR_UNIQUE_SUFFIX
HASH_LENGTH
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(QT_SBOM_NO_AUTO_SPDX_SUFFIX)
set(${arg_OUT_VAR_UNIQUE_SUFFIX} "" PARENT_SCOPE)
return()
endif()
if(NOT arg_OUT_VAR_UNIQUE_SUFFIX)
message(FATAL_ERROR "OUT_VAR_UNIQUE_SUFFIX must be set")
endif()
if(NOT arg_SPDX_NAMESPACE)
message(FATAL_ERROR "SPDX_NAMESPACE must be set")
endif()
if(QT_SBOM_FAKE_DETERMINISTIC_BUILD
# This is to allow developers test a fake build, without a fake uuid
AND NOT QT_SBOM_NO_FAKE_DETERMINISTIC_BUILD_SPDX_IDX_SUFFIX
)
set(unique_suffix "fake-id-suffix")
else()
string(SHA1 namespace_sha "${arg_SPDX_NAMESPACE}")
if(arg_HASH_LENGTH)
set(sha_suffix_length "${arg_HASH_LENGTH}")
else()
set(sha_suffix_length "12")
endif()
string(SUBSTRING "${namespace_sha}" 0 "${sha_suffix_length}" unique_suffix)
endif()
set(${arg_OUT_VAR_UNIQUE_SUFFIX} "${unique_suffix}" PARENT_SCOPE)
endfunction()
# Retrieves the current project's spdx id suffix.
function(_qt_internal_sbom_get_spdx_id_unique_suffix out_var)
get_cmake_property(unique_suffix _qt_internal_sbom_repo_spdx_id_unique_suffix)
if(NOT unique_suffix)
set(unique_suffix "")
endif()
set(${out_var} "${unique_suffix}" PARENT_SCOPE)
endfunction()
# Returns a lower case host platform name for sbom document namespace purposes.
function(_qt_internal_sbom_get_host_platform_name out_var)
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" main_value)
@@ -52,10 +52,12 @@ function(_qt_internal_sbom_handle_target_binary_files target)
THIRD_PARTY_LIBRARY
THIRD_PARTY_LIBRARY_WITH_FILES
THIRD_PARTY_SOURCES
SBOM_PROJECT
EXECUTABLE
LIBRARY
TRANSLATIONS
RESOURCES
BUILD_TOOL
CUSTOM
CUSTOM_NO_INFIX
)
@@ -75,8 +77,10 @@ function(_qt_internal_sbom_handle_target_binary_files target)
SYSTEM_LIBRARY
THIRD_PARTY_LIBRARY
THIRD_PARTY_SOURCES
SBOM_PROJECT
TRANSLATIONS
RESOURCES
BUILD_TOOL
CUSTOM
CUSTOM_NO_INFIX
)
@@ -797,6 +801,7 @@ function(_qt_internal_sbom_map_path_to_reproducible_relative_path out_var)
set(is_in_source_dir FALSE)
set(is_in_build_dir FALSE)
set(is_in_sysroot_dir FALSE)
if(NOT arg_REPO_PROJECT_NAME_LOWERCASE)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name)
@@ -827,6 +832,12 @@ function(_qt_internal_sbom_map_path_to_reproducible_relative_path out_var)
if(NOT is_in_source_dir AND NOT is_in_build_dir)
_qt_internal_path_is_prefix(CMAKE_INSTALL_PREFIX "${path}" is_in_prefix_dir)
endif()
if(CMAKE_SYSROOT
AND NOT is_in_source_dir
AND NOT is_in_build_dir
AND NOT is_in_prefix_dir)
_qt_internal_path_is_prefix(CMAKE_SYSROOT "${path}" is_in_sysroot_dir)
endif()
else()
# We consider relative paths to be relative to the current source dir.
set(is_in_source_dir TRUE)
@@ -854,6 +865,10 @@ function(_qt_internal_sbom_map_path_to_reproducible_relative_path out_var)
set(handled TRUE)
set(marker "/install_dir")
string(REPLACE "${CMAKE_INSTALL_PREFIX}/" "${marker}/" path_out "${path_in_real}")
elseif(is_in_sysroot_dir)
set(handled TRUE)
set(marker "/sysroot_dir")
string(REPLACE "${CMAKE_SYSROOT}/" "${marker}/" path_out "${path_in_real}")
else()
# If it's not a source dir, a build dir, or install dir, it might be some kind of
# weird genex or marker that we don't handle yet.
@@ -1159,6 +1174,8 @@ endfunction()
# Get a sanitized spdx id for a file.
# For consistency, we prefix the id with SPDXRef-PackagedFile-. This is not a requirement.
function(_qt_internal_sbom_get_file_spdx_id target out_var)
_qt_internal_sbom_get_sanitized_spdx_id(spdx_id "SPDXRef-PackagedFile-${target}")
_qt_internal_sbom_get_spdx_id_unique_suffix(spdx_id_unique_suffix)
_qt_internal_sbom_get_sanitized_spdx_id(spdx_id
"SPDXRef-PackagedFile-${target}${spdx_id_unique_suffix}")
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()
@@ -85,6 +85,8 @@ serial_number_uuid = \"${arg_BOM_SERIAL_NUMBER_UUID}\" # Required
description = '''${project_comment}
'''
<PROJECT_RELATIONSHIP_PLACEHOLDER>
[[project_build_tools]]
name = \"cmake\"
version = \"${CMAKE_VERSION}\"
@@ -92,14 +94,12 @@ component_type = \"application\"
description = \"Build system tool used to build the project.\"
")
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_create_sbom_staging_file(
CONTENT "${content}"
SBOM_FORMAT "CYDX_V1_6"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
OUT_VAR_CREATE_STAGING_FILE create_staging_file
OUT_VAR_SBOM_DIR sbom_dir
)
set(sbom_format "CYDX_V1_6")
_qt_internal_sbom_save_intro_content(
SBOM_FORMAT "${sbom_format}"
CONTENT "${content}")
_qt_internal_sbom_create_sbom_staging_dir(OUT_VAR_SBOM_DIR sbom_dir)
_qt_internal_sbom_save_project_info_in_global_properties(
SUPPLIER "${arg_SUPPLIER}"
@@ -113,16 +113,16 @@ description = \"Build system tool used to build the project.\"
OUTPUT "${arg_OUTPUT}"
OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
SBOM_DIR "${sbom_dir}"
PROPERTY_SUFFIX _cydx
SBOM_FORMAT "${sbom_format}"
)
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files_cydx "${create_staging_file}")
endfunction()
# Finalizes the CycloneDX sbom generation for a project.
function(_qt_internal_sbom_end_project_generate_cyclone)
set(sbom_format "CYDX_V1_6")
_qt_internal_sbom_get_common_path_variables_from_global_properties(
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
@@ -137,18 +137,34 @@ function(_qt_internal_sbom_end_project_generate_cyclone)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
_qt_internal_sbom_handle_project_relationships(
OUTPUT_SBOM_FORMAT "${sbom_format}"
OUT_VAR_RELATIONSHIP_STRINGS relationship_strings
)
set(staging_file_args "")
if(relationship_strings)
list(APPEND staging_file_args RELATIONSHIP_STRINGS "${relationship_strings}")
endif()
_qt_internal_sbom_create_sbom_staging_file(
SBOM_FORMAT "${sbom_format}"
SBOM_DIR "${sbom_dir}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
${staging_file_args}
)
# Process licenses before getting the includes.
_qt_internal_sbom_add_recorded_licenses_cydx()
_qt_internal_sbom_get_cmake_include_files(
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
OUT_VAR_INCLUDES includes
OUT_VAR_POST_GENERATION_INCLUDES post_generation_includes
)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(build_time_args "")
if(includes)
list(APPEND build_time_args INCLUDES "${includes}")
@@ -157,7 +173,7 @@ function(_qt_internal_sbom_end_project_generate_cyclone)
list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
endif()
_qt_internal_sbom_create_build_time_sbom_targets(
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
@@ -169,7 +185,7 @@ function(_qt_internal_sbom_end_project_generate_cyclone)
_qt_internal_sbom_setup_multi_config_install_markers(
SBOM_DIR "${sbom_dir}"
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
@@ -203,7 +219,7 @@ function(_qt_internal_sbom_end_project_generate_cyclone)
endif()
_qt_internal_sbom_setup_sbom_install_code(
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
@@ -213,7 +229,7 @@ function(_qt_internal_sbom_end_project_generate_cyclone)
)
_qt_internal_sbom_clear_cmake_include_files(
SBOM_FORMAT "CYDX_V1_6"
SBOM_FORMAT "${sbom_format}"
)
endfunction()
@@ -241,8 +257,8 @@ function(_qt_internal_sbom_generate_cyclone_add_package)
# Additions compared to spdx function signature.
PURL_VALUES
DEPENDENCIES
CYDX_PROPERTIES
SBOM_RELATIONSHIP_ENTRIES
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -298,14 +314,21 @@ function(_qt_internal_sbom_generate_cyclone_add_package)
set(version_field "version = \\\"${arg_VERSION}\\\"")
endif()
set(dependency_field "")
set(dependency_list ${arg_DEPENDENCIES})
if(dependency_list)
# Wrap values in double quotes.
list(TRANSFORM dependency_list PREPEND "\\\"")
list(TRANSFORM dependency_list APPEND "\\\"")
list(JOIN dependency_list ", " dependency_string)
set(dependency_field "dependencies = [${dependency_string}]")
set(relationships_field "")
if(arg_SBOM_RELATIONSHIP_ENTRIES)
_qt_internal_sbom_serialize_relationship_entries(
OUTPUT_SBOM_FORMAT "CYDX_V1_6"
CYDX_TOML_KEY "components."
ESCAPE_CYDX_QUOTES
OUT_VAR_RELATIONSHIPS_STRINGS entries_relationships_strings
SBOM_RELATIONSHIP_ENTRIES ${arg_SBOM_RELATIONSHIP_ENTRIES}
)
if(entries_relationships_strings)
# Remove duplicates, because apparently we sometimes get them for some system libraries.
list(REMOVE_DUPLICATES entries_relationships_strings)
list(JOIN entries_relationships_strings "\n" relationships_field)
endif()
endif()
set(copyright_field "")
@@ -367,8 +390,8 @@ ${external_bom_link_field}
${cpe_field}
${purl_field}
${license_concluded_field}
${dependency_field}
${properties_field}
${relationships_field}
\"
)
")
@@ -141,6 +141,8 @@ PackageVersion: ${QT_SBOM_GIT_VERSION}")
set(project_comment "<text>${project_comment}</text>")
_qt_internal_sbom_get_spdx_id_unique_suffix(spdx_id_unique_suffix)
set(content
"SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
@@ -152,20 +154,6 @@ CreatorComment: <text>This SPDX document was created from CMake ${CMAKE_VERSION}
build system from https://code.qt.io/cgit/qt/qtbase.git/tree/cmake/QtPublicSbomHelpers.cmake</text>
Created: ${current_utc}\${QT_SBOM_EXTERNAL_DOC_REFS}
PackageName: ${CMAKE_CXX_COMPILER_ID}
SPDXID: SPDXRef-compiler
PackageVersion: ${CMAKE_CXX_COMPILER_VERSION}
PackageDownloadLocation: NOASSERTION
PackageLicenseConcluded: NOASSERTION
PackageLicenseDeclared: NOASSERTION
PackageCopyrightText: NOASSERTION
PackageSupplier: Organization: Anonymous
FilesAnalyzed: false
PackageSummary: <text>The compiler as identified by CMake, running on ${CMAKE_HOST_SYSTEM_NAME} (${CMAKE_HOST_SYSTEM_PROCESSOR})</text>
PrimaryPackagePurpose: APPLICATION
Relationship: SPDXRef-compiler BUILD_DEPENDENCY_OF ${project_spdx_id}
RelationshipComment: <text>${project_spdx_id} is built by compiler ${CMAKE_CXX_COMPILER_ID} version ${CMAKE_CXX_COMPILER_VERSION}</text>
PackageName: ${arg_PROJECT}
SPDXID: ${project_spdx_id}${fields}
ExternalRef: PACKAGE-MANAGER purl pkg:generic/${arg_SUPPLIER}/${arg_PROJECT}@${QT_SBOM_GIT_VERSION}
@@ -178,19 +166,14 @@ PackageHomePage: ${arg_SUPPLIER_URL}
PackageComment: ${project_comment}
FilesAnalyzed: false
BuiltDate: ${current_utc}
Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
")
Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}")
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_create_sbom_staging_file(
CONTENT "${content}"
SBOM_FORMAT "SPDX_V2"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
OUT_VAR_CREATE_STAGING_FILE create_staging_file
OUT_VAR_SBOM_DIR sbom_dir
)
set(sbom_format "SPDX_V2")
_qt_internal_sbom_save_intro_content(
SBOM_FORMAT "${sbom_format}"
CONTENT "${content}")
set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
_qt_internal_sbom_create_sbom_staging_dir(OUT_VAR_SBOM_DIR sbom_dir)
_qt_internal_sbom_save_project_info_in_global_properties(
SUPPLIER "${arg_SUPPLIER}"
@@ -198,16 +181,16 @@ Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
NAMESPACE "${arg_NAMESPACE}"
PROJECT "${arg_PROJECT}"
PROJECT_SPDX_ID "${project_spdx_id}"
EXTERNAL_REFERENCE_SBOM_DIRS "${sbom_dir}"
)
_qt_internal_sbom_save_common_path_variables_in_global_properties(
OUTPUT "${arg_OUTPUT}"
OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
SBOM_DIR "${sbom_dir}"
SBOM_FORMAT "${sbom_format}"
)
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${create_staging_file}")
set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count 0)
set_property(GLOBAL PROPERTY _qt_sbom_relationship_counter 0)
endfunction()
@@ -216,8 +199,9 @@ endfunction()
# Creates an 'sbom' custom target to generate an incomplete sbom at build time (no checksums).
# Creates install rules to install a complete (with checksums) sbom.
function(_qt_internal_sbom_end_project_generate)
set(sbom_format "SPDX_V2")
_qt_internal_sbom_get_common_path_variables_from_global_properties(
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
@@ -232,9 +216,27 @@ function(_qt_internal_sbom_end_project_generate)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
_qt_internal_sbom_handle_project_relationships(
OUTPUT_SBOM_FORMAT "${sbom_format}"
OUT_VAR_RELATIONSHIP_STRINGS relationship_strings
)
set(staging_file_args "")
if(relationship_strings)
list(APPEND staging_file_args RELATIONSHIP_STRINGS "${relationship_strings}")
endif()
_qt_internal_sbom_create_sbom_staging_file(
SBOM_FORMAT "${sbom_format}"
SBOM_DIR "${sbom_dir}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
${staging_file_args}
)
_qt_internal_sbom_get_cmake_include_files(
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
OUT_VAR_INCLUDES includes
OUT_VAR_BEFORE_CHECKSUM_INCLUDES before_checksum_includes
OUT_VAR_AFTER_CHECKSUM_INCLUDES after_checksum_includes
@@ -242,8 +244,6 @@ function(_qt_internal_sbom_end_project_generate)
OUT_VAR_VERIFY_INCLUDES verify_includes
)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(build_time_args "")
if(includes)
list(APPEND build_time_args INCLUDES "${includes}")
@@ -252,7 +252,7 @@ function(_qt_internal_sbom_end_project_generate)
list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
endif()
_qt_internal_sbom_create_build_time_sbom_targets(
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
@@ -280,7 +280,7 @@ function(_qt_internal_sbom_end_project_generate)
_qt_internal_sbom_setup_multi_config_install_markers(
SBOM_DIR "${sbom_dir}"
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
@@ -335,7 +335,7 @@ unset(_verification_code)
endif()
_qt_internal_sbom_setup_sbom_install_code(
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
@@ -346,7 +346,7 @@ unset(_verification_code)
)
_qt_internal_sbom_clear_cmake_include_files(
SBOM_FORMAT "SPDX_V2"
SBOM_FORMAT "${sbom_format}"
)
endfunction()
@@ -529,22 +529,29 @@ Relationship: ${arg_RELATIONSHIP}
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${file_sbom_to_install}")
endfunction()
# Helper to add a reference to an external SPDX document.
# Helper to add a reference to an external SPDX v2 document.
#
# EXTERNAL_DOCUMENT_SPDX_ID: The spdx id by which the external document should be referenced in
# the current project SPDX document. This semantically serves as a pointer to the external document
# URI.
# e.g. DocumentRef-qtbase.
#
# EXTERNAL_DOCUMENT_FILE_PATH: The relative file path of the external sbom document.
# e.g. "sbom/qtbase-6.9.0.spdx"
# SBOM_FORMAT: Type of SPDX file, SPDX_V2_TAG_VALUE or SPDX_V2_JSON.
#
# EXTERNAL_DOCUMENT_NAMESPACE: Explicit SPDX namespace to embed, instead of parsing the file passed
# in EXTERNAL_DOCUMENT_FILE_PATH.
#
# EXTERNAL_DOCUMENT_SHA1: Explicit sha1 to embed, instead of calculating it from the file passed
# in EXTERNAL_DOCUMENT_FILE_PATH.
#
# EXTERNAL_DOCUMENT_FILE_PATH: An absolute or relative file path of the external sbom document.
# In case of a relative file path, it will be searched for in the directories specified by the
# EXTERNAL_DOCUMENT_INSTALL_PREFIXES option.
# e.g. "sbom/qtbase-6.9.0.spdx"
# Can contain generator expressions.
# The file path is searched for in the directories specified by the
# EXTERNAL_DOCUMENT_INSTALL_PREFIXES option during sbom generation, to compute the file checksum.
# The file path is NOT embedded into the current project spdx document.
# Only its spdx id and namespace is embedded. The namespace is extracted from the contents of the
# referenced file.
# The file path is NOT embedded into the current project spdx document. Only the document ref id,
# spdx namespace and sha1 is embedded.
# The namespace is extracted from the contents of the referenced file.
#
# EXTERNAL_DOCUMENT_INSTALL_PREFIXES: A list of directories where the external document file path
# is searched for. The first existing file is used. Additionally the following locations are
@@ -571,6 +578,10 @@ function(_qt_internal_sbom_generate_add_external_reference)
EXTERNAL_DOCUMENT_SPDX_ID
EXTERNAL_PACKAGE_SPDX_ID
RELATIONSHIP_STRING
EXTERNAL_DOCUMENT_NAMESPACE
EXTERNAL_DOCUMENT_SHA1
SBOM_FORMAT
)
set(multi_args
EXTERNAL_DOCUMENT_INSTALL_PREFIXES
@@ -578,7 +589,46 @@ function(_qt_internal_sbom_generate_add_external_reference)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(EXTERNAL_DOCUMENT_FILE_PATH "")
# Default to tag:value if nothing is passed, because that was the previous behavior before this
# option got introduced.
if(NOT arg_SBOM_FORMAT)
set(arg_SBOM_FORMAT "SPDX_V2_TAG_VALUE")
endif()
if(NOT arg_EXTERNAL_DOCUMENT_FILE_PATH AND NOT arg_EXTERNAL_DOCUMENT_NAMESPACE)
message(FATAL_ERROR "Either EXTERNAL_DOCUMENT_FILE_PATH or "
"EXTERNAL_DOCUMENT_NAMESPACE together with EXTERNAL_DOCUMENT_SHA1 must be specified "
"to be able to add an external reference for an external SPDX v2 tag:value document.")
endif()
set(debug_var_assignments "
EXTERNAL_DOCUMENT_FILE_PATH: '${arg_EXTERNAL_DOCUMENT_FILE_PATH}'
EXTERNAL_DOCUMENT_NAMESPACE: '${arg_EXTERNAL_DOCUMENT_NAMESPACE}'
EXTERNAL_DOCUMENT_SHA1: '${arg_EXTERNAL_DOCUMENT_SHA1}'
EXTERNAL_DOCUMENT_SPDX_ID: '${arg_EXTERNAL_DOCUMENT_SPDX_ID}'
")
if(NOT arg_EXTERNAL_DOCUMENT_FILE_PATH
AND arg_EXTERNAL_DOCUMENT_NAMESPACE
AND NOT arg_EXTERNAL_DOCUMENT_SHA1)
message(FATAL_ERROR
"EXTERNAL_DOCUMENT_SHA1 must be specified if EXTERNAL_DOCUMENT_NAMESPACE is specified "
"and EXTERNAL_DOCUMENT_FILE_PATH is not specified.\n${debug_var_assignments}")
endif()
if(NOT arg_EXTERNAL_DOCUMENT_FILE_PATH
AND NOT arg_EXTERNAL_DOCUMENT_NAMESPACE
AND arg_EXTERNAL_DOCUMENT_SHA1)
message(FATAL_ERROR
"EXTERNAL_DOCUMENT_NAMESPACE must be specified if EXTERNAL_DOCUMENT_SHA1 is specified "
"and EXTERNAL_DOCUMENT_FILE_PATH is not specified.\n${debug_var_assignments}")
endif()
if(arg_EXTERNAL_DOCUMENT_FILE_PATH)
set(find_external_document TRUE)
else()
set(find_external_document FALSE)
endif()
if(NOT arg_EXTERNAL_DOCUMENT_SPDX_ID)
get_property(spdx_id_count GLOBAL PROPERTY _qt_sbom_spdx_id_count)
@@ -594,7 +644,8 @@ function(_qt_internal_sbom_generate_add_external_reference)
if(NOT sbom_project_spdx_id)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
if(arg_RELATIONSHIP_STRING STREQUAL "")
if(NOT arg_RELATIONSHIP_STRING)
if(arg_EXTERNAL_PACKAGE_SPDX_ID)
set(external_package "${arg_EXTERNAL_DOCUMENT_SPDX_ID}:${arg_EXTERNAL_PACKAGE_SPDX_ID}")
set(arg_RELATIONSHIP_STRING
@@ -608,43 +659,23 @@ function(_qt_internal_sbom_generate_add_external_reference)
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
set(install_prefixes "")
set(document_search_paths "")
if(find_external_document)
set(search_path_args "")
if(arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES)
list(APPEND search_path_args
EXTERNAL_DOCUMENT_SEARCH_PATHS ${arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES}
)
endif()
# Add the current sbom build dirs as install prefixes, so that we can use ninja 'sbom'
# in top-level builds. This is needed because the external references will point
# to sbom docs in different build dirs, not just one.
# We also need it in case we are converting a json document to a tag/value format in the
# current build dir of the project, and want it to be found.
get_cmake_property(build_sbom_dirs _qt_internal_sbom_dirs)
if(build_sbom_dirs)
foreach(build_sbom_dir IN LISTS build_sbom_dirs)
list(APPEND install_prefixes "${build_sbom_dir}")
endforeach()
_qt_internal_sbom_get_external_reference_search_paths(document_search_paths
SEARCH_IN_BUILD_SBOM_DIRS
SEARCH_IN_QT_PREFIXES
SEARCH_IN_DESTDIR_INSTALL_PREFIX_AT_INSTALL_TIME
${search_path_args}
)
endif()
# Always append the install time install prefix.
# The variable is escaped, so it is evaluated during cmake install time, so that the value
# can be overridden with cmake --install . --prefix <path>.
list(APPEND install_prefixes "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}")
if(arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES)
list(APPEND install_prefixes ${arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES})
endif()
if(QT6_INSTALL_PREFIX)
list(APPEND install_prefixes ${QT6_INSTALL_PREFIX})
endif()
if(QT_ADDITIONAL_PACKAGES_PREFIX_PATH)
list(APPEND install_prefixes ${QT_ADDITIONAL_PACKAGES_PREFIX_PATH})
endif()
if(QT_ADDITIONAL_SBOM_DOCUMENT_PATHS)
list(APPEND install_prefixes ${QT_ADDITIONAL_SBOM_DOCUMENT_PATHS})
endif()
list(REMOVE_DUPLICATES install_prefixes)
set(relationship_content "")
if(arg_RELATIONSHIP_STRING)
set(relationship_content "
@@ -655,29 +686,40 @@ function(_qt_internal_sbom_generate_add_external_reference)
# File path may not exist yet, and it could be a generator expression.
set(content "
set(relative_file_name \"${arg_EXTERNAL_DOCUMENT_FILE_PATH}\")
set(document_dir_paths ${install_prefixes})
list(JOIN document_dir_paths \"\\n\" document_dir_paths_per_line)
foreach(document_dir_path IN LISTS document_dir_paths)
set(document_file_path \"\${document_dir_path}/\${relative_file_name}\")
if(EXISTS \"\${document_file_path}\")
break()
endif()
endforeach()
if(NOT EXISTS \"\${document_file_path}\")
message(FATAL_ERROR \"Could not find external SBOM document \${relative_file_name}\"
\" in any of the document dir paths: \${document_dir_paths_per_line} \"
set(find_external_document \"${find_external_document}\")
set(document_search_paths \"${document_search_paths}\")
set(maybe_external_document_file_path \"${arg_EXTERNAL_DOCUMENT_FILE_PATH}\")
set(sbom_format \"${arg_SBOM_FORMAT}\")
set(explicit_external_document_namespace \"${arg_EXTERNAL_DOCUMENT_NAMESPACE}\")
set(explicit_external_document_sha1 \"${arg_EXTERNAL_DOCUMENT_SHA1}\")
# The helper functions below are used both during configure time and SBOM generation
# time. During configure time, QT_GENERATE_SBOM is checked to return early gracefully,
# to allow projects to skip SBOM generation.
# During SBOM generation time, we need to ensure these functions run properly.
# Backup the var instead of setting it directly, because some of the testing infrastructure
# does checks of the variable at the end of sbom generation.
set(backup_qt_generate_sbom \"${QT_GENERATE_SBOM}\")
set(QT_GENERATE_SBOM ON)
if(find_external_document)
_qt_internal_sbom_find_external_reference_document(document_file_path
EXTERNAL_DOCUMENT_FILE_PATH \"\${maybe_external_document_file_path}\"
EXTERNAL_DOCUMENT_SEARCH_PATHS \${document_search_paths}
)
endif()
file(SHA1 \"\${document_file_path}\" ext_sha1)
file(READ \"\${document_file_path}\" ext_content)
if(NOT \"\${ext_content}\" MATCHES \"[\\r\\n]DocumentNamespace:\")
message(FATAL_ERROR \"Missing DocumentNamespace in \${document_file_path}\")
endif()
_qt_internal_sbom_parse_spdx_v2_document_namespace(
EXTERNAL_DOCUMENT_FILE_PATH \"\${document_file_path}\"
SBOM_FORMAT \"\${sbom_format}\"
OUT_VAR_DOCUMENT_NAMESPACE ext_ns
)
string(REGEX REPLACE \"^.*[\\r\\n]DocumentNamespace:[ \\t]*([^#\\r\\n]*).*$\"
\"\\\\1\" ext_ns \"\${ext_content}\")
file(SHA1 \"\${document_file_path}\" ext_sha1)
else()
set(ext_ns \"\${explicit_external_document_namespace}\")
set(ext_sha1 \"\${explicit_external_document_sha1}\")
endif()
set(QT_GENERATE_SBOM \${backup_qt_generate_sbom})
string(APPEND QT_SBOM_EXTERNAL_DOC_REFS \"
ExternalDocumentRef: ${arg_EXTERNAL_DOCUMENT_SPDX_ID} \${ext_ns} SHA1: \${ext_sha1}\")
@@ -699,12 +741,12 @@ function(_qt_internal_sbom_generate_add_package)
)
set(single_args
PACKAGE
PACKAGE_SUMMARY
VERSION
LICENSE_DECLARED
LICENSE_CONCLUDED
COPYRIGHT
DOWNLOAD_LOCATION
RELATIONSHIP
SPDXID
SUPPLIER
PURPOSE
@@ -713,6 +755,8 @@ function(_qt_internal_sbom_generate_add_package)
set(multi_args
EXTREF
CPE
RELATIONSHIPS # Deprecated, SBOM_RELATIONSHIP_ENTRIES is preferred
SBOM_RELATIONSHIP_ENTRIES
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -776,6 +820,12 @@ PackageCopyrightText: NOASSERTION"
)
endif()
if(arg_PACKAGE_SUMMARY)
set(fields "${fields}
PackageSummary: <text>${arg_PACKAGE_SUMMARY}</text>"
)
endif()
if(arg_PURPOSE)
set(fields "${fields}
PrimaryPackagePurpose: ${arg_PURPOSE}"
@@ -802,12 +852,33 @@ ExternalRef: SECURITY cpe23Type ${cpe}"
if(NOT sbom_project_spdx_id)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
if(NOT arg_RELATIONSHIP)
set(arg_RELATIONSHIP "${sbom_project_spdx_id} CONTAINS ${arg_SPDXID}")
else()
string(REPLACE "@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
set(relationship_strings "")
if(arg_SBOM_RELATIONSHIP_ENTRIES)
_qt_internal_sbom_serialize_relationship_entries(
OUTPUT_SBOM_FORMAT "SPDX_V2"
OUT_VAR_RELATIONSHIPS_STRINGS entries_relationships_strings
SBOM_RELATIONSHIP_ENTRIES ${arg_SBOM_RELATIONSHIP_ENTRIES}
)
if(entries_relationships_strings)
list(APPEND relationship_strings ${entries_relationships_strings})
endif()
endif()
# TODO: This is deprecated, SBOM_RELATIONSHIP_ENTRIES is preferred, remove it once qtwebengine
# is ported over.
if(arg_RELATIONSHIPS)
string(REPLACE
"@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIPS "${arg_RELATIONSHIPS}")
list(APPEND relationship_strings ${arg_RELATIONSHIPS})
endif()
# Remove duplicates, because apparently we sometimes get them for some system libraries.
list(REMOVE_DUPLICATES relationship_strings)
list(JOIN relationship_strings "\n" relationships_str)
string(PREPEND relationships_str "\n")
set(fields "${fields}\\\${QT_SBOM_VERIFICATION_CODE_${arg_SPDXID}}")
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
@@ -824,8 +895,7 @@ SPDXID: ${arg_SPDXID}
PackageDownloadLocation: ${arg_DOWNLOAD_LOCATION}
PackageVersion: ${arg_VERSION}
PackageSupplier: ${arg_SUPPLIER}${fields}
FilesAnalyzed: \\\${QT_SBOM_PACKAGE_HAS_FILES_${arg_SPDXID}}
Relationship: ${arg_RELATIONSHIP}
FilesAnalyzed: \\\${QT_SBOM_PACKAGE_HAS_FILES_${arg_SPDXID}}${relationships_str}
\"
)
")
@@ -31,6 +31,9 @@ function(_qt_internal_sbom_begin_project)
USE_GIT_VERSION
__QT_INTERNAL_HANDLE_QT_REPO
NO_AUTO_DOCUMENT_NAMESPACE_INFIX
NO_AUTO_SEARCH_EXTERNAL_DOCUMENTS_IN_CMAKE_PATHS
NO_AUTO_SPDX_ID_SUFFIX
NO_AUTO_ADD_BUILD_TOOLS
)
set(single_args
INSTALL_PREFIX
@@ -43,6 +46,8 @@ function(_qt_internal_sbom_begin_project)
DOCUMENT_NAMESPACE_INFIX
DOCUMENT_NAMESPACE_SUFFIX
DOCUMENT_NAMESPACE_URL_PREFIX
SPDX_ID_SUFFIX
SPDX_ID_SUFFIX_HASH_LENGTH
VERSION
SBOM_PROJECT_NAME
QT_REPO_PROJECT_NAME
@@ -52,6 +57,7 @@ function(_qt_internal_sbom_begin_project)
set(multi_args
COPYRIGHTS
LICENSE_DIR_PATHS
EXTERNAL_DOCUMENT_SEARCH_PATHS
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -123,6 +129,20 @@ function(_qt_internal_sbom_begin_project)
endif()
endif()
if(arg_EXTERNAL_DOCUMENT_SEARCH_PATHS)
set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_external_document_search_paths
${arg_EXTERNAL_DOCUMENT_SEARCH_PATHS})
endif()
if(arg_NO_AUTO_SEARCH_EXTERNAL_DOCUMENTS_IN_CMAKE_PATHS)
set(auto_search_external_documents_in_cmake_paths FALSE)
else()
set(auto_search_external_documents_in_cmake_paths TRUE)
endif()
set_property(GLOBAL PROPERTY
_qt_internal_sbom_auto_search_external_documents_in_paths
"${auto_search_external_documents_in_cmake_paths}")
if(arg_DOCUMENT_NAMESPACE)
set(repo_spdx_namespace "${arg_DOCUMENT_NAMESPACE}")
@@ -186,15 +206,41 @@ function(_qt_internal_sbom_begin_project)
)
endif()
if(QT_SBOM_GENERATE_CYDX_V1_6)
_qt_internal_sbom_get_cyclone_bom_serial_number(
_qt_internal_sbom_get_cyclone_bom_serial_number(
SPDX_NAMESPACE "${repo_spdx_namespace}"
OUT_VAR_UUID cyclone_dx_bom_serial_number_uuid
)
list(APPEND begin_project_generate_args_cydx
BOM_SERIAL_NUMBER_UUID "${cyclone_dx_bom_serial_number_uuid}")
if(QT_SBOM_SPDX_ID_SUFFIX)
set(spdx_id_unique_suffix "-${QT_SBOM_SPDX_ID_SUFFIX}")
elseif(arg_SPDX_ID_SUFFIX)
set(spdx_id_unique_suffix "-${arg_SPDX_ID_SUFFIX}")
elseif(NOT arg_NO_AUTO_SPDX_ID_SUFFIX AND NOT QT_SBOM_NO_AUTO_SPDX_SUFFIX)
set(compute_unique_spdx_id_suffix_args "")
if(QT_SBOM_SPDX_ID_SUFFIX_HASH_LENGTH)
list(APPEND compute_unique_spdx_id_suffix_args
HASH_LENGTH "${QT_SBOM_SPDX_ID_SUFFIX_HASH_LENGTH}")
elseif(arg_SPDX_ID_SUFFIX_HASH_LENGTH)
list(APPEND compute_unique_spdx_id_suffix_args
HASH_LENGTH "${arg_SPDX_ID_SUFFIX_HASH_LENGTH}")
endif()
_qt_internal_sbom_compute_uniqueish_spdx_id_suffix(
SPDX_NAMESPACE "${repo_spdx_namespace}"
OUT_VAR_UUID cyclone_dx_bom_serial_number_uuid
OUT_VAR_UNIQUE_SUFFIX spdx_id_unique_suffix
${compute_unique_spdx_id_suffix_args}
)
list(APPEND begin_project_generate_args_cydx
BOM_SERIAL_NUMBER_UUID "${cyclone_dx_bom_serial_number_uuid}")
string(PREPEND spdx_id_unique_suffix "-")
else()
set(spdx_id_unique_suffix "")
endif()
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_spdx_id_unique_suffix
"${spdx_id_unique_suffix}")
if(arg_INSTALL_SBOM_DIR)
set(install_sbom_dir "${arg_INSTALL_SBOM_DIR}")
elseif(INSTALL_SBOMDIR)
@@ -231,9 +277,22 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_path_join(repo_spdx_relative_install_path_spdx
"${install_sbom_dir}" "${repo_project_file_name_spdx}")
# Currently only used for exporting as a target's property.
_qt_internal_path_join(repo_spdx_relative_install_path_spdx_json
"${install_sbom_dir}" "${repo_project_file_name_spdx}.json")
# This is actually the path to the intermediate CycloneDX toml file.
_qt_internal_path_join(repo_spdx_relative_install_path_cydx
"${install_sbom_dir}" "${repo_project_file_name_cydx}")
# Compute the relative path to the final cydx json file, to be exported in a target's property.
get_filename_component(repo_project_file_name_cydx_without_ext
"${repo_project_file_name_cydx}" NAME_WLE)
_qt_internal_path_join(repo_spdx_relative_install_path_cydx_json
"${install_sbom_dir}" "${repo_project_file_name_cydx_without_ext}.json")
# Prepend DESTDIR, to allow relocating installed sbom. Needed for CI.
_qt_internal_path_join(repo_spdx_install_path_spdx
"\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path_spdx}")
@@ -353,9 +412,20 @@ function(_qt_internal_sbom_begin_project)
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid
"${cyclone_dx_bom_serial_number_uuid}")
# TODO: Figure out what needs to be done to fully port usage to the tag value var below,
# taking into account compatibility for Qt Creator, etc.
set_property(GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path
"${repo_spdx_relative_install_path_spdx}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_document_spdx_v2_tag_value_relative_path
"${repo_spdx_relative_install_path_spdx}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_document_spdx_v2_json_relative_path
"${repo_spdx_relative_install_path_spdx_json}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_document_cydx_v1_6_json_relative_path
"${repo_spdx_relative_install_path_cydx_json}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase
"${repo_project_name_lowercase}")
@@ -365,6 +435,8 @@ function(_qt_internal_sbom_begin_project)
set_property(GLOBAL PROPERTY _qt_internal_sbom_project_spdx_id
"${repo_project_spdx_id}")
_qt_internal_create_project_sbom_target()
# Collect project licenses.
set(license_dirs "")
@@ -413,6 +485,10 @@ function(_qt_internal_sbom_begin_project)
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_begin_called TRUE)
_qt_internal_sbom_setup_project_ops()
if(NOT arg_NO_AUTO_ADD_BUILD_TOOLS)
_qt_internal_sbom_add_project_default_build_tools()
endif()
endfunction()
# Check various internal options to decide which sbom generation operations should be setup.
@@ -774,9 +850,13 @@ function(_qt_internal_sbom_end_project)
set_property(GLOBAL PROPERTY _qt_known_external_documents "")
foreach(external_document IN LISTS known_external_documents)
set_property(GLOBAL PROPERTY _qt_known_external_documents_${external_document} "")
set_property(GLOBAL PROPERTY _qt_known_external_documents_${external_document}_target "")
endforeach()
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_begin_called FALSE)
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_spdx_id_unique_suffix "")
set_property(GLOBAL PROPERTY _qt_internal_sbom_external_document_search_paths "")
set_property(GLOBAL PROPERTY _qt_internal_sbom_auto_search_external_documents_in_paths "")
# Add configure-time dependency on project attribution files.
get_property(attribution_files GLOBAL PROPERTY _qt_internal_project_attribution_files)
@@ -937,6 +1017,7 @@ macro(_qt_internal_get_sbom_add_target_common_options opt_args single_args multi
NO_DEFAULT_QT_SUPPLIER
SBOM_INCOMPLETE_3RD_PARTY_DEPENDENCIES
IS_QT_3RD_PARTY_HEADER_MODULE
IS_EXTERNAL_SBOM_ENTITY
USE_ATTRIBUTION_FILES
CREATE_SBOM_FOR_EACH_ATTRIBUTION
__QT_INTERNAL_HANDLE_QT_ENTITY_TYPE_PACKAGE_VERSION
@@ -948,9 +1029,12 @@ macro(_qt_internal_get_sbom_add_target_common_options opt_args single_args multi
__QT_INTERNAL_HANDLE_QT_ENTITY_ATTRIBUTION_FILES
)
set(${single_args}
SPDX_ID
EXTERNAL_SBOM_DOCUMENT_TARGET
DEFAULT_SBOM_ENTITY_TYPE
SBOM_ENTITY_TYPE
PACKAGE_VERSION
PACKAGE_SUMMARY
FRIENDLY_PACKAGE_NAME
SUPPLIER
CPE_VENDOR
@@ -970,6 +1054,9 @@ macro(_qt_internal_get_sbom_add_target_common_options opt_args single_args multi
ATTRIBUTION_FILE_PATHS
ATTRIBUTION_FILE_DIR_PATHS
ATTRIBUTION_IDS
SBOM_RELATIONSHIP_ENTRIES
# deprecated, previously used for SPDX v2 string-ified relationships
# still used by WebEngine.
SBOM_RELATIONSHIPS
)
@@ -1114,13 +1201,7 @@ function(_qt_internal_sbom_add_target target)
string(APPEND package_comment "Contained in CMake package: ${qt_package_name}\n")
endif()
# Record the target spdx id right now, so we can refer to it in later attribution targets
# if needed.
_qt_internal_sbom_record_target_spdx_id(${target}
SBOM_ENTITY_TYPE "${sbom_entity_type}"
PACKAGE_NAME "${package_name_for_spdx_id}"
OUT_VAR package_spdx_id
)
_qt_internal_sbom_get_spdx_id_for_target("${target}" package_spdx_id)
if(arg_USE_ATTRIBUTION_FILES)
set(attribution_args
@@ -1471,35 +1552,39 @@ function(_qt_internal_sbom_add_target target)
list(APPEND project_package_options_cydx COMMENT "\n${package_comment}")
endif()
_qt_internal_sbom_handle_target_dependencies("${target}"
SPDX_ID "${package_spdx_id}"
LIBRARIES "${arg_LIBRARIES}"
PUBLIC_LIBRARIES "${arg_PUBLIC_LIBRARIES}"
OUT_CYDX_DEPENDENCIES cydx_dependencies
OUT_SPDX_RELATIONSHIPS spdx_relationships
OUT_EXTERNAL_TARGET_DEPENDENCIES external_target_dependencies
)
if(cydx_dependencies)
list(APPEND project_package_options_cydx DEPENDENCIES ${cydx_dependencies})
endif()
# These are processed at the end of document generation.
if(external_target_dependencies)
_qt_internal_sbom_record_external_target_dependecies(
TARGETS ${external_target_dependencies}
)
endif()
get_cmake_property(project_spdx_id _qt_internal_sbom_project_spdx_id)
list(APPEND spdx_relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
if(arg_SBOM_RELATIONSHIPS)
list(APPEND spdx_relationships "${arg_SBOM_RELATIONSHIPS}")
_qt_internal_forward_function_args(
FORWARD_PREFIX arg
FORWARD_OUT_VAR relationship_args
FORWARD_MULTI
LIBRARIES
PUBLIC_LIBRARIES
SBOM_DEPENDENCIES
SBOM_RELATIONSHIP_ENTRIES
SBOM_RELATIONSHIPS # deprecated, still used by WebEngine
)
_qt_internal_sbom_handle_target_relationships("${target}"
SPDX_ID "${package_spdx_id}"
PROJECT_SPDX_ID "${project_spdx_id}"
${relationship_args}
OUT_VAR_SBOM_RELATIONSHIP_ENTRIES sbom_relationship_entries
OUT_VAR_SPDX_V2_RELATIONSHIPS spdx_relationships # deprecated, still used by WebEngine
)
if(sbom_relationship_entries)
list(APPEND project_package_options_cydx
SBOM_RELATIONSHIP_ENTRIES ${sbom_relationship_entries})
list(APPEND project_package_options_spdx
SBOM_RELATIONSHIP_ENTRIES ${sbom_relationship_entries})
endif()
if(spdx_relationships)
list(APPEND project_package_options_spdx RELATIONSHIPS ${spdx_relationships})
endif()
list(REMOVE_DUPLICATES spdx_relationships)
list(JOIN spdx_relationships "\nRelationship: " relationships)
list(APPEND project_package_options_spdx RELATIONSHIP "${relationships}")
if(arg_PACKAGE_SUMMARY)
list(APPEND project_package_options_spdx PACKAGE_SUMMARY "${arg_PACKAGE_SUMMARY}")
endif()
if(QT_SBOM_GENERATE_SPDX_V2)
_qt_internal_sbom_generate_add_package(
@@ -1604,9 +1689,7 @@ function(_qt_internal_add_sbom target)
return()
endif()
set(opt_args
IMMEDIATE_FINALIZATION
)
set(opt_args "")
set(single_args "")
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -1619,10 +1702,6 @@ function(_qt_internal_add_sbom target)
_qt_internal_create_sbom_target("${target}" ${forward_args})
endif()
if(arg_IMMEDIATE_FINALIZATION)
list(APPEND forward_args IMMEDIATE_FINALIZATION)
endif()
# Save the passed options.
_qt_internal_extend_sbom("${target}" ${forward_args})
endfunction()
@@ -1654,9 +1733,99 @@ function(_qt_internal_create_sbom_target target)
)
endfunction()
# Creates a custom target to represent a project's root SBOM package / component.
# For SPDX it represents what we consider the root package, although the spec doesn't define
# any such package.
# For CYDX it represents the root component, which is defined by the spec.
# The target is currently intended to be used in sbom relationship entries, but might be expanded
# with further information.
function(_qt_internal_create_project_sbom_target)
if(NOT QT_GENERATE_SBOM)
return()
endif()
set(opt_args "")
set(single_args "")
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_get_current_project_target(target)
if(TARGET "${target}")
message(FATAL_ERROR "The target ${target} already exists.")
endif()
add_library("${target}" INTERFACE IMPORTED)
get_property(project_spdx_id GLOBAL PROPERTY _qt_internal_sbom_project_spdx_id)
if(NOT project_spdx_id)
message(FATAL_ERROR "The global property _qt_internal_sbom_project_spdx_id was not set, "
"which is required to create a project sbom target.")
endif()
get_property(repo_document_namespace
GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace)
if(NOT repo_document_namespace)
message(FATAL_ERROR "The global property _qt_internal_sbom_repo_document_namespace was not"
" set, which is required to create a project sbom target.")
endif()
get_property(bom_serial_number_uuid
GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid)
if(NOT bom_serial_number_uuid)
message(FATAL_ERROR "The global property "
"_qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid"
" was not set, which is required to create a project sbom target.")
endif()
_qt_internal_sbom_compute_external_document_ref_spdx_id(
"${arg_PROJECT_NAME}" external_document_ref)
set_target_properties("${target}" PROPERTIES
IMPORTED_GLOBAL TRUE
_qt_sbom_is_custom_sbom_target "TRUE"
_qt_sbom_is_project_sbom_target "TRUE"
_qt_sbom_spdx_id "${project_spdx_id}"
_qt_sbom_entity_type "SBOM_PROJECT"
_qt_sbom_spdx_repo_document_namespace "${repo_document_namespace}"
_qt_sbom_spdx_v2_external_document_ref "${external_document_ref}"
_qt_sbom_cydx_bom_serial_number_uuid "${bom_serial_number_uuid}"
_qt_sbom_spdx_repo_project_name_lowercase "${project_name}"
)
# The computation for these relies on the previous ones being set.
_qt_internal_sbom_compute_external_spdx_v2_id("${target}" external_spdx_v2_id)
_qt_internal_sbom_get_cydx_external_bom_link("${target}" external_bom_link)
set_target_properties("${target}" PROPERTIES
_qt_sbom_spdx_v2_external_spdx_id "${external_spdx_v2_id}"
_qt_sbom_cydx_external_bom_link "${external_bom_link}"
)
endfunction()
# Returns the target name representing the current sbom project.
# When SBOM generation is disable, returns an empty string.
function(_qt_internal_sbom_get_current_project_target out_var)
if(NOT QT_GENERATE_SBOM)
set(${out_var} "" PARENT_SCOPE)
return()
endif()
_qt_internal_sbom_get_root_project_name_lower_case(project_name)
set(target "${project_name}ProjectSbom")
set(${out_var} "${target}" PARENT_SCOPE)
endfunction()
# Helper to add additional sbom information for an existing target.
# Just appends the options to the target's sbom args property, which will will be evaluated
# during finalization.
# For external sbom targets there is no finalization, so the relevant info is recorded
# immediately.
function(_qt_internal_extend_sbom target)
if(NOT QT_GENERATE_SBOM)
return()
@@ -1671,12 +1840,15 @@ function(_qt_internal_extend_sbom target)
set(opt_args
NO_FINALIZATION
IMMEDIATE_FINALIZATION
IS_EXTERNAL_SBOM_ENTITY
)
set(single_args
TYPE # deprecated
SBOM_ENTITY_TYPE
DEFAULT_SBOM_ENTITY_TYPE
FRIENDLY_PACKAGE_NAME
SPDX_ID
EXTERNAL_SBOM_DOCUMENT_TARGET
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -1692,29 +1864,69 @@ function(_qt_internal_extend_sbom target)
_qt_internal_map_sbom_entity_type(sbom_entity_type ${ARGN})
# Make sure a spdx id is recorded for the target right now, so it is "known" when handling
# relationships for other targets, even if the target was not yet finalized.
if(sbom_entity_type)
# Friendly package name is allowed to be empty.
set(package_name_option "")
if(arg_FRIENDLY_PACKAGE_NAME)
set(package_name_option PACKAGE_NAME "${arg_FRIENDLY_PACKAGE_NAME}")
# Make sure the spdx id and sbom entity type is recorded for the target right now, so it is
# "known" when handling relationships for other targets, even if the target was not yet
# finalized.
# The SBOM entity type is expected to be passed when creating a new SBOM target, or extending
# an existing target for the first time with SBOM info.
_qt_internal_sbom_get_spdx_id_for_target("${target}" spdx_id)
if(NOT spdx_id)
set(record_spdx_id_args "")
# Isn't allowed to be empty when creating the spdx id.
if(sbom_entity_type)
list(APPEND record_spdx_id_args SBOM_ENTITY_TYPE "${sbom_entity_type}")
else()
message(FATAL_ERROR
"Target '${target}' is missing an SBOM_ENTITY_TYPE value. "
"Make sure to pass it when creating new SBOM target or extending an existing "
"target with SBOM infromation for the first time.")
endif()
_qt_internal_sbom_record_target_spdx_id(${target}
SBOM_ENTITY_TYPE "${sbom_entity_type}"
${package_name_option}
)
# Friendly package name is allowed to be empty.
if(arg_FRIENDLY_PACKAGE_NAME)
list(APPEND record_spdx_id_args PACKAGE_NAME "${arg_FRIENDLY_PACKAGE_NAME}")
endif()
# Allow specifying a custom spdx id, even for non-external targets.
if(arg_SPDX_ID)
list(APPEND record_spdx_id_args SPDX_ID "${arg_SPDX_ID}")
endif()
if(arg_IS_EXTERNAL_SBOM_ENTITY)
list(APPEND record_spdx_id_args IS_EXTERNAL_SBOM_ENTITY)
endif()
if(arg_EXTERNAL_SBOM_DOCUMENT_TARGET)
list(APPEND record_spdx_id_args
EXTERNAL_SBOM_DOCUMENT_TARGET ${arg_EXTERNAL_SBOM_DOCUMENT_TARGET})
endif()
_qt_internal_sbom_record_target_spdx_id(${target} ${record_spdx_id_args})
endif()
get_target_property(is_system_library "${target}" _qt_internal_sbom_is_system_library)
get_target_property(is_project_sbom_target "${target}" _qt_sbom_is_project_sbom_target)
set_property(TARGET ${target} APPEND PROPERTY _qt_finalize_sbom_args "${forward_args}")
# Record the target's relevant properties immediately after they are saved above, so we can
# refer to it in relationship entries.
if(arg_IS_EXTERNAL_SBOM_ENTITY AND QT_SBOM_GENERATE_CYDX_V1_6)
_qt_internal_sbom_record_external_target_dependecies(TARGETS "${target}")
endif()
# If requested via NO_FINALIZATION or the target is a system library, don't run finalization.
# This is necessary for system libraries because those are handled in special code path just
# before finishing project sbom generation, and finalizing them would cause issues because they
# don't actually have a TYPE until a later point in time.
if(NOT arg_NO_FINALIZATION AND NOT is_system_library)
# Also skip regular processing / finalization for external targets, because their handling
# is different, and we don't create a regular package for them.
if(NOT arg_NO_FINALIZATION
AND NOT is_system_library
AND NOT is_project_sbom_target
AND NOT arg_IS_EXTERNAL_SBOM_ENTITY
)
# Defer finalization. In case it was already deferred, it will be a no-op.
# Some targets need immediate finalization, like the PlatformInternal ones,
# because otherwise they would be finalized after the sbom was already generated.
@@ -1884,7 +2096,7 @@ function(_qt_internal_sbom_get_root_project_name_lower_case out_var)
get_cmake_property(project_name _qt_internal_sbom_repo_project_name)
if(NOT project_name)
message(FATAL_ERROR "No SBOM project name was set.")
message(FATAL_ERROR "The current active SBOM project name was not found.")
endif()
string(TOLOWER "${project_name}" repo_project_name_lowercase)
@@ -1904,9 +2116,110 @@ function(_qt_internal_sbom_get_qt_repo_project_name_lower_case out_var)
set(${out_var} "${repo_project_name_lowercase}" PARENT_SCOPE)
endfunction()
# Get a spdx id to reference an external document.
# Compute a SPDX v2.3 DocumentRef ID, to reference the current project's SPDX document as an
# external document in another project.
#
# This is only meant to be used for exporting the value as part of the target's properties, to be
# used in a different project.
#
# It should NOT be used to refer to already exported targets.
#
# For querying the DocumentRef of an exported target, use
# _qt_internal_sbom_get_external_document_ref_spdx_id_from_sbom_target.
function(_qt_internal_sbom_compute_external_document_ref_spdx_id repo_name out_var)
_qt_internal_sbom_get_spdx_id_unique_suffix(spdx_id_unique_suffix)
set(${out_var} "DocumentRef-${repo_name}${spdx_id_unique_suffix}" PARENT_SCOPE)
endfunction()
# Older deprecated function name of the function above, kept for compatibility in case it is used.
function(_qt_internal_sbom_get_external_document_ref_spdx_id repo_name out_var)
set(${out_var} "DocumentRef-${repo_name}" PARENT_SCOPE)
if(NOT QT_NO_DEPRECATED_GET_EXTERNAL_DOCUMENT_REF_SPDX_ID)
message(DEPRECATION
"This function is deprecated. "
"Please use _qt_internal_sbom_compute_external_document_ref_spdx_id() instead."
"To silence this deprecation, pass "
"-DQT_NO_DEPRECATED_GET_EXTERNAL_DOCUMENT_REF_SPDX_ID=ON "
"when configuring the project."
)
endif()
_qt_internal_sbom_compute_external_document_ref_spdx_id("${repo_name}" external_document_ref)
set(${out_var} "${external_document_ref}" PARENT_SCOPE)
endfunction()
# Query the external reference DocumentRef ID from the given SBOM target.
#
# If CREATE_TEMPORARY_REF_WHEN_MISSING is set, it instead of erroring out when the document ref
# isn't found, create one and assign it to the to the usual target property.
# This allows referring to older targets that were exported without the necessary properties.
function(_qt_internal_sbom_get_external_document_ref_spdx_id_from_sbom_target)
if(NOT QT_GENERATE_SBOM)
set(${out_var} "" PARENT_SCOPE)
return()
endif()
set(opt_args
CREATE_TEMPORARY_REF_WHEN_MISSING
)
set(single_args
TARGET
OUT_VAR
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_TARGET)
message(FATAL_ERROR "TARGET argument is required.")
endif()
if(NOT arg_OUT_VAR)
message(FATAL_ERROR "OUT_VAR argument is required.")
endif()
set(target_unaliased "${arg_TARGET}")
_qt_internal_dealias_target(target_unaliased)
get_target_property(external_document_ref
"${target_unaliased}" _qt_sbom_spdx_v2_external_document_ref)
if(NOT external_document_ref)
if(NOT arg_CREATE_TEMPORARY_REF_WHEN_MISSING
AND NOT QT_SBOM_NO_AUTO_CREATE_DOCUMENT_REF_WHEN_MISSING)
message(FATAL_ERROR
"The target '${arg_TARGET}' does not have a recorded SPDX v2.3 external document "
"DocumentRef value recorded.")
else()
string(MAKE_C_IDENTIFIER "${arg_TARGET}" target_c_identifier)
string(REPLACE "_" "-" target_c_identifier "${target_c_identifier}")
_qt_internal_sbom_compute_external_document_ref_spdx_id("${target_c_identifier}"
external_document_ref
)
set_target_properties("${target_unaliased}" PROPERTIES
_qt_sbom_spdx_v2_external_document_ref "${external_document_ref}")
message(DEBUG "Did not find external document ref for target '${arg_TARGET}'. "
"Created one and set it to '${external_document_ref}' in the target's properties.")
endif()
endif()
set(${arg_OUT_VAR} "${external_document_ref}" PARENT_SCOPE)
endfunction()
# Computes a spdx id to reference the target's spdx v2 package via a SPDX v2 external document ref.
# To be used for exporting the value as part of the target's metadata.
function(_qt_internal_sbom_compute_external_spdx_v2_id target out_var)
get_target_property(project_name_lowercase "${target}"
_qt_sbom_spdx_repo_project_name_lowercase)
_qt_internal_sbom_compute_external_document_ref_spdx_id(
"${project_name_lowercase}" external_document_ref)
_qt_internal_sbom_get_spdx_id_for_target("${target}" spdx_id)
set(ext_spdx_id "${external_document_ref}:${spdx_id}")
set(${out_var} "${ext_spdx_id}" PARENT_SCOPE)
endfunction()
# Sanitize a given value to be used as a SPDX id.
@@ -1920,12 +2233,21 @@ function(_qt_internal_sbom_get_sanitized_spdx_id out_var hint)
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()
# Generates a spdx id for a target and saves it its properties.
# Generates a spdx id for a target and saves it, the passed in SBOM_ENTITY_TYPE value, and a lot
# of other project specific properties like the spdx document namespace, cyclone dx document serial
# number, etc, in its target properties.
# A SBOM_ENTITY_TYPE value is required when saving the spdx id.
# Exits early and returns the spdx id if it was already created earlier.
function(_qt_internal_sbom_record_target_spdx_id target)
set(opt_args "")
set(opt_args
IS_EXTERNAL_SBOM_ENTITY
)
set(single_args
SPDX_ID
PACKAGE_NAME
SBOM_ENTITY_TYPE
EXTERNAL_SBOM_DOCUMENT_TARGET
OUT_VAR
)
set(multi_args "")
@@ -1948,14 +2270,32 @@ function(_qt_internal_sbom_record_target_spdx_id target)
set(package_name_for_spdx_id "${target}")
endif()
_qt_internal_sbom_generate_target_package_spdx_id(package_spdx_id
SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
PACKAGE_NAME "${package_name_for_spdx_id}"
)
# If an explicit SPDX_ID is set, use it rather than generating one.
if(arg_SPDX_ID)
set(package_spdx_id "${arg_SPDX_ID}")
else()
_qt_internal_sbom_generate_target_package_spdx_id(package_spdx_id
SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
PACKAGE_NAME "${package_name_for_spdx_id}"
)
endif()
set(save_spdx_id_args "")
if(arg_IS_EXTERNAL_SBOM_ENTITY)
list(APPEND save_spdx_id_args IS_EXTERNAL_SBOM_ENTITY)
endif()
if(arg_EXTERNAL_SBOM_DOCUMENT_TARGET)
list(APPEND save_spdx_id_args
EXTERNAL_SBOM_DOCUMENT_TARGET ${arg_EXTERNAL_SBOM_DOCUMENT_TARGET})
endif()
_qt_internal_sbom_save_spdx_id_for_target("${target}"
SPDX_ID "${package_spdx_id}"
PACKAGE_NAME "${package_name_for_spdx_id}"
SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
${save_spdx_id_args}
)
_qt_internal_sbom_is_qt_entity_type("${arg_SBOM_ENTITY_TYPE}" is_qt_entity_type)
@@ -1987,9 +2327,13 @@ function(_qt_internal_sbom_generate_target_package_spdx_id out_var)
_qt_internal_sbom_get_root_project_name_for_spdx_id(repo_project_name_spdx_id)
_qt_internal_sbom_get_package_infix("${arg_SBOM_ENTITY_TYPE}" package_infix)
_qt_internal_sbom_get_spdx_id_unique_suffix(spdx_id_unique_suffix)
_qt_internal_sbom_get_sanitized_spdx_id(spdx_id
"SPDXRef-${repo_project_name_spdx_id}-${package_infix}-${arg_PACKAGE_NAME}")
string(CONCAT spdx_id_input
"SPDXRef-${repo_project_name_spdx_id}-${package_infix}-${arg_PACKAGE_NAME}"
"${spdx_id_unique_suffix}"
)
_qt_internal_sbom_get_sanitized_spdx_id(spdx_id "${spdx_id_input}")
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()
@@ -2001,11 +2345,14 @@ endfunction()
# Also saves the sbom entity type and package name, because it's needed when creating CycloneDX
# components where the target is in an external document.
function(_qt_internal_sbom_save_spdx_id_for_target target)
set(opt_args "")
set(opt_args
IS_EXTERNAL_SBOM_ENTITY
)
set(single_args
SPDX_ID
PACKAGE_NAME
SBOM_ENTITY_TYPE
EXTERNAL_SBOM_DOCUMENT_TARGET
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -2023,6 +2370,32 @@ function(_qt_internal_sbom_save_spdx_id_for_target target)
message(FATAL_ERROR "SBOM_ENTITY_TYPE must be set")
endif()
if(arg_IS_EXTERNAL_SBOM_ENTITY AND NOT arg_EXTERNAL_SBOM_DOCUMENT_TARGET)
message(FATAL_ERROR
"Target '${target}' is marked as external with the IS_EXTERNAL_SBOM_ENTITY option "
"but no EXTERNAL_SBOM_DOCUMENT_TARGET was provided. Make sure to pass in a "
"an external sbom document target created by "
"_qt_internal_sbom_add_external_reference_document() to the mentioned option.")
endif()
if(arg_EXTERNAL_SBOM_DOCUMENT_TARGET AND NOT TARGET "${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}")
message(FATAL_ERROR
"The target '${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}' passed to "
"EXTERNAL_SBOM_DOCUMENT_TARGET does not exist.")
endif()
if(arg_EXTERNAL_SBOM_DOCUMENT_TARGET)
get_target_property(is_external_project_sbom_target "${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}"
_qt_sbom_is_external_project_sbom_target)
if(NOT is_external_project_sbom_target)
message(FATAL_ERROR
"The target '${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}' passed to "
"EXTERNAL_SBOM_DOCUMENT_TARGET is not marked as an external project sbom target. "
"Make sure to create the target with "
"_qt_internal_sbom_add_external_reference_document().")
endif()
endif()
set(spdx_id "${arg_SPDX_ID}")
message(DEBUG "Saving spdx id for target ${target}: ${spdx_id}")
@@ -2033,18 +2406,50 @@ function(_qt_internal_sbom_save_spdx_id_for_target target)
set_target_properties(${target_unaliased} PROPERTIES
_qt_sbom_spdx_id "${spdx_id}")
# Retrieve repo specific properties.
get_property(repo_document_namespace
GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace)
# Retrieve some of the repo / project specific properties.
if(arg_IS_EXTERNAL_SBOM_ENTITY)
get_target_property(repo_document_namespace
"${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}" _qt_sbom_spdx_repo_document_namespace)
else()
get_property(repo_document_namespace
GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace)
endif()
get_property(bom_serial_number_uuid
GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid)
if(arg_IS_EXTERNAL_SBOM_ENTITY)
get_target_property(bom_serial_number_uuid
"${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}" _qt_sbom_cydx_bom_serial_number_uuid)
else()
get_property(bom_serial_number_uuid
GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid)
endif()
get_property(relative_installed_repo_document_path
GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path)
if(arg_IS_EXTERNAL_SBOM_ENTITY)
get_target_property(relative_installed_repo_document_path
"${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}"
_qt_sbom_spdx_relative_installed_repo_document_path)
else()
get_property(relative_installed_repo_document_path
GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path)
endif()
get_property(project_name_lowercase
GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase)
if(NOT arg_IS_EXTERNAL_SBOM_ENTITY)
get_property(document_spdx_v2_tag_value_relative_path
GLOBAL PROPERTY _qt_internal_sbom_document_spdx_v2_tag_value_relative_path)
get_property(document_spdx_v2_json_relative_path
GLOBAL PROPERTY _qt_internal_sbom_document_spdx_v2_json_relative_path)
get_property(document_cydx_v1_6_json_relative_path
GLOBAL PROPERTY _qt_internal_sbom_document_cydx_v1_6_json_relative_path)
endif()
if(arg_IS_EXTERNAL_SBOM_ENTITY)
get_target_property(project_name_lowercase
"${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}" _qt_sbom_spdx_repo_project_name_lowercase)
else()
get_property(project_name_lowercase
GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase)
endif()
# And save them on the target.
set_property(TARGET ${target_unaliased} PROPERTY
@@ -2055,6 +2460,20 @@ function(_qt_internal_sbom_save_spdx_id_for_target target)
_qt_sbom_cydx_bom_serial_number_uuid
"${bom_serial_number_uuid}")
if(NOT arg_IS_EXTERNAL_SBOM_ENTITY)
set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_spdx_v2_document_tag_value_relative_path
"${document_spdx_v2_tag_value_relative_path}")
set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_spdx_v2_document_json_relative_path
"${document_spdx_v2_json_relative_path}")
set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_cydx_v1_6_document_json_relative_path
"${document_cydx_v1_6_json_relative_path}")
endif()
set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_spdx_relative_installed_repo_document_path
"${relative_installed_repo_document_path}")
@@ -2071,19 +2490,53 @@ function(_qt_internal_sbom_save_spdx_id_for_target target)
_qt_sbom_entity_type
"${arg_SBOM_ENTITY_TYPE}")
# Export the properties, so they can be queried by other repos.
# We also do it for versionless targets.
set(export_properties
_qt_sbom_entity_type
_qt_sbom_package_name
_qt_sbom_spdx_id
_qt_sbom_spdx_repo_document_namespace
_qt_sbom_cydx_bom_serial_number_uuid
_qt_sbom_spdx_relative_installed_repo_document_path
_qt_sbom_spdx_repo_project_name_lowercase
)
set_property(TARGET "${target_unaliased}" APPEND PROPERTY
EXPORT_PROPERTIES "${export_properties}")
if(arg_IS_EXTERNAL_SBOM_ENTITY)
get_target_property(external_document_ref
"${arg_EXTERNAL_SBOM_DOCUMENT_TARGET}" _qt_sbom_spdx_v2_external_document_ref)
else()
_qt_internal_sbom_compute_external_document_ref_spdx_id(
"${project_name_lowercase}" external_document_ref)
endif()
set_property(TARGET "${target_unaliased}" PROPERTY
_qt_sbom_spdx_v2_external_document_ref "${external_document_ref}")
_qt_internal_sbom_compute_external_spdx_v2_id("${target_unaliased}" external_spdx_v2_id)
set_property(TARGET "${target_unaliased}" PROPERTY
_qt_sbom_spdx_v2_external_spdx_id "${external_spdx_v2_id}")
_qt_internal_sbom_get_cydx_external_bom_link("${target_unaliased}" external_bom_link)
set_property(TARGET "${target_unaliased}" PROPERTY
_qt_sbom_cydx_external_bom_link "${external_bom_link}")
_qt_internal_sbom_get_cydx_external_urn_bom_version("${target_unaliased}"
external_urn_bom_version)
set_property(TARGET "${target_unaliased}" PROPERTY
_qt_sbom_cydx_external_urn_bom_version "${external_urn_bom_version}")
if(NOT arg_IS_EXTERNAL_SBOM_ENTITY)
# Export the properties, so they can be queried by other repos.
# We also do it for versionless targets.
set(export_properties
_qt_sbom_entity_type
_qt_sbom_package_name
_qt_sbom_spdx_id
_qt_sbom_spdx_repo_document_namespace
_qt_sbom_spdx_v2_external_document_ref
_qt_sbom_spdx_v2_external_spdx_id
_qt_sbom_spdx_v2_document_tag_value_relative_path
_qt_sbom_spdx_v2_document_json_relative_path
_qt_sbom_cydx_bom_serial_number_uuid
_qt_sbom_cydx_external_bom_link
_qt_sbom_cydx_external_urn_bom_version
_qt_sbom_cydx_v1_6_document_json_relative_path
_qt_sbom_spdx_relative_installed_repo_document_path
_qt_sbom_spdx_repo_project_name_lowercase
)
set_property(TARGET "${target_unaliased}" APPEND PROPERTY
EXPORT_PROPERTIES "${export_properties}")
endif()
endfunction()
# Returns whether the given sbom type is considered to be a Qt type like a module or a tool.
@@ -2165,6 +2618,12 @@ function(_qt_internal_sbom_get_spdx_id_for_target target out_var)
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()
# Retrieves a saved spdx id for the current project. Might be empty.
function(_qt_internal_sbom_get_current_project_spdx_id out_var)
get_cmake_property(spdx_id _qt_internal_sbom_project_spdx_id)
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()
# Returns a package infix for a given target sbom type to be used in spdx package id generation.
function(_qt_internal_sbom_get_package_infix type out_infix)
if(type STREQUAL "QT_MODULE")
@@ -2201,10 +2660,14 @@ function(_qt_internal_sbom_get_package_infix type out_infix)
set(package_infix "3rdparty-library-with-files")
elseif(type STREQUAL "THIRD_PARTY_SOURCES")
set(package_infix "3rdparty-sources")
elseif(type STREQUAL "SBOM_PROJECT")
set(package_infix "sbom-project")
elseif(type STREQUAL "TRANSLATIONS")
set(package_infix "translations")
elseif(type STREQUAL "RESOURCES")
set(package_infix "resource")
elseif(type STREQUAL "BUILD_TOOL")
set(package_infix "build-tool")
elseif(type STREQUAL "CUSTOM")
set(package_infix "custom")
elseif(type STREQUAL "CUSTOM_NO_INFIX")
@@ -2252,10 +2715,14 @@ function(_qt_internal_sbom_get_package_purpose type out_purpose)
set(package_purpose "LIBRARY")
elseif(type STREQUAL "THIRD_PARTY_SOURCES")
set(package_purpose "LIBRARY")
elseif(type STREQUAL "SBOM_PROJECT")
set(package_purpose "OTHER")
elseif(type STREQUAL "TRANSLATIONS")
set(package_purpose "OTHER")
elseif(type STREQUAL "RESOURCES")
set(package_purpose "OTHER")
elseif(type STREQUAL "BUILD_TOOL")
set(package_purpose "OTHER")
elseif(type STREQUAL "CUSTOM")
set(package_purpose "OTHER")
elseif(type STREQUAL "CUSTOM_NO_INFIX")
@@ -166,7 +166,7 @@ endfunction()
# DEPS_FOUND_FOR_$OP_KEY var.
function(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found)
set(opt_args
REQUIRED
FINAL_ATTEMPT
PYTHON_FOUND
DEP_FOUND
EVERYTHING_FOUND
@@ -176,17 +176,20 @@ function(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found)
PYTHON_PATH
PYTHON_VERSION
DEP_PACKAGE_NAME
PYTHON_FIND_OUTPUT
DEP_FIND_OUTPUT
ATTEMPT_MESSAGE
ATTEMPT_RESULT_MESSAGE
OP_KEY
ATTEMPT_LOG_OUT_VAR
ATTEMPT_COUNT_OUT_VAR
)
set(multi_args
PYTHON_COMMON_ARGS
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
if(arg_EVERYTHING_FOUND)
message(DEBUG "Python Dependencies found. "
"Using Python '${arg_PYTHON_PATH}' for running SBOM op '${arg_OP_KEY}'.")
message(DEBUG "Python and dependencies found for SBOM operation '${arg_OP_KEY}'. "
"Using Python '${arg_PYTHON_PATH}'")
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
message(DEBUG "Setting QT_INTERNAL_SBOM_PYTHON_EXECUTABLE to ${arg_PYTHON_PATH}")
@@ -202,40 +205,110 @@ function(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found)
return()
endif()
if(arg_REQUIRED)
set(message_type "FATAL_ERROR")
else()
set(message_type "DEBUG")
endif()
if(NOT arg_PYTHON_FOUND)
# Look for python one more time, this time without QUIET, to show an error why it
# wasn't found.
if(arg_REQUIRED)
_qt_internal_sbom_find_python_helper(${arg_PYTHON_COMMON_ARGS}
OUT_VAR_PYTHON_PATH unused_python
OUT_VAR_PYTHON_FOUND unused_found
)
endif()
message(${message_type}
"Python ${arg_REQUIRED_VERSION} for running SBOM op '${arg_OP_KEY}' NOT found.")
string(CONCAT not_found_message
"Python ${arg_REQUIRED_VERSION} for running SBOM operation '${arg_OP_KEY}' NOT found.\n"
"Suggestion: In case if Python is installed, but not in a standard location, specify "
"it using -DQT_SBOM_PYTHON_INTERP=<python-executable-path>\n"
)
elseif(NOT arg_DEP_FOUND)
set(extra_install_message "")
if(message_type STREQUAL "FATAL_ERROR")
set(extra_install_message
"Install it using pip install '${arg_DEP_PACKAGE_NAME}' \n")
endif()
message(${message_type}
"Python dependency for running SBOM op '${arg_OP_KEY}' NOT found:\n"
${extra_install_message}
"Details:\n"
"Python interpreter: ${arg_PYTHON_PATH}\n"
"Error output: \n${arg_DEP_FIND_OUTPUT}\n\n"
string(CONCAT not_found_message
"Python dependency for running SBOM operation '${arg_OP_KEY}' NOT found.\n"
"Suggestion: Install it using pip install '${arg_DEP_PACKAGE_NAME}'\n"
)
endif()
set(dep_find_contents "")
if(arg_DEP_FIND_OUTPUT)
string(CONCAT dep_find_contents
"Dependency find log begins.\n${arg_DEP_FIND_OUTPUT}\n"
"Dependency find log ends.\n"
)
endif()
string(CONCAT current_attempt_message
"${arg_ATTEMPT_MESSAGE}\n"
"Result: ${not_found_message}\n"
"Python find log begins.\n${arg_PYTHON_FIND_OUTPUT}\n"
"Python find log ends.\n"
"${dep_find_contents}"
"${arg_ATTEMPT_RESULT_MESSAGE}\n\n"
)
message(DEBUG "${current_attempt_message}")
if(arg_FINAL_ATTEMPT)
set(previous_attempts_contents "")
if(${arg_ATTEMPT_LOG_OUT_VAR})
string(CONCAT previous_attempts_contents
"Previous attempts log below.\n"
"${${arg_ATTEMPT_LOG_OUT_VAR}}"
)
endif()
string(CONCAT final_attempt_message
"${current_attempt_message}\n"
"${previous_attempts_contents}"
)
message(FATAL_ERROR "${final_attempt_message}")
endif()
# Collect all previous attempt messages, to show them at the very end if not found.
if(arg_ATTEMPT_LOG_OUT_VAR)
set(attempt_count "${${arg_ATTEMPT_COUNT_OUT_VAR}}")
math(EXPR attempt_count "${attempt_count} + 1")
set(${arg_ATTEMPT_COUNT_OUT_VAR} "${attempt_count}" PARENT_SCOPE)
string(CONCAT attempt_log_content
"${${arg_ATTEMPT_LOG_OUT_VAR}}\n"
"Attempt ${attempt_count}:\n"
"${current_attempt_message}")
set(${arg_ATTEMPT_LOG_OUT_VAR} "${attempt_log_content}" PARENT_SCOPE)
endif()
endfunction()
# Helper macro to output a debug or error message when python or one of its dependencies are not
# found. Expects the caller to set all of the vars.
# Meant to reduce the line noise due to the repeated calls.
macro(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda)
# Assemble args to show what wasn't found.
if(python_found)
list(APPEND verify_args PYTHON_FOUND)
endif()
if(dep_found)
list(APPEND verify_args DEP_FOUND)
endif()
if(everything_found)
list(APPEND verify_args EVERYTHING_FOUND)
endif()
if(python_path)
list(APPEND verify_args PYTHON_PATH "${python_path}")
endif()
if(python_version)
list(APPEND verify_args PYTHON_VERSION "${python_version}")
endif()
if(python_find_output)
list(APPEND verify_args PYTHON_FIND_OUTPUT "${python_find_output}")
endif()
if(dep_find_output)
list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
endif()
if(attempt_message)
list(APPEND verify_args ATTEMPT_MESSAGE "${attempt_message}")
endif()
if(attempt_result_message)
list(APPEND verify_args ATTEMPT_RESULT_MESSAGE "${attempt_result_message}")
endif()
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
${verify_args}
REQUIRED_VERSION "${required_version}"
OP_KEY "${arg_OP_KEY}"
DEP_PACKAGE_NAME "${dep_package_name}"
ATTEMPT_LOG_OUT_VAR attempt_log
ATTEMPT_COUNT_OUT_VAR attempt_count
)
endmacro()
# Helper to find a python interpreter and a specific python dependency, e.g. to be able to generate
# a SPDX JSON SBOM, or run post-installation steps like NTIA verification.
# The exact dependency should be specified as the OP_KEY.
@@ -244,6 +317,8 @@ endfunction()
# avoid conflicts with any other found python interpreter.
# Also caches the python version found in QT_INTERNAL_SBOM_PYTHON_VERSION, so we can show it
# in the configure summary.
# Attempts to find a suitable Python interpreter with multiple searching approaches, due to platform
# specifics and quirks in how the FindPython module behaves.
function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
set(opt_args
REQUIRED
@@ -298,86 +373,151 @@ function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
set(everything_found FALSE)
# On macOS FindPython prefers looking in the system framework location, but that usually would
# Used by the macro calls below below to collect debug find output across attempts.
set(attempt_log "")
set(attempt_count 0)
# Look in QT_SBOM_PYTHON_INTERP if it's set.
# If QT_SBOM_FORCE_USE_PYTHON_INTERP is set, attempt to use it without preforming a search.
_qt_internal_sbom_get_sbom_python_interp_path(
OUT_VAR_FOUND sbom_python_interp_found
OUT_VAR_PYTHON_PATH sbom_python_interp_path
OUT_VAR_PYTHON_DIR_PATH sbom_python_interp_dir
)
if(sbom_python_interp_found)
set(extra_python_args
QUIET
)
# It's possible that sbom_python_interp_path can't be computed, so we need to check that
# it's not empty.
if(QT_SBOM_FORCE_USE_PYTHON_INTERP AND sbom_python_interp_path)
set(using_message "${sbom_python_interp_path}")
list(APPEND extra_python_args
FORCE_SET_INTERPRETER_PATH
INTERPRETER_PATH "${sbom_python_interp_path}"
)
else()
set(using_message "${sbom_python_interp_dir}")
list(APPEND extra_python_args
SEARCH_IN_INTERPRETER_DIR_PATH
INTERPRETER_DIR_PATH "${sbom_python_interp_dir}"
)
endif()
string(CONCAT attempt_message
"Looking for Python and dependencies for operation '${arg_OP_KEY}'. "
"Searching using QT_SBOM_PYTHON_INTERP: '${using_message}'.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
set(attempt_result_message "")
if(NOT everything_found)
string(CONCAT attempt_result_message
"SBOM Python search using QT_SBOM_PYTHON_INTERP did "
"not find all dependencies for operation '${arg_OP_KEY}'. Search continues."
)
endif()
set(verify_args "")
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda()
endif()
# Look into any custom provided root dir, that was not set via QT_SBOM_PYTHON_INTERP.
_qt_internal_sbom_get_python_root_dir(OUT_VAR_PYTHON_ROOT_DIR_PATH python_root_dir)
if(NOT everything_found AND python_root_dir)
set(extra_python_args
QUIET
SEARCH_IN_ROOT_DIR
)
string(CONCAT attempt_message
"Looking for Python and dependencies for operation '${arg_OP_KEY}'. "
"Searching using Python3_ROOT_DIR: '${python_root_dir}'.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
set(attempt_result_message "")
if(NOT everything_found)
string(CONCAT attempt_result_message
"SBOM Python search using Python3_ROOT_DIR did not find all dependencies "
"for operation '${arg_OP_KEY}'. Search continues."
)
endif()
set(verify_args "")
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda()
endif()
# On macOS, FindPython prefers looking in the system framework location, but that usually would
# not have the required dependencies. So we first look in it, and then fallback to any other
# non-framework python found.
if(CMAKE_HOST_APPLE)
set(extra_python_args SEARCH_IN_FRAMEWORKS QUIET)
message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}' "
"in the system framework location first.")
if(NOT everything_found AND CMAKE_HOST_APPLE)
set(extra_python_args
QUIET
SEARCH_IN_FRAMEWORKS
)
string(CONCAT attempt_message
"Looking for Python and dependencies for operation '${arg_OP_KEY}'. "
"Searching in the macOS framework locations.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
set(attempt_result_message "")
if(NOT everything_found)
# Assemble args to show what wasn't found.
set(verify_args "")
if(python_found)
list(APPEND verify_args PYTHON_FOUND)
endif()
if(dep_found)
list(APPEND verify_args DEP_FOUND)
endif()
if(everything_found)
list(APPEND verify_args EVERYTHING_FOUND)
endif()
if(python_path)
list(APPEND verify_args PYTHON_PATH "${python_path}")
endif()
if(python_version)
list(APPEND verify_args PYTHON_VERSION "${python_version}")
endif()
if(dep_find_output)
list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
endif()
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
${verify_args}
REQUIRED_VERSION "${required_version}"
OP_KEY "${arg_OP_KEY}"
DEP_PACKAGE_NAME "${dep_package_name}"
PYTHON_COMMON_ARGS ${python_common_args}
string(CONCAT attempt_result_message
"macOS-specific SBOM Python search did not find all dependencies "
"for operation '${arg_OP_KEY}'. Search continues."
)
message(DEBUG "Initial Apple-specific SBOM Python search did not find all dependencies "
"for op ${arg_OP_KEY}, looking again outside of the system framework location.")
endif()
set(verify_args "")
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda()
endif()
# Looking again if something wasn't found, or a search was not done yet.
if(NOT everything_found)
set(extra_python_args QUIET)
message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}'.")
# On Windows, also look in the registry first.
if(NOT everything_found AND CMAKE_HOST_WIN32)
set(extra_python_args
QUIET
SEARCH_IN_REGISTRY
)
string(CONCAT attempt_message
"Looking for Python and dependencies for operation '${arg_OP_KEY}'. Searching in the "
" Windows registry.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
set(attempt_result_message "")
if(NOT everything_found)
string(CONCAT attempt_result_message
"Windows-specific SBOM Python search did not find all dependencies "
"for operation '${arg_OP_KEY}'. Search continues."
)
endif()
set(verify_args "")
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda()
endif()
# Assemble args to show what was or wasn't found.
set(verify_args "")
if(arg_REQUIRED)
list(APPEND verify_args REQUIRED)
# Do a final lookup without all the approaches above.
if(NOT everything_found)
set(extra_python_args
QUIET
)
string(CONCAT attempt_message
"Looking for Python and dependencies for operation "
"'${arg_OP_KEY}'. Searching default locations as per FindPython3 docs.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
set(attempt_result_message "")
if(NOT everything_found)
string(CONCAT attempt_result_message
"SBOM Python search in default locations did not find all dependencies "
"for operation '${arg_OP_KEY}'. Search concluded."
)
endif()
set(verify_args "")
# If REQUIRED was passed, and we are at the final attempt, in case we didn't find the
# deps, we will error out.
if(arg_REQUIRED)
list(APPEND verify_args FINAL_ATTEMPT)
endif()
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found_lambda()
endif()
if(python_found)
list(APPEND verify_args PYTHON_FOUND)
endif()
if(dep_found)
list(APPEND verify_args DEP_FOUND)
endif()
if(everything_found)
list(APPEND verify_args EVERYTHING_FOUND)
endif()
if(python_path)
list(APPEND verify_args PYTHON_PATH "${python_path}")
endif()
if(python_version)
list(APPEND verify_args PYTHON_VERSION "${python_version}")
endif()
if(dep_find_output)
list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
endif()
_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
${verify_args}
REQUIRED_VERSION "${required_version}"
OP_KEY "${arg_OP_KEY}"
DEP_PACKAGE_NAME "${dep_package_name}"
PYTHON_COMMON_ARGS ${python_common_args}
)
if(arg_OUT_VAR_DEPS_FOUND)
set(${arg_OUT_VAR_DEPS_FOUND} "${QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY}}"
@@ -15,6 +15,7 @@ macro(_qt_internal_sbom_find_python_and_dependency_helper_lambda)
OUT_VAR_PYTHON_VERSION python_version
OUT_VAR_DEP_FOUND dep_found
OUT_VAR_PYTHON_AND_DEP_FOUND everything_found
OUT_VAR_PYTHON_FIND_OUTPUT python_find_output
OUT_VAR_DEP_FIND_OUTPUT dep_find_output
)
endmacro()
@@ -31,6 +32,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
OUT_VAR_PYTHON_VERSION
OUT_VAR_DEP_FOUND
OUT_VAR_PYTHON_AND_DEP_FOUND
OUT_VAR_PYTHON_FIND_OUTPUT
OUT_VAR_DEP_FIND_OUTPUT
)
set(multi_args
@@ -68,6 +70,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
OUT_VAR_PYTHON_PATH python_path_inner
OUT_VAR_PYTHON_FOUND python_found_inner
OUT_VAR_PYTHON_VERSION python_version_inner
OUT_VAR_PYTHON_FIND_OUTPUT python_find_output_inner
)
if(python_found_inner AND python_path_inner)
@@ -90,14 +93,14 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
endif()
set(${arg_OUT_VAR_DEP_FOUND} "${dep_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_AND_DEP_FOUND} "${everything_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FIND_OUTPUT} "${python_find_output_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_DEP_FIND_OUTPUT} "${dep_find_output_inner}" PARENT_SCOPE)
endfunction()
# Tries to find the python intrepreter, given the QT_SBOM_PYTHON_INTERP path hint, as well as
# other options.
# Tries to find a python intrepreter for SBOM handling.
# Ignores any previously found python.
# Returns the python interpreter path and whether it was successfully found, along with the version
# found.
# found and some debug output when not found.
#
# This is intentionally a function, and not a macro, to prevent overriding the Python3_EXECUTABLE
# non-cache variable in a global scope in case if a different python is found and used for a
@@ -110,13 +113,20 @@ endfunction()
function(_qt_internal_sbom_find_python_helper)
set(opt_args
SEARCH_IN_FRAMEWORKS
SEARCH_IN_REGISTRY
SEARCH_IN_ROOT_DIR
SEARCH_IN_INTERPRETER_DIR_PATH
FORCE_SET_INTERPRETER_PATH
QUIET
)
set(single_args
VERSION
INTERPRETER_PATH
INTERPRETER_DIR_PATH
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_FOUND
OUT_VAR_PYTHON_VERSION
OUT_VAR_PYTHON_FIND_OUTPUT
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -130,9 +140,59 @@ function(_qt_internal_sbom_find_python_helper)
message(FATAL_ERROR "OUT_VAR_PYTHON_FOUND var is required")
endif()
set(find_output "Search options:")
# Allow disabling looking for a python interpreter shipped as part of a macOS system framework.
if(NOT arg_SEARCH_IN_FRAMEWORKS)
set(Python3_FIND_FRAMEWORK NEVER)
list(APPEND find_output "Will NOT search in macOS framework locations.")
else()
list(APPEND find_output "Will search in macOS framework locations.")
endif()
if(NOT arg_SEARCH_IN_REGISTRY)
set(Python3_FIND_REGISTRY NEVER)
list(APPEND find_output "Will NOT search in Windows registry.")
else()
list(APPEND find_output "Will search in Windows registry.")
endif()
# Remove the root dir if not requested to search in it.
if(NOT arg_SEARCH_IN_ROOT_DIR)
unset(Python3_ROOT_DIR)
# Temporarily also unset the env var.
set(python_root_dir_env_value "$ENV{Python3_ROOT_DIR}")
if(python_root_dir_env_value AND NOT QT_SBOM_NO_PYTHON_ROOT_DIR_ENV_BACKUP)
set(python_root_dir_env_backup "$ENV{Python3_ROOT_DIR}")
unset(ENV{Python3_ROOT_DIR})
endif()
list(APPEND find_output "Will NOT search in Python3_ROOT_DIR.")
else()
_qt_internal_sbom_get_python_root_dir(OUT_VAR_PYTHON_ROOT_DIR_PATH Python3_ROOT_DIR)
list(APPEND find_output "Will search in Python3_ROOT_DIR: '${Python3_ROOT_DIR}'.")
endif()
set(force_python_interpreter_path "")
if(arg_FORCE_SET_INTERPRETER_PATH AND arg_INTERPRETER_PATH)
set(force_python_interpreter_path "${arg_INTERPRETER_PATH}")
list(APPEND find_output
"Will force set Python3_EXECUTABLE to provided INTERPRETER_PATH: "
"'${arg_INTERPRETER_PATH}'.")
else()
list(APPEND find_output "Will NOT force set Python3_EXECUTABLE to INTERPRETER_PATH.")
endif()
if(arg_SEARCH_IN_INTERPRETER_DIR_PATH AND arg_INTERPRETER_DIR_PATH)
# Python3_ROOT_DIR expects a directory, not a path.
set(Python3_ROOT_DIR "${arg_INTERPRETER_DIR_PATH}")
list(APPEND find_output
"Will search in provided INTERPRETER_DIR_PATH: '${arg_INTERPRETER_DIR_PATH}'.")
else()
list(APPEND find_output "Will NOT search in INTERPRETER_DIR_PATH.")
endif()
set(required_version "")
@@ -141,32 +201,158 @@ function(_qt_internal_sbom_find_python_helper)
endif()
set(find_quiet "")
if(arg_QUIET)
if(arg_QUIET AND NOT QT_INTERNAL_SBOM_NO_FIND_PYTHON_QUIET)
set(find_quiet "QUIET")
endif()
# Locally reset any executable that was possibly already found.
# We do this to ensure we always re-do the lookup/
# This needs to be set to an empty string, to override any cache variable
set(Python3_EXECUTABLE "")
# When performing a search, locally reset any executable that was possibly already found.
# We do this to ensure we always re-do the lookup.
# This needs to be set to an empty string, to override any cache variable.
# When FORCE_SET_INTERPRETER_PATH is used, set the executable to the given path.
if(arg_FORCE_SET_INTERPRETER_PATH
AND force_python_interpreter_path
AND EXISTS "${force_python_interpreter_path}"
)
set(Python3_EXECUTABLE "${force_python_interpreter_path}")
else()
set(Python3_EXECUTABLE "")
endif()
# This needs to be unset, because the Python module checks whether the variable is defined, not
# whether it is empty.
unset(_Python3_EXECUTABLE)
if(QT_SBOM_PYTHON_INTERP)
set(Python3_ROOT_DIR ${QT_SBOM_PYTHON_INTERP})
find_package(Python3 ${required_version} ${find_quiet} COMPONENTS Interpreter)
if(python_root_dir_env_backup AND NOT QT_SBOM_NO_PYTHON_ROOT_DIR_ENV_BACKUP)
set(ENV{Python3_ROOT_DIR} "${python_root_dir_env_backup}")
endif()
find_package(Python3 ${required_version} ${find_quiet} COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
list(APPEND find_output
"Python interpreter found: '${Python3_EXECUTABLE} (version ${Python3_VERSION})'.")
else()
list(APPEND find_output "Python interpreter NOT found.")
endif()
list(JOIN find_output "\n" find_output_joined)
set(${arg_OUT_VAR_PYTHON_PATH} "${Python3_EXECUTABLE}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FOUND} "${Python3_Interpreter_FOUND}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FIND_OUTPUT} "${find_output_joined}" PARENT_SCOPE)
if(arg_OUT_VAR_PYTHON_VERSION)
set(${arg_OUT_VAR_PYTHON_VERSION} "${Python3_VERSION}" PARENT_SCOPE)
endif()
endfunction()
# Helper to retrieve the python interpreter path and directory, from either a QT_SBOM_PYTHON_INTERP
# env var or regular (cache) var, when one of them is set.
# The regular var is preferred.
# In the very first implementation, the var expected to be passed an interpreter path, rather
# than the dir where the python interpreter is located, which is what FindPython's
# Python3_ROOT_DIR expects.
# Now both cases are handled properly. So the variable name is a bit of a misnomer, but we keep
# it for compatibility.
function(_qt_internal_sbom_get_sbom_python_interp_path)
set(opt_args "")
set(single_args
OUT_VAR_FOUND
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_DIR_PATH
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
set(candidates
"${QT_SBOM_PYTHON_INTERP}"
"$ENV{QT_SBOM_PYTHON_INTERP}"
)
set(something_found FALSE)
foreach(candidate IN LISTS candidates)
if(NOT candidate OR NOT EXISTS "${candidate}")
continue()
endif()
set(something_found TRUE)
if(IS_DIRECTORY "${candidate}")
set(interp_dir "${candidate}")
set(interp_path "")
# We need to find the executable in the given directory, to be able to assign it to
# Python3_EXECUTABLE when FORCE_SET_INTERPRETER_PATH is used.
find_program(QT_INTERNAL_SBOM_PYTHON_INTERP_PATH
NAMES python3 python
HINTS "${interp_dir}"
NO_DEFAULT_PATH
)
if(QT_INTERNAL_SBOM_PYTHON_INTERP_PATH)
set(interp_path "${QT_INTERNAL_SBOM_PYTHON_INTERP_PATH}")
message(DEBUG
"Found python executable '${interp_path}' in QT_SBOM_PYTHON_INTERP candidate "
"directory: '${candidate}'.")
else()
message(DEBUG
"QT_SBOM_PYTHON_INTERP '${candidate}' is a directory, but no "
"python executable found in it.")
endif()
else()
get_filename_component(interp_dir "${candidate}" DIRECTORY)
set(interp_path "${candidate}")
endif()
break()
endforeach()
if(arg_OUT_VAR_FOUND)
set(${arg_OUT_VAR_FOUND} "${something_found}" PARENT_SCOPE)
endif()
if(arg_OUT_VAR_PYTHON_PATH)
set(${arg_OUT_VAR_PYTHON_PATH} "${interp_path}" PARENT_SCOPE)
endif()
if(arg_OUT_VAR_PYTHON_DIR_PATH)
set(${arg_OUT_VAR_PYTHON_DIR_PATH} "${interp_dir}" PARENT_SCOPE)
endif()
endfunction()
# Returns the value of Python3_ROOT_DIR if it's set, either as a cmake variable or env var.
function(_qt_internal_sbom_get_python_root_dir)
set(opt_args "")
set(single_args
OUT_VAR_FOUND
OUT_VAR_PYTHON_ROOT_DIR_PATH
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
set(candidates
"${Python3_ROOT_DIR}"
"$ENV{Python3_ROOT_DIR}"
)
set(python_root_dir "")
set(something_found FALSE)
foreach(candidate IN LISTS candidates)
if(NOT candidate OR NOT EXISTS "${candidate}")
continue()
endif()
set(something_found TRUE)
if(IS_DIRECTORY "${candidate}")
set(python_root_dir "${candidate}")
endif()
endforeach()
if(arg_OUT_VAR_FOUND)
set(${arg_OUT_VAR_FOUND} "${something_found}" PARENT_SCOPE)
endif()
if(arg_OUT_VAR_PYTHON_ROOT_DIR_PATH)
set(${arg_OUT_VAR_PYTHON_ROOT_DIR_PATH} "${python_root_dir}" PARENT_SCOPE)
endif()
endfunction()
# Helper that takes an python import statement to run using the given python interpreter path,
# to confirm that the given python dependency can be found.
# Returns whether the dependency was found and the output of running the import, for error handling.
@@ -205,11 +391,14 @@ function(_qt_internal_sbom_find_python_dependency_helper)
if("${res}" STREQUAL "0")
set(found TRUE)
set(output "${output}")
string(CONCAT output
"SBOM Python dependency '${arg_DEPENDENCY_IMPORT_STATEMENT}' found. "
"Output:\n${output}")
else()
set(found FALSE)
string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} NOT found. "
"Error:\n${output}")
string(CONCAT output
"SBOM Python dependency '${arg_DEPENDENCY_IMPORT_STATEMENT}' NOT found. "
"Output:\n${output} Exit code: ${res}")
endif()
set(${arg_OUT_VAR_FOUND} "${found}" PARENT_SCOPE)
@@ -240,13 +429,26 @@ function(_qt_internal_sbom_find_python_dependency_program)
# The path to python installed apps is different on Windows compared to UNIX, so we use
# a different path than where the python interpreter might be located.
if(QT_SBOM_PYTHON_APPS_PATH)
list(APPEND hints ${QT_SBOM_PYTHON_APPS_PATH})
list(APPEND hints "${QT_SBOM_PYTHON_APPS_PATH}")
endif()
# Sometimes the installed apps might be in the same location as the interepreter, if both are
# in a virtual env. So look also in the interpreter path.
if(QT_SBOM_PYTHON_INTERP)
list(APPEND hints ${QT_SBOM_PYTHON_INTERP})
_qt_internal_sbom_get_sbom_python_interp_path(OUT_VAR_PYTHON_DIR_PATH sbom_python_interp_dir)
if(sbom_python_interp_dir)
list(APPEND hints "${sbom_python_interp_dir}")
endif()
# If we already found a python interpreter for sbom purposes, look in its parent directory.
if(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
get_filename_component(python_interp_dir "${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE}" DIRECTORY)
list(APPEND hints "${python_interp_dir}")
endif()
# Look in the python root dir as well.
_qt_internal_sbom_get_python_root_dir(OUT_VAR_PYTHON_ROOT_DIR_PATH python_root_dir)
if(python_root_dir)
list(APPEND hints "${python_root_dir}")
endif()
find_program(${cache_var}
@@ -288,6 +288,13 @@ function(_qt_internal_sbom_get_qt_entity_repo_source_dir target)
message(FATAL_ERROR "OUT_VAR must be set")
endif()
# Interface libs don't have a source dir. Return early, to avoid errors with CMake 3.16.
get_target_property(target_type "${target}" TYPE)
if(target_type STREQUAL "INTERFACE_LIBRARY")
set(${arg_OUT_VAR} "" PARENT_SCOPE)
return()
endif()
get_target_property(repo_source_dir "${target}" SOURCE_DIR)
# Get the path relative to the PROJECT_SOURCE_DIR
@@ -1,6 +1,134 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
function(_qt_internal_normalize_project_version)
# Default if PROJECT_VERSION is missing or empty
if(NOT DEFINED PROJECT_VERSION OR PROJECT_VERSION STREQUAL "")
set(_version "1.0.0.0")
else()
set(_version "${PROJECT_VERSION}")
endif()
# Split version into components
string(REPLACE "." ";" _ver_list "${_version}")
# Ensure exactly 4 segments
list(LENGTH _ver_list _len)
while(_len LESS 4)
list(APPEND _ver_list "0")
list(LENGTH _ver_list _len)
endwhile()
# Truncate extra segments
list(SUBLIST _ver_list 0 4 _ver_list)
# Windows Application Manifests require a 4-part version (Major.Minor.Build.Revision).
# Each segment is stored as a 16-bit unsigned integer (USHORT).
# Values exceeding 65535 will cause schema validation errors in MSIX/AppX
# or 'Side-by-Side' (SxS) configuration failures in Win32 executables.
# We clamp the values here to ensure the manifest remains valid for Windows.
set(_ver_list_tmp "")
foreach(i RANGE 0 3)
list(GET _ver_list ${i} seg)
if(seg LESS 0)
message(WARNING
"Version segment '${seg}' is less than 0. Capping to 0.")
set(seg 0)
elseif(seg GREATER 65535)
message(WARNING
"Version segment '${seg}' exceeds 65535. Capping to 65535.")
set(seg 65535)
endif()
list(APPEND _ver_list_tmp ${seg})
endforeach()
list(GET _ver_list_tmp 0 _maj)
list(GET _ver_list_tmp 1 _min)
list(GET _ver_list_tmp 2 _build)
list(GET _ver_list_tmp 3 _revision)
# Recompose canonical version
set(PROJECT_VERSION
"${_maj}.${_min}.${_build}.${_revision}"
PARENT_SCOPE)
endfunction()
function(_qt_internal_finalize_windows_app target)
get_target_property(targetType "${target}" TYPE)
if(NOT "${targetType}" STREQUAL "EXECUTABLE" AND
NOT "${targetType}" STREQUAL "SHARED_LIBRARY")
return()
endif()
# If the project already specifies a custom file, we don't override it.
get_target_property(sources "${target}" SOURCES)
set(manifest_in "${sources}")
list(FILTER manifest_in INCLUDE REGEX ".*\.manifest")
if(manifest_in)
message(DEBUG "Skipping manifest magic due to user specified manifest file")
return()
endif()
set(manifest_in "${__qt_internal_cmake_windows_support_files_path}/app.exe.manifest.in")
get_target_property(output_name ${target} OUTPUT_NAME)
if(NOT output_name)
set(output_name "${target}")
endif()
string(MAKE_C_IDENTIFIER "${target}" target_identifier)
set(manifest_out_dir "${CMAKE_CURRENT_BINARY_DIR}")
set(manifest_out "${manifest_out_dir}/${output_name}.exe.manifest")
_qt_internal_normalize_project_version()
get_target_property(project_identifier "${target}" QT_WINDOWS_APP_PROJECT_IDENTIFIER)
if(NOT project_identifier)
message(DEBUG "QT_WINDOWS_APP_PROJECT_IDENTIFIER not set, "
"using com.yourcompany.${target_identifier} as fallback")
set(QT_WINDOWS_APP_PROJECT_IDENTIFIER "com.yourcompany.${target_identifier}")
else()
set(QT_WINDOWS_APP_PROJECT_IDENTIFIER "${project_identifier}")
endif()
get_target_property(project_executionlevel "${target}" QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL)
if(NOT project_executionlevel)
message(DEBUG "QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL not set, "
"using asInvoker as fallback")
set(QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL "asInvoker")
else()
# Validate value
set(_valid_execution_levels
"asInvoker"
"highestAvailable"
"requireAdministrator"
)
if(NOT project_executionlevel IN_LIST _valid_execution_levels)
message(WARNING
"Invalid QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL value: "
"'${project_executionlevel}' for target '${target}'."
"Defaulting to 'asInvoker'."
" Valid values are: asInvoker, highestAvailable, requireAdministrator."
)
set(QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL "asInvoker")
else()
set(QT_WINDOWS_APP_PROJECT_EXECUTION_LEVEL "${project_executionlevel}")
endif()
endif()
# Call configure_file to substitute Qt-specific @FOO@ values, not ${FOO} values.
configure_file(
"${manifest_in}"
"${manifest_out}"
@ONLY
)
target_sources("${target}" PRIVATE "${manifest_out}")
if(MSVC)
target_link_options(${target} PRIVATE "/MANIFEST:NO")
endif()
endfunction()
function(qt6_add_win_app_sdk target)
if(NOT MSVC)
message(WARNING
@@ -24,7 +24,7 @@ function(qt_internal_add_resource target resourceName)
cmake_parse_arguments(PARSE_ARGV 2 arg
""
"PREFIX;LANG;BASE;OUTPUT_TARGETS"
"FILES;OPTIONS")
"FILES")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_process_resource(${target} ${resourceName}
@@ -32,7 +32,6 @@ function(qt_internal_add_resource target resourceName)
LANG "${arg_LANG}"
BASE "${arg_BASE}"
FILES ${arg_FILES}
OPTIONS ${arg_OPTIONS}
OUTPUT_TARGETS out_targets
)
@@ -103,7 +103,7 @@ function(qt_internal_sbom_get_project_spdx_id out_var)
set(result "")
if(QT_GENERATE_SBOM)
get_cmake_property(project_spdx_id _qt_internal_sbom_project_spdx_id)
_qt_internal_sbom_get_current_project_spdx_id(project_spdx_id)
if(project_spdx_id)
set(result "${project_spdx_id}")
endif()
@@ -127,7 +127,7 @@ function(qt_internal_sbom_get_external_document_ref_spdx_id project_name out_var
if(QT_GENERATE_SBOM)
string(TOLOWER "${project_name}" project_name_lowercase)
_qt_internal_sbom_get_external_document_ref_spdx_id("${project_name_lowercase}" result)
_qt_internal_sbom_compute_external_document_ref_spdx_id("${project_name_lowercase}" result)
endif()
set(${out_var} "${result}" PARENT_SCOPE)
@@ -242,12 +242,12 @@ function(qt_internal_add_sbom_summary_info)
qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_CYDX_V1_6 value)
qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 suffix)
qt_configure_add_summary_entry(ARGS "Generate CyloneDX v1.6"
qt_configure_add_summary_entry(ARGS "Generate CycloneDX v1.6"
TYPE "message" MESSAGE "${value}${suffix}")
qt_internal_is_sbom_option_enabled(QT_SBOM_VERIFY_CYDX_V1_6 value)
qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 suffix)
qt_configure_add_summary_entry(ARGS "Verify CyloneDX v1.6"
qt_configure_add_summary_entry(ARGS "Verify CycloneDX v1.6"
TYPE "message" MESSAGE "${value}${suffix}")
# Python interpreter info.
@@ -682,12 +682,12 @@ function(qt_internal_add_test name)
"This is fine if OpenSSL was built statically.")
endif()
endif()
qt_internal_android_test_runner_arguments("${name}" test_executable extra_test_args)
set(test_executable "$<TARGET_PROPERTY:${name},_qt_android_runner_wrapper_path>")
list(APPEND extra_test_args "--timeout" "${android_timeout}")
set(build_environment "")
if(DEFINED ENV{QT_BUILD_ENVIRONMENT})
set(build_environment "$ENV{QT_BUILD_ENVIRONMENT}")
if(DEFINED ENV{QTEST_ENVIRONMENT})
set(build_environment "$ENV{QTEST_ENVIRONMENT}")
endif()
if(QT_ENABLE_VERBOSE_DEPLOYMENT OR build_environment STREQUAL "ci")
@@ -11,6 +11,24 @@ gccsetup --cc gcc --cxx g++ --config "$BAUHAUS_CONFIG"
cd "$CAFECC_BASEPATH"
BAUHAUS_IR_COMPRESSION=none COMPILE_ONLY=1 cmake -G Ninja -DAXIVION_ANALYSIS_TOOLCHAIN_FILE=/home/qt/bauhaus-suite/profiles/cmake/axivion-launcher-toolchain.cmake -DCMAKE_PREFIX_PATH=/home/qt/work/qt/qtbase/build -DCMAKE_PROJECT_INCLUDE_BEFORE=/home/qt/bauhaus-suite/profiles/cmake/axivion-before-project-hook.cmake -B build -S . --fresh
cmake --build build -j4
ROOT_DIR=src
MAGIC="Qt-Security score:critical"
INCLUDE_FILES=""
while IFS= read -r file; do
if [ -z "$INCLUDE_FILES" ]; then
INCLUDE_FILES="$file"
else
INCLUDE_FILES="$INCLUDE_FILES:$file"
fi
done <<EOF
$(grep -rl "$MAGIC" "$ROOT_DIR")
EOF
export INCLUDE_FILES
for MODULE in qtconcurrent qtcore qtdbus qtgui qtnetwork qtopengl qtopenglwidgets qtsql qttest qtwidgets qtprintsupport qtxml; do
export MODULE
export PLUGINS=""
@@ -118,56 +118,56 @@ instructions:
# Run CTest, with or without ignoring exit code
# FIXME: De-duplicate the CTest instruction when
# ignoreExitCode expands environment variables.
- type: Group
- type: ExecuteCommand
command: "{{.Env.TESTS_ENV_PREFIX}} ctest {{.Env.CTEST_ARGS}}"
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: false
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 14400
userMessageOnFailure: >
Failed to run tests.
enable_if:
condition: runtime
env_var: COIN_CTEST_IGNORE_EXIT_CODE
not_equals_value: "1"
instructions:
- type: ExecuteCommand
command: "{{.Env.TESTS_ENV_PREFIX}} ctest {{.Env.CTEST_ARGS}}"
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: false
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 14400
userMessageOnFailure: >
Failed to run tests.
- type: ExecuteCommand
command: >
cmake -E copy
Testing{{.Env.CI_PATH_SEP}}Temporary{{.Env.CI_PATH_SEP}}LastTest.log
{{.Env.COIN_CTEST_RESULTSDIR}}{{.Env.CI_PATH_SEP}}CTest.log
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: false
userMessageOnFailure: >
Failed to copy LastTest.log to testresults directory.
executeOn: always
maxTimeInSeconds: 20
maxTimeBetweenOutput: 20
- type: Group
- type: ExecuteCommand
command: "{{.Env.TESTS_ENV_PREFIX}} ctest {{.Env.CTEST_ARGS}}"
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: true
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 14400
userMessageOnFailure: >
Failed to run tests.
enable_if:
condition: runtime
env_var: COIN_CTEST_IGNORE_EXIT_CODE
equals_value: "1"
instructions:
- type: ExecuteCommand
command: "{{.Env.TESTS_ENV_PREFIX}} ctest {{.Env.CTEST_ARGS}}"
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: true
maxTimeInSeconds: 14400
maxTimeBetweenOutput: 14400
userMessageOnFailure: >
Failed to run tests.
- type: ExecuteCommand
command: >
cmake -E copy
Testing{{.Env.CI_PATH_SEP}}Temporary{{.Env.CI_PATH_SEP}}LastTest.log
{{.Env.COIN_CTEST_RESULTSDIR}}{{.Env.CI_PATH_SEP}}CTest.log
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
ignoreExitCode: true
userMessageOnFailure: >
Failed to copy LastTest.log to testresults directory.
executeOn: always
- type: ExecuteCommand
instructionName: "Copy ctest log"
command: >
cmake -E copy
Testing{{.Env.CI_PATH_SEP}}Temporary{{.Env.CI_PATH_SEP}}LastTest.log
{{.Env.COIN_CTEST_RESULTSDIR}}
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
userMessageOnFailure: >
Failed to copy LastTest.log to testresults directory.
executeOn: always
maxTimeInSeconds: 60
maxTimeBetweenOutput: 60
- type: ExecuteCommand
command: >
cmake -E copy
Testing{{.Env.CI_PATH_SEP}}Temporary{{.Env.CI_PATH_SEP}}LastTest.log.tmp
{{.Env.COIN_CTEST_RESULTSDIR}}
executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution
userMessageOnFailure: >
Failed to copy LastTest.log.tmp to testresults directory.
executeOn: failure
executeOnTarget: Copy ctest log
resetError: target
maxTimeInSeconds: 60
maxTimeBetweenOutput: 60
- type: Group
instructions:
@@ -310,7 +310,13 @@ instructions:
condition: property
property: host.compiler
equals_value: MSVC2022
- type: EnvironmentVariable
variableName: VC_SCRIPT
variableValue: "%ProgramFiles%\\Microsoft Visual Studio\\18\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat"
enable_if:
condition: property
property: host.compiler
equals_value: MSVC2026
# With MSVC we need setup the environment before every subprocess call, the group below creates a script that
# does it. It is enough to prepand it to every call (it is safe to add it even on other OSes)
@@ -555,8 +561,12 @@ instructions:
variableValue: >-
-DQT_GENERATE_SBOM=ON
-DQT_GENERATE_SOURCE_SBOM=ON
-DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON
-DQT_SBOM_VERIFY_SPDX_V2=ON
-DQT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON=ON
-DQT_SBOM_REQUIRE_VERIFY_SPDX_V2=ON
-DQT_SBOM_VERIFY_CYDX_V1_6=ON
-DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON
-DQT_SBOM_REQUIRE_VERIFY_CYDX_V1_6=ON
- type: AppendToEnvironmentVariable
variableName: COMMON_CMAKE_ARGS
variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
@@ -1306,12 +1306,17 @@ qt_feature("coverage"
CONDITION QT_FEATURE_coverage_gcov
)
qt_feature("android_16kb_pages"
# Mark the feature as PRIVATE because we want it to be exported, but we don't want modifications
# in the public qconfig.h header in a patch release.
qt_feature("android_16kb_pages" PRIVATE
LABEL "Using 16KB page sizes in Android"
CONDITION ANDROID AND (((CMAKE_ANDROID_NDK_VERSION VERSION_GREATER_EQUAL "25.0.0"))
AND ((CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a") OR
(CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64")))
)
# The feature is saved into the public QT_CONFIG var in the qt_lib_core.pri file, to ensure it's
# available automatically for testing with qtConfig() in all user projects.
qt_feature_config("android_16kb_pages" QMAKE_PUBLIC_QT_CONFIG)
qt_configure_add_summary_build_type_and_config()
qt_configure_add_summary_section(NAME "Build options")
@@ -1,21 +1,25 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\externalpage http://qt.io/
\externalpage https://qt.io/
\title Qt Homepage
*/
/*!
\externalpage http://bugreports.qt.io
\externalpage https://qt-project.atlassian.net/jira/
\title Qt Bug Tracker
*/
/*!
\externalpage http://qt.io/download
\externalpage https://qt.io/download
\title Downloads
*/
/*!
\externalpage http://www.qt.io/about-us/
\externalpage https://www.qt.io/about-us/
\title The Qt Company
*/
/*!
\externalpage https://www.qt.io/about-us/
\title The Qt Group
*/
/*!
\externalpage https://code.qt.io/
\title Public Qt Repository
@@ -57,7 +57,6 @@ macro.examplecategory = "\\meta category {\1}\n\\ingroup category \1"
macro.B2Q = "Boot to Qt"
macro.B2QSS = "Boot to Qt Software Stack"
macro.B2QST = "Boot to Qt Startup Screen"
macro.IFW = "Qt Installer Framework"
macro.Q4A = "Qt for Android"
macro.QA = "Qt Assistant"
@@ -81,6 +80,7 @@ macro.QOI = "Qt Online Installer"
macro.QQEM = "Qt Quick Effect Maker"
macro.QQV = "Qt QML Viewer"
macro.QtAA = "Qt for Android Automotive"
macro.QTOB = "Qt Onboard"
macro.QUL = "Qt Quick Ultralite"
macro.QtTAS = "Qt Tools for Android Studio"
@@ -16,7 +16,6 @@
android:smallScreens="true" />
<application
android:name="org.qtproject.qt.android.bindings.QtApplication"
android:extractNativeLibs="true"
android:hardwareAccelerated="true"
android:label="Qt Notifier"
android:requestLegacyExternalStorage="true"
@@ -92,7 +92,7 @@
\snippet serialization/savegame/level.h 0
We want the levels in our game to each each have several NPCs, so we keep a QList
We want the levels in our game to each have several NPCs, so we keep a QList
of Character objects. We also provide the familiar fromJson() and toJson()
functions.
@@ -10,5 +10,6 @@
\brief Demonstrates how to receive information sent to a multicast group.
This example demonstrates how to receive messages sent to a multicast group.
\image multicastreceiver-example.webp
\image multicastreceiver-example.webp {Received IPv4 and IPv6 multicast
datagrams with Quit button}
*/
@@ -13,7 +13,8 @@
classes to download a collection of images from the network and scale them,
without blocking the UI.
\image imagescaling.webp
\image imagescaling.webp {Downloaded images in a grid with Add URLs,
Cancel buttons and Finished status}
The application consists of the following steps:
@@ -18,7 +18,8 @@
prime numbers within the list and display the total count of prime numbers
found.
\image primecounter.png
\image primecounter.png {Prime counter with reduce option selector,
Start button, progress bars, and prime count result}
\include examples-run.qdocinc
@@ -36,6 +36,11 @@
an instance of \c TableModel to add, edit and remove entries to
the address book.
\c{struct Contact} stores a contact and provides
comparison and serialization via QDataStream:
\snippet itemviews/addressbook/contact.h 0
\c TableModel is a subclass of QAbstractTableModel that provides
the standard model/view API to access data. It holds a list of
added contacts.
@@ -299,18 +304,10 @@
The constructor instantiates the \c addButton,
\c descriptionLabel and connects the \c{addButton}'s signal to
the \c{addEntry()} slot.
the \c{triggered()} signal.
\snippet itemviews/addressbook/newaddresstab.cpp 0
The \c addEntry() function is similar to \c AddressWidget's
\c addEntry() in the sense that both functions instantiate an
\c AddDialog object. Data from the dialog is extracted and sent
to \c AddressWidget's \c addEntry() slot by emitting the
\c sendDetails() signal.
\snippet itemviews/addressbook/newaddresstab.cpp 1
\image signals-n-slots-aw-nat.png {Function calls for adding a new contact}
@@ -6,8 +6,8 @@
\title Combo Widget Mapper Example
\examplecategory {User Interface Components}
\ingroup examples-itemviews
\brief The Combo Widget Mapper example shows how to use a custom delegate to
map information from a model to specific widgets on a form.
\brief The Combo Widget Mapper example shows how to use a QDataWidgetMapper
to map information from a model to specific widgets on a form.
\image combowidgetmapper-example.png
{Application with various fields such as Name, Address, and Type}
@@ -17,29 +17,44 @@
can be entered, we provide a combo box to allow their addresses to be
classified as "Home", "Work" or "Other".
\section1 Person Class Definition
We define a struct \c{Person} containing name, address and address type
to represent the model data:
\snippet itemviews/combowidgetmapper/person.h Person
It is declared to be a Q_GADGET with the members to be properties
of the same name to be able to conveniently populate a QRangeModel.
\section1 Window Class Definition
The class provides a constructor, a slot to keep the buttons up to date,
and a private function to set up the model:
The class provides a constructor and a slot to keep the buttons up to date:
\snippet itemviews/combowidgetmapper/window.h Window definition
In addition to the QDataWidgetMapper object and the controls used to make
up the user interface, we use a QStandardItemModel to hold our data and
a QStringListModel to hold information about the types of address that
can be applied to each person's data.
up the user interface, we use a QList<Person> to hold our data
and a QRangeModel operating on it.
\section1 Window Class Implementation
The constructor of the \c Window class can be explained in three parts.
The constructor of the \c Window class can be explained in several parts.
In the first part, we set up the widgets used for the user interface:
\snippet itemviews/combowidgetmapper/window.cpp Set up widgets
The person list is populated using an initalizer list, which is then
passed to the QRangeModel:
\snippet itemviews/combowidgetmapper/window.cpp Set up the model
Note that we set up the mapping the combo box in the same way as for other
widgets, but that we apply its own model to it so that it will display
data from its own model, the \c typeModel, rather than from the model
containing data about each person.
widgets, but that we apply its own model holding information about the
types of address to it, so that it will display the address type string
rather than the data from the model containing data about each person:
\snippet itemviews/combowidgetmapper/window.cpp Set up type combo box
Next, we set up the widget mapper, relating each input widget to a column
in the model specified by the call to \l{QDataWidgetMapper::}{setModel()}:
@@ -64,20 +79,6 @@
\snippet itemviews/combowidgetmapper/window.cpp Set up connections and layouts
The model is initialized in the window's \c{setupModel()} function. Here,
we create a standard model with 5 rows and 3 columns. In each row, we
insert a name, address, and a value that indicates the type of address.
The address types are stored in a string list model.
\snippet itemviews/combowidgetmapper/window.cpp Set up the model
As we insert each row into the model, like a record in a database, we
store values that correspond to items in \c typeModel for each person's
address type. When the widget mapper reads these values from the final
column of each row, it will need to use them as references to values in
\c typeModel, as shown in the following diagram. This is where the
delegate is used.
\image widgetmapper-combo-mapping.png
{Mapping of the model to the view using a delegate}
@@ -189,13 +189,13 @@
data structure:
\snippet itemviews/customsortfiltermodel/window.cpp 3
\snippet itemviews/customsortfiltermodel/window.cpp 4
Note that whenever the user changes one of the filtering options,
we must explicitly reapply the filter. This is done by connecting
the various editors to functions that update the proxy model.
\snippet itemviews/customsortfiltermodel/window.cpp 5
\snippet itemviews/customsortfiltermodel/window.cpp 4
The sorting will be handled by the view. All we have to do is to
enable sorting for our proxy view by setting the
@@ -203,12 +203,14 @@
default). Then we add all the filtering widgets and the proxy view
to a layout that we install on a corresponding group box.
\snippet itemviews/customsortfiltermodel/window.cpp 6
\snippet itemviews/customsortfiltermodel/window.cpp 5
Finally, after putting our two group boxes into another layout
that we install on our main application widget, we customize the
application window.
\snippet itemviews/customsortfiltermodel/window.cpp 6
As mentioned above, we create the source model in the \c main ()
function, calling the \c Window::setSourceModel() function to make
the application use it:
@@ -253,14 +255,15 @@
model by creating the model in the \c main () function. First we
create the application, then we create the source model:
\snippet itemviews/customsortfiltermodel/main.cpp 0
\snippet itemviews/customsortfiltermodel/main.cpp 1
The \c createMailModel() function is a convenience function
provided to simplify the constructor. All it does is to create and
return a model describing a collection of emails. The model is an
instance of the QStandardItemModel class, i.e., a generic model
for storing custom data typically used as a repository for
standard Qt data types. Each mail description is added to the
model using \c addMail(), another convenience function. See \c
{itemviews/customsortfiltermodel/main.cpp} for details.
We create a class \c MailModel, which inherits QRangeModel and
reimplements QAbstractItemView::headerData() to return the correct section
headings. It is populated from an array of struct \c MailHeader.
\c MailHeader is declared to be a \c Q_GADGET with properties to be used
by QRangeModel:
\snippet itemviews/customsortfiltermodel/mailheader.h 0
\snippet itemviews/customsortfiltermodel/main.cpp 0
*/
@@ -153,6 +153,10 @@ WidgetGallery::WidgetGallery(QWidget *parent)
QGuiApplication::styleHints()->setColorScheme(static_cast<Qt::ColorScheme>(index));
});
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, [colorSchemeComboBox](Qt::ColorScheme colorScheme) {
colorSchemeComboBox->setCurrentIndex(static_cast<int>(colorScheme));
});
const QKeySequence helpKeySequence(QKeySequence::HelpContents);
const QString helpText = tr("Press <kbd>%1</kbd> over a widget to see Documentation")
.arg(helpKeySequence.toString(QKeySequence::NativeText));
@@ -11,6 +11,7 @@ qt_standard_project_setup()
qt_add_executable(addressbook
adddialog.cpp adddialog.h
addresswidget.cpp addresswidget.h
contact.h
main.cpp
mainwindow.cpp mainwindow.h
newaddresstab.cpp newaddresstab.h
@@ -2,58 +2,63 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "adddialog.h"
#include "contact.h"
#include <QtWidgets>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPushButton>
//! [0]
AddDialog::AddDialog(QWidget *parent)
: QDialog(parent),
buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this)),
nameText(new QLineEdit),
addressText(new QTextEdit)
addressText(new QPlainTextEdit)
{
auto nameLabel = new QLabel(tr("Name"));
auto addressLabel = new QLabel(tr("Address"));
auto okButton = new QPushButton(tr("OK"));
auto cancelButton = new QPushButton(tr("Cancel"));
auto *formLayout = new QFormLayout;
formLayout->addRow(tr("Name"), nameText);
formLayout->addRow(tr("Address"), addressText);
auto gLayout = new QGridLayout;
gLayout->setColumnStretch(1, 2);
gLayout->addWidget(nameLabel, 0, 0);
gLayout->addWidget(nameText, 0, 1);
auto *mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(formLayout);
mainLayout->addWidget(buttonBox);
gLayout->addWidget(addressLabel, 1, 0, Qt::AlignLeft|Qt::AlignTop);
gLayout->addWidget(addressText, 1, 1, Qt::AlignLeft);
auto buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
gLayout->addLayout(buttonLayout, 2, 1, Qt::AlignRight);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(gLayout);
setLayout(mainLayout);
connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept);
connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(nameText, &QLineEdit::textChanged, this, &AddDialog::updateEnabled);
connect(addressText, &QPlainTextEdit::textChanged, this, &AddDialog::updateEnabled);
setWindowTitle(tr("Add a Contact"));
updateEnabled();
}
QString AddDialog::name() const
void AddDialog::updateEnabled()
{
return nameText->text();
Contact c = contact();
const bool valid = !c.name.isEmpty() && c.name.front().isLetter() && !c.address.isEmpty();
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid);
}
QString AddDialog::address() const
Contact AddDialog::contact() const
{
return addressText->toPlainText();
return { nameText->text().trimmed(), addressText->toPlainText().trimmed() };
}
void AddDialog::editAddress(const QString &name, const QString &address)
void AddDialog::setContact(const Contact &c)
{
nameText->setText(c.name);
addressText->setPlainText(c.address);
updateEnabled();
}
void AddDialog::editAddress(const Contact &c)
{
nameText->setReadOnly(true);
nameText->setText(name);
addressText->setPlainText(address);
setContact(c);
}
//! [0]
@@ -7,27 +7,34 @@
#include <QDialog>
QT_BEGIN_NAMESPACE
class QDialogButtonBox;
class QLabel;
class QPushButton;
class QTextEdit;
class QPlainTextEdit;
class QLineEdit;
QT_END_NAMESPACE
struct Contact;
//! [0]
class AddDialog : public QDialog
{
Q_OBJECT
public:
AddDialog(QWidget *parent = nullptr);
explicit AddDialog(QWidget *parent = nullptr);
QString name() const;
QString address() const;
void editAddress(const QString &name, const QString &address);
Contact contact() const;
void setContact(const Contact &c);
void editAddress(const Contact &c);
private slots:
void updateEnabled();
private:
QDialogButtonBox *buttonBox;
QLineEdit *nameText;
QTextEdit *addressText;
QPlainTextEdit *addressText;
};
//! [0]
@@ -9,6 +9,7 @@ SOURCES = adddialog.cpp \
tablemodel.cpp
HEADERS = adddialog.h \
addresswidget.h \
contact.h \
mainwindow.h \
newaddresstab.h \
tablemodel.h
@@ -3,8 +3,33 @@
#include "addresswidget.h"
#include "adddialog.h"
#include "newaddresstab.h"
#include "tablemodel.h"
#include <QtWidgets>
#include <QHeaderView>
#include <QMessageBox>
#include <QTableView>
#include <QDataStream>
#include <QDir>
#include <QFile>
#include <QItemSelection>
#include <QItemSelectionModel>
#include <QModelIndex>
#include <QRegularExpression>
#include <QSortFilterProxyModel>
#include <QStandardPaths>
#include <algorithm>
using namespace Qt::StringLiterals;
QString AddressWidget::fileName()
{
static const QString result = QStandardPaths::standardLocations(QStandardPaths::TempLocation).value(0)
+ "/addressbook.dat"_L1;
return result;
}
//! [0]
AddressWidget::AddressWidget(QWidget *parent)
@@ -12,8 +37,7 @@ AddressWidget::AddressWidget(QWidget *parent)
table(new TableModel(this)),
newAddressTab(new NewAddressTab(this))
{
connect(newAddressTab, &NewAddressTab::sendDetails,
this, &AddressWidget::addEntry);
connect(newAddressTab, &NewAddressTab::triggered, this, &AddressWidget::showAddEntryDialog);
addTab(newAddressTab, tr("Address Book"));
@@ -24,30 +48,33 @@ AddressWidget::AddressWidget(QWidget *parent)
//! [2]
void AddressWidget::showAddEntryDialog()
{
AddDialog aDialog;
AddDialog aDialog(this);
if (aDialog.exec())
addEntry(aDialog.name(), aDialog.address());
if (aDialog.exec() == QDialog::Accepted)
addEntry(aDialog.contact());
}
//! [2]
//! [3]
void AddressWidget::addEntry(const QString &name, const QString &address)
void AddressWidget::addEntry(const Contact &contact)
{
if (!name.front().isLetter()) {
QMessageBox::information(this, tr("Invalid name"),
tr("The name must start with a letter."));
} else if (!table->getContacts().contains({ name, address })) {
table->insertRows(0, 1, QModelIndex());
QModelIndex index = table->index(0, 0, QModelIndex());
table->setData(index, name, Qt::EditRole);
index = table->index(0, 1, QModelIndex());
table->setData(index, address, Qt::EditRole);
removeTab(indexOf(newAddressTab));
} else {
if (table->getContacts().contains(contact)) {
QMessageBox::information(this, tr("Duplicate Name"),
tr("The name \"%1\" already exists.").arg(name));
tr("The name \"%1\" already exists.").arg(contact.name));
return;
}
table->insertRows(0, 1);
table->setData(table->index(0, 0), contact.name, Qt::EditRole);
table->setData(table->index(0, 1), contact.address, Qt::EditRole);
removeTab(indexOf(newAddressTab));
const QChar firstChar = contact.name.at(0).toUpper();
for (int t = 0, tabCount = count(); t < tabCount; ++t) {
if (tabText(t).contains(firstChar)) {
setCurrentIndex(t);
break;
}
}
}
//! [3]
@@ -55,37 +82,35 @@ void AddressWidget::addEntry(const QString &name, const QString &address)
//! [4a]
void AddressWidget::editEntry()
{
QTableView *temp = static_cast<QTableView*>(currentWidget());
QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
QItemSelectionModel *selectionModel = temp->selectionModel();
auto *tableView = static_cast<QTableView *>(currentWidget());
auto *proxy = static_cast<QSortFilterProxyModel *>(tableView->model());
QItemSelectionModel *selectionModel = tableView->selectionModel();
const QModelIndexList indexes = selectionModel->selectedRows();
QString name;
QString address;
int row = -1;
if (indexes.isEmpty())
return;
for (const QModelIndex &index : indexes) {
row = proxy->mapToSource(index).row();
QModelIndex nameIndex = table->index(row, 0, QModelIndex());
QVariant varName = table->data(nameIndex, Qt::DisplayRole);
name = varName.toString();
const int row = proxy->mapToSource(indexes.constFirst()).row();
QModelIndex addressIndex = table->index(row, 1, QModelIndex());
QVariant varAddr = table->data(addressIndex, Qt::DisplayRole);
address = varAddr.toString();
}
Contact contact;
QModelIndex nameIndex = table->index(row, 0);
QVariant varName = table->data(nameIndex, Qt::DisplayRole);
contact.name = varName.toString();
QModelIndex addressIndex = table->index(row, 1);
QVariant varAddr = table->data(addressIndex, Qt::DisplayRole);
contact.address = varAddr.toString();
//! [4a]
//! [4b]
AddDialog aDialog;
AddDialog aDialog(this);
aDialog.setWindowTitle(tr("Edit a Contact"));
aDialog.editAddress(name, address);
aDialog.editAddress(contact);
if (aDialog.exec()) {
const QString newAddress = aDialog.address();
if (newAddress != address) {
const QModelIndex index = table->index(row, 1, QModelIndex());
table->setData(index, newAddress, Qt::EditRole);
if (aDialog.exec() == QDialog::Accepted) {
const Contact newContact = aDialog.contact();
if (newContact != contact) {
const QModelIndex index = table->index(row, 1);
table->setData(index, newContact.address, Qt::EditRole);
}
}
}
@@ -94,18 +119,18 @@ void AddressWidget::editEntry()
//! [5]
void AddressWidget::removeEntry()
{
QTableView *temp = static_cast<QTableView*>(currentWidget());
QSortFilterProxyModel *proxy = static_cast<QSortFilterProxyModel*>(temp->model());
QItemSelectionModel *selectionModel = temp->selectionModel();
auto *tableView = static_cast<QTableView *>(currentWidget());
auto *proxy = static_cast<QSortFilterProxyModel *>(tableView->model());
QItemSelectionModel *selectionModel = tableView->selectionModel();
const QModelIndexList indexes = selectionModel->selectedRows();
if (indexes.isEmpty())
return;
for (QModelIndex index : indexes) {
int row = proxy->mapToSource(index).row();
table->removeRows(row, 1, QModelIndex());
}
const int row = proxy->mapToSource(indexes.constFirst()).row();
table->removeRows(row, 1);
if (table->rowCount(QModelIndex()) == 0)
if (table->rowCount() == 0)
insertTab(0, newAddressTab, tr("Address Book"));
}
//! [5]
@@ -113,7 +138,6 @@ void AddressWidget::removeEntry()
//! [1]
void AddressWidget::setupTabs()
{
using namespace Qt::StringLiterals;
const auto groups = { "ABC"_L1, "DEF"_L1, "GHI"_L1, "JKL"_L1, "MNO"_L1, "PQR"_L1,
"STU"_L1, "VW"_L1, "XYZ"_L1 };
@@ -121,12 +145,12 @@ void AddressWidget::setupTabs()
const auto regExp = QRegularExpression(QLatin1StringView("^[%1].*").arg(str),
QRegularExpression::CaseInsensitiveOption);
auto proxyModel = new QSortFilterProxyModel(this);
auto *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(table);
proxyModel->setFilterRegularExpression(regExp);
proxyModel->setFilterKeyColumn(0);
QTableView *tableView = new QTableView;
auto *tableView = new QTableView;
tableView->setModel(proxyModel);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->horizontalHeader()->setStretchLastSection(true);
@@ -149,14 +173,15 @@ void AddressWidget::setupTabs()
//! [1]
//! [7]
void AddressWidget::readFromFile()
bool AddressWidget::readFromFile()
{
QFile file(fileName);
QFile file(fileName());
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::information(this, tr("Unable to open file"),
file.errorString());
return;
tr("Cannot open %1: %2").arg(QDir::toNativeSeparators(fileName()),
file.errorString()));
return false;
}
QList<Contact> contacts;
@@ -166,24 +191,32 @@ void AddressWidget::readFromFile()
if (contacts.isEmpty()) {
QMessageBox::information(this, tr("No contacts in file"),
tr("The file you are attempting to open contains no contacts."));
} else {
for (const auto &contact: std::as_const(contacts))
addEntry(contact.name, contact.address);
}
for (const auto &contact : std::as_const(contacts))
addEntry(contact);
return true;
}
//! [7]
//! [6]
void AddressWidget::writeToFile()
bool AddressWidget::writeToFile()
{
QFile file(fileName);
QFile file(fileName());
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::information(this, tr("Unable to open file"), file.errorString());
return;
QMessageBox::information(this, tr("Unable to open file"),
tr("Cannot write to %1: %2").arg(QDir::toNativeSeparators(fileName()),
file.errorString()));
return false;
}
auto sortedContacts = table->getContacts();
std::sort(sortedContacts.begin(), sortedContacts.end());
QDataStream out(&file);
out << table->getContacts();
out << sortedContacts;
return true;
}
//! [6]
@@ -4,31 +4,33 @@
#ifndef ADDRESSWIDGET_H
#define ADDRESSWIDGET_H
#include "newaddresstab.h"
#include "tablemodel.h"
#include <QItemSelection>
#include <QTabWidget>
#include <QStandardPaths>
QT_BEGIN_NAMESPACE
class QSortFilterProxyModel;
class QItemSelectionModel;
class QItemSelection;
QT_END_NAMESPACE
struct Contact;
class NewAddressTab;
class TableModel;
//! [0]
class AddressWidget : public QTabWidget
{
Q_OBJECT
public:
AddressWidget(QWidget *parent = nullptr);
void readFromFile();
void writeToFile();
explicit AddressWidget(QWidget *parent = nullptr);
bool readFromFile();
bool writeToFile();
static QString fileName();
public slots:
void showAddEntryDialog();
void addEntry(const QString &name, const QString &address);
void addEntry(const Contact &contact);
void editEntry();
void removeEntry();
@@ -38,9 +40,6 @@ signals:
private:
void setupTabs();
inline static QString fileName =
QStandardPaths::standardLocations(QStandardPaths::TempLocation).value(0)
+ QStringLiteral("/addressbook.dat");
TableModel *table;
NewAddressTab *newAddressTab;
};
@@ -5,12 +5,18 @@
#include <QApplication>
#include <QScreen>
#include <QRect>
//! [0]
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mw;
const auto availableGeometry = mw.screen()->availableGeometry();
mw.resize(availableGeometry.width() / 3, availableGeometry.height() / 3);
mw.show();
return app.exec();
return QCoreApplication::exec();
}
//! [0]
@@ -6,6 +6,14 @@
#include <QAction>
#include <QFileDialog>
#include <QMenuBar>
#include <QStatusBar>
#include <QIcon>
#include <QKeySequence>
#include <QDir>
#include <QItemSelection>
#include <QModelIndexList>
//! [0]
MainWindow::MainWindow()
@@ -23,24 +31,31 @@ void MainWindow::createMenus()
{
QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
QAction *openAct = new QAction(tr("&Open..."), this);
auto *openAct = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpen),
tr("&Open..."), this);
openAct->setShortcut(QKeySequence(QKeySequence::Open));
fileMenu->addAction(openAct);
connect(openAct, &QAction::triggered, this, &MainWindow::openFile);
//! [1a]
QAction *saveAct = new QAction(tr("&Save"), this);
auto *saveAct = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSave),
tr("&Save"), this);
saveAct->setShortcut(QKeySequence(QKeySequence::Save));
fileMenu->addAction(saveAct);
connect(saveAct, &QAction::triggered, this, &MainWindow::saveFile);
fileMenu->addSeparator();
QAction *exitAct = new QAction(tr("E&xit"), this);
auto *exitAct = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit),
tr("E&xit"), this);
exitAct->setShortcut(QKeySequence(QKeySequence::Quit));
fileMenu->addAction(exitAct);
connect(exitAct, &QAction::triggered, this, &QWidget::close);
QMenu *toolMenu = menuBar()->addMenu(tr("&Tools"));
QAction *addAct = new QAction(tr("&Add Entry..."), this);
auto *addAct = new QAction(tr("&Add Entry..."), this);
addAct->setShortcut(QKeySequence(Qt::ControlModifier | Qt::Key_A));
toolMenu->addAction(addAct);
connect(addAct, &QAction::triggered,
addressWidget, &AddressWidget::showAddEntryDialog);
@@ -59,35 +74,32 @@ void MainWindow::createMenus()
connect(removeAct, &QAction::triggered, addressWidget, &AddressWidget::removeEntry);
connect(addressWidget, &AddressWidget::selectionChanged,
this, &MainWindow::updateActions);
this, &MainWindow::updateActions);
}
//! [1b]
//! [2]
void MainWindow::openFile()
{
addressWidget->readFromFile();
if (addressWidget->readFromFile())
statusBar()->showMessage(tr("Read %1").arg(QDir::toNativeSeparators(AddressWidget::fileName())));
}
//! [2]
//! [3]
void MainWindow::saveFile()
{
addressWidget->writeToFile();
if (addressWidget->writeToFile())
statusBar()->showMessage(tr("Wrote %1").arg(QDir::toNativeSeparators(AddressWidget::fileName())));
}
//! [3]
//! [4]
void MainWindow::updateActions(const QItemSelection &selection)
{
QModelIndexList indexes = selection.indexes();
const QModelIndexList indexes = selection.indexes();
if (!indexes.isEmpty()) {
removeAct->setEnabled(true);
editAct->setEnabled(true);
} else {
removeAct->setEnabled(false);
editAct->setEnabled(false);
}
removeAct->setEnabled(!indexes.isEmpty());
editAct->setEnabled(!indexes.isEmpty());
}
//! [4]
@@ -2,35 +2,24 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "newaddresstab.h"
#include "adddialog.h"
#include <QtWidgets>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
//! [0]
NewAddressTab::NewAddressTab(QWidget *parent)
: QWidget(parent)
{
auto descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. "
"\nClick Add to add new contacts."));
auto *descriptionLabel = new QLabel(tr("There are currently no contacts in your address book. "
"\nClick Add to add new contacts."));
auto addButton = new QPushButton(tr("Add"));
auto *addButton = new QPushButton(tr("Add"));
connect(addButton, &QAbstractButton::clicked, this, &NewAddressTab::addEntry);
connect(addButton, &QAbstractButton::clicked, this, &NewAddressTab::triggered);
auto mainLayout = new QVBoxLayout;
mainLayout->addWidget(descriptionLabel);
auto *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(descriptionLabel, 0, Qt::AlignCenter);
mainLayout->addWidget(addButton, 0, Qt::AlignCenter);
setLayout(mainLayout);
}
//! [0]
//! [1]
void NewAddressTab::addEntry()
{
AddDialog aDialog;
if (aDialog.exec())
emit sendDetails(aDialog.name(), aDialog.address());
}
//! [1]
@@ -6,25 +6,16 @@
#include <QWidget>
QT_BEGIN_NAMESPACE
class QLabel;
class QPushButton;
class QVBoxLayout;
QT_END_NAMESPACE
//! [0]
class NewAddressTab : public QWidget
{
Q_OBJECT
public:
NewAddressTab(QWidget *parent = nullptr);
public slots:
void addEntry();
explicit NewAddressTab(QWidget *parent = nullptr);
signals:
void sendDetails(const QString &name, const QString &address);
void triggered();
};
//! [0]
@@ -31,10 +31,10 @@ int TableModel::columnCount(const QModelIndex &parent) const
QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
return {};
if (index.row() >= contacts.size() || index.row() < 0)
return QVariant();
return {};
if (role == Qt::DisplayRole) {
const auto &contact = contacts.at(index.row());
@@ -48,17 +48,14 @@ QVariant TableModel::data(const QModelIndex &index, int role) const
break;
}
}
return QVariant();
return {};
}
//! [2]
//! [3]
QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal) {
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return tr("Name");
@@ -68,7 +65,7 @@ QVariant TableModel::headerData(int section, Qt::Orientation orientation, int ro
break;
}
}
return QVariant();
return {};
}
//! [3]
@@ -4,48 +4,29 @@
#ifndef TABLEMODEL_H
#define TABLEMODEL_H
#include "contact.h"
#include <QAbstractTableModel>
#include <QList>
//! [0]
struct Contact
{
QString name;
QString address;
bool operator==(const Contact &other) const
{
return name == other.name && address == other.address;
}
};
inline QDataStream &operator<<(QDataStream &stream, const Contact &contact)
{
return stream << contact.name << contact.address;
}
inline QDataStream &operator>>(QDataStream &stream, Contact &contact)
{
return stream >> contact.name >> contact.address;
}
class TableModel : public QAbstractTableModel
{
Q_OBJECT
public:
TableModel(QObject *parent = nullptr);
explicit TableModel(QObject *parent = nullptr);
TableModel(const QList<Contact> &contacts, QObject *parent = nullptr);
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool insertRows(int position, int rows, const QModelIndex &index = {}) override;
bool removeRows(int position, int rows, const QModelIndex &index = {}) override;
const QList<Contact> &getContacts() const;
private:
@@ -10,7 +10,7 @@ qt_standard_project_setup()
qt_add_executable(basicsortfiltermodel
main.cpp
window.cpp window.h
window.cpp window.h mailheader.h
)
set_target_properties(basicsortfiltermodel PROPERTIES
@@ -1,7 +1,8 @@
QT += widgets
requires(qtConfig(combobox))
HEADERS = window.h
HEADERS = window.h \
mailheader.h
SOURCES = main.cpp \
window.cpp
@@ -1,58 +1,72 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mailheader.h"
#include "window.h"
#include <QApplication>
#include <QStandardItemModel>
#include <QTime>
#include <QtWidgets/QApplication>
void addMail(QAbstractItemModel *model, const QString &subject,
const QString &sender, const QDateTime &date)
#include <QtCore/QRangeModel>
#include <array>
using namespace Qt::StringLiterals;
class MailModel : public QRangeModel
{
model->insertRow(0);
model->setData(model->index(0, 0), subject);
model->setData(model->index(0, 1), sender);
model->setData(model->index(0, 2), date);
}
public:
explicit MailModel(QObject *parent = nullptr) : QRangeModel(mails, parent) {}
QAbstractItemModel *createMailModel(QObject *parent)
{
QStandardItemModel *model = new QStandardItemModel(0, 3, parent);
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return Window::tr("Subject");
case 1:
return Window::tr("Sender");
case 2:
return Window::tr("Date");
default:
break;
}
}
return QRangeModel::headerData(section, orientation, role);
}
model->setHeaderData(0, Qt::Horizontal, QObject::tr("Subject"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Sender"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Date"));
private:
static const std::array<MailHeader, 10> mails;
};
addMail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
QDateTime(QDate(2006, 12, 31), QTime(17, 03)));
addMail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
QDateTime(QDate(2006, 12, 22), QTime(9, 44)));
addMail(model, "Accounts", "pascale@nospam.com",
QDateTime(QDate(2006, 12, 31), QTime(12, 50)));
addMail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
QDateTime(QDate(2006, 12, 25), QTime(11, 39)));
addMail(model, "Re: Expenses", "Andy <andy@nospam.com>",
QDateTime(QDate(2007, 01, 02), QTime(16, 05)));
addMail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
QDateTime(QDate(2007, 01, 03), QTime(14, 18)));
addMail(model, "Re: Accounts", "Andy <andy@nospam.com>",
QDateTime(QDate(2007, 01, 03), QTime(14, 26)));
addMail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(11, 33)));
addMail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(12, 00)));
addMail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(12, 01)));
return model;
}
const std::array<MailHeader, 10> MailModel::mails = {
MailHeader{ u"RE: Sports"_s, u"Petra Schmidt <petras@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(12, 01)) },
MailHeader{ u"AW: Sports"_s, u"Rolf Newschweinstein <rolfn@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(12, 00)) },
MailHeader{ u"Sports"_s, u"Linda Smith <linda.smith@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(11, 33)) },
MailHeader{ u"Re: Accounts"_s, u"Andy <andy@nospam.com>"_s,
QDateTime(QDate(2007, 01, 03), QTime(14, 26)) },
MailHeader{ u"Re: Accounts"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
QDateTime(QDate(2007, 01, 03), QTime(14, 18)) },
MailHeader{ u"Re: Expenses"_s, u"Andy <andy@nospam.com>"_s,
QDateTime(QDate(2007, 01, 02), QTime(16, 05)) },
MailHeader{ u"Expenses"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
QDateTime(QDate(2006, 12, 25), QTime(11, 39)) },
MailHeader{ u"Accounts"_s, u"pascale@nospam.com"_s,
QDateTime(QDate(2006, 12, 31), QTime(12, 50)) },
MailHeader{ u"Radically new concept"_s, u"Grace K. <grace@software-inc.com>"_s,
QDateTime(QDate(2006, 12, 22), QTime(9, 44)) },
MailHeader{ u"Happy New Year!"_s, u"Grace K. <grace@software-inc.com>"_s,
QDateTime(QDate(2006, 12, 31), QTime(17, 03)) }
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.setSourceModel(createMailModel(&window));
window.setSourceModel(new MailModel(&window));
window.show();
return app.exec();
return QCoreApplication::exec();
}
@@ -1,10 +1,26 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "window.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QStyle>
#include <QtWidgets/QTreeView>
#include <QtGui/QColor>
#include <QtGui/QScreen>
#include <QtCore/QRegularExpression>
#include <QtCore/QSortFilterProxyModel>
using namespace Qt::StringLiterals;
static inline QColor textColor(const QPalette &palette)
{
return palette.color(QPalette::Active, QPalette::Text);
@@ -20,40 +36,32 @@ static void setTextColor(QWidget *w, const QColor &c)
}
Window::Window()
: proxyModel(new QSortFilterProxyModel),
sourceView(new QTreeView),
proxyView(new QTreeView),
sortCaseSensitivityCheckBox(new QCheckBox(tr("Case sensitive sorting"))),
filterCaseSensitivityCheckBox(new QCheckBox(tr("Case sensitive filter"))),
filterPatternLineEdit(new QLineEdit),
filterSyntaxComboBox(new QComboBox),
filterColumnComboBox(new QComboBox)
{
proxyModel = new QSortFilterProxyModel;
sourceView = new QTreeView;
sourceView->setRootIsDecorated(false);
sourceView->setAlternatingRowColors(true);
proxyView = new QTreeView;
proxyView->setRootIsDecorated(false);
proxyView->setAlternatingRowColors(true);
proxyView->setModel(proxyModel);
proxyView->setSortingEnabled(true);
sortCaseSensitivityCheckBox = new QCheckBox(tr("Case sensitive sorting"));
filterCaseSensitivityCheckBox = new QCheckBox(tr("Case sensitive filter"));
filterPatternLineEdit = new QLineEdit;
filterPatternLineEdit->setClearButtonEnabled(true);
filterPatternLabel = new QLabel(tr("&Filter pattern:"));
filterPatternLabel->setBuddy(filterPatternLineEdit);
filterSyntaxComboBox = new QComboBox;
filterSyntaxComboBox->addItem(tr("Regular expression"), RegularExpression);
filterSyntaxComboBox->addItem(tr("Wildcard"), Wildcard);
filterSyntaxComboBox->addItem(tr("Fixed string"), FixedString);
filterSyntaxLabel = new QLabel(tr("Filter &syntax:"));
filterSyntaxLabel->setBuddy(filterSyntaxComboBox);
filterColumnComboBox = new QComboBox;
filterColumnComboBox->addItem(tr("Subject"));
filterColumnComboBox->addItem(tr("Sender"));
filterColumnComboBox->addItem(tr("Date"));
filterColumnLabel = new QLabel(tr("Filter &column:"));
filterColumnLabel->setBuddy(filterColumnComboBox);
connect(filterPatternLineEdit, &QLineEdit::textChanged,
this, &Window::filterRegularExpressionChanged);
@@ -66,39 +74,38 @@ Window::Window()
connect(sortCaseSensitivityCheckBox, &QAbstractButton::toggled,
this, &Window::sortChanged);
sourceGroupBox = new QGroupBox(tr("Original Model"));
proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
auto *sourceGroupBox = new QGroupBox(tr("Original Model"));
auto *proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
QHBoxLayout *sourceLayout = new QHBoxLayout;
auto *sourceLayout = new QHBoxLayout(sourceGroupBox);
sourceLayout->addWidget(sourceView);
sourceGroupBox->setLayout(sourceLayout);
QGridLayout *proxyLayout = new QGridLayout;
proxyLayout->addWidget(proxyView, 0, 0, 1, 3);
proxyLayout->addWidget(filterPatternLabel, 1, 0);
proxyLayout->addWidget(filterPatternLineEdit, 1, 1, 1, 2);
proxyLayout->addWidget(filterSyntaxLabel, 2, 0);
proxyLayout->addWidget(filterSyntaxComboBox, 2, 1, 1, 2);
proxyLayout->addWidget(filterColumnLabel, 3, 0);
proxyLayout->addWidget(filterColumnComboBox, 3, 1, 1, 2);
proxyLayout->addWidget(filterCaseSensitivityCheckBox, 4, 0, 1, 2);
proxyLayout->addWidget(sortCaseSensitivityCheckBox, 4, 2);
proxyGroupBox->setLayout(proxyLayout);
auto *proxyLayout = new QVBoxLayout(proxyGroupBox);
proxyLayout->addWidget(proxyView);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *filterLayout = new QFormLayout;
filterLayout->addRow(tr("&Filter pattern:"), filterPatternLineEdit);
filterLayout->addRow(tr("Filter &syntax:"), filterSyntaxComboBox);
filterLayout->addRow(tr("Filter &column:"), filterColumnComboBox);
proxyLayout->addLayout(filterLayout);
auto *checkBoxLayout = new QHBoxLayout();
checkBoxLayout->addWidget(filterCaseSensitivityCheckBox);
checkBoxLayout->addWidget(sortCaseSensitivityCheckBox);
checkBoxLayout->addStretch();
proxyLayout->addLayout(checkBoxLayout);
auto *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(sourceGroupBox);
mainLayout->addWidget(proxyGroupBox);
setLayout(mainLayout);
setWindowTitle(tr("Basic Sort/Filter Model"));
resize(500, 450);
auto screenGeometry = screen()->geometry();
resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3);
proxyView->sortByColumn(1, Qt::AscendingOrder);
filterColumnComboBox->setCurrentIndex(1);
filterPatternLineEdit->setText("Andy|Grace");
filterPatternLineEdit->setText(u"Andy|Grace"_s);
filterCaseSensitivityCheckBox->setChecked(true);
sortCaseSensitivityCheckBox->setChecked(true);
}
@@ -107,6 +114,11 @@ void Window::setSourceModel(QAbstractItemModel *model)
{
proxyModel->setSourceModel(model);
sourceView->setModel(model);
for (int i = 0; i < proxyModel->columnCount(); ++i)
proxyView->resizeColumnToContents(i);
for (int i = 0; i < model->columnCount(); ++i)
sourceView->resizeColumnToContents(i);
}
void Window::filterRegularExpressionChanged()
@@ -4,14 +4,12 @@
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class QAbstractItemModel;
class QCheckBox;
class QComboBox;
class QGroupBox;
class QLabel;
class QLineEdit;
class QSortFilterProxyModel;
class QTreeView;
@@ -34,15 +32,10 @@ private slots:
private:
QSortFilterProxyModel *proxyModel;
QGroupBox *sourceGroupBox;
QGroupBox *proxyGroupBox;
QTreeView *sourceView;
QTreeView *proxyView;
QCheckBox *filterCaseSensitivityCheckBox;
QCheckBox *sortCaseSensitivityCheckBox;
QLabel *filterPatternLabel;
QLabel *filterSyntaxLabel;
QLabel *filterColumnLabel;
QCheckBox *filterCaseSensitivityCheckBox;
QLineEdit *filterPatternLineEdit;
enum Syntax {
RegularExpression,
@@ -11,6 +11,7 @@ qt_standard_project_setup()
qt_add_executable(combowidgetmapper
main.cpp
window.cpp window.h
person.h
)
set_target_properties(combowidgetmapper PROPERTIES
@@ -1,7 +1,7 @@
QT += widgets
requires(qtConfig(combobox))
HEADERS = window.h
HEADERS = window.h person.h
SOURCES = main.cpp \
window.cpp
@@ -1,7 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include <QtWidgets/QApplication>
#include "window.h"
@@ -10,5 +10,5 @@ int main(int argc, char **argv)
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
return QCoreApplication::exec();
}
@@ -1,33 +1,46 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "window.h"
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDataWidgetMapper>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QVBoxLayout>
#include <QtCore/QRangeModel>
#include <QtCore/QStringListModel>
using namespace Qt::StringLiterals;
//! [Set up widgets]
Window::Window(QWidget *parent)
: QWidget(parent)
{
setupModel();
nameLabel = new QLabel(tr("Na&me:"));
nameEdit = new QLineEdit();
addressLabel = new QLabel(tr("&Address:"));
addressEdit = new QTextEdit();
typeLabel = new QLabel(tr("&Type:"));
typeComboBox = new QComboBox();
nextButton = new QPushButton(tr("&Next"));
previousButton = new QPushButton(tr("&Previous"));
nameLabel->setBuddy(nameEdit);
addressLabel->setBuddy(addressEdit);
typeLabel->setBuddy(typeComboBox);
typeComboBox->setModel(typeModel);
: QWidget(parent),
nameEdit(new QLineEdit),
addressEdit(new QTextEdit),
typeComboBox(new QComboBox),
nextButton(new QPushButton(tr("&Next"))),
previousButton(new QPushButton(tr("&Previous"))),
//! [Set up widgets]
//! [Set up the model]
data{Person{u"Alice"_s, u"<qt>123 Main Street<br/>Market Town</qt>"_s, u"0"_s},
Person{u"Bob"_s, u"<qt>PO Box 32<br/>Mail Handling Service<br/>Service City</qt>"_s, u"1"_s},
Person{u"Carol"_s, u"<qt>The Lighthouse<br/>Remote Island</qt>"_s, u"2"_s},
Person{u"Donald"_s, u"<qt>47338 Park Avenue<br/>Big City</qt>"_s, u"0"_s},
Person{u"Emma"_s, u"<qt>Research Station<br/>Base Camp<br/>Big Mountain</qt>"_s, u"2"_s}},
model(new QRangeModel(data, this)),
mapper(new QDataWidgetMapper(this))
//! [Set up the model]
{
//! [Set up type combo box]
typeComboBox->setModel(new QStringListModel({ tr("Home"), tr("Work"), tr("Other") }, this));
//! [Set up type combo box]
//! [Set up the mapper]
mapper = new QDataWidgetMapper(this);
mapper->setModel(model);
mapper->addMapping(nameEdit, 0);
mapper->addMapping(addressEdit, 1);
@@ -42,54 +55,25 @@ Window::Window(QWidget *parent)
connect(mapper, &QDataWidgetMapper::currentIndexChanged,
this, &Window::updateButtons);
QGridLayout *layout = new QGridLayout();
layout->addWidget(nameLabel, 0, 0, 1, 1);
layout->addWidget(nameEdit, 0, 1, 1, 1);
layout->addWidget(previousButton, 0, 2, 1, 1);
layout->addWidget(addressLabel, 1, 0, 1, 1);
layout->addWidget(addressEdit, 1, 1, 2, 1);
layout->addWidget(nextButton, 1, 2, 1, 1);
layout->addWidget(typeLabel, 3, 0, 1, 1);
layout->addWidget(typeComboBox, 3, 1, 1, 1);
setLayout(layout);
auto *formLayout = new QFormLayout;
formLayout->addRow(tr("Na&me:"), nameEdit);
formLayout->addRow(tr("&Address:"), addressEdit);
formLayout->addRow(tr("&Type:"), typeComboBox);
auto *buttonLayout = new QVBoxLayout;
buttonLayout->addWidget(previousButton);
buttonLayout->addWidget(nextButton);
buttonLayout->addStretch();
auto *mainLayout = new QHBoxLayout(this);
mainLayout->addLayout(formLayout);
mainLayout->addLayout(buttonLayout);
setWindowTitle(tr("Delegate Widget Mapper"));
mapper->toFirst();
}
//! [Set up connections and layouts]
//! [Set up the model]
void Window::setupModel()
{
QStringList items;
items << tr("Home") << tr("Work") << tr("Other");
typeModel = new QStringListModel(items, this);
model = new QStandardItemModel(5, 3, this);
QStringList names;
names << "Alice" << "Bob" << "Carol" << "Donald" << "Emma";
QStringList addresses;
addresses << "<qt>123 Main Street<br/>Market Town</qt>"
<< "<qt>PO Box 32<br/>Mail Handling Service"
"<br/>Service City</qt>"
<< "<qt>The Lighthouse<br/>Remote Island</qt>"
<< "<qt>47338 Park Avenue<br/>Big City</qt>"
<< "<qt>Research Station<br/>Base Camp<br/>Big Mountain</qt>";
QStringList types;
types << "0" << "1" << "2" << "0" << "2";
for (int row = 0; row < 5; ++row) {
QStandardItem *item = new QStandardItem(names[row]);
model->setItem(row, 0, item);
item = new QStandardItem(addresses[row]);
model->setItem(row, 1, item);
item = new QStandardItem(types[row]);
model->setItem(row, 2, item);
}
}
//! [Set up the model]
//! [Slot for updating the buttons]
void Window::updateButtons(int row)
{
@@ -4,16 +4,17 @@
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include "person.h"
#include <QtWidgets/QWidget>
#include <QtCore/QList>
QT_BEGIN_NAMESPACE
class QComboBox;
class QDataWidgetMapper;
class QLabel;
class QLineEdit;
class QPushButton;
class QStandardItemModel;
class QStringListModel;
class QRangeModel;
class QTextEdit;
QT_END_NAMESPACE
@@ -29,19 +30,13 @@ private slots:
void updateButtons(int row);
private:
void setupModel();
QLabel *nameLabel;
QLabel *addressLabel;
QLabel *typeLabel;
QLineEdit *nameEdit;
QTextEdit *addressEdit;
QComboBox *typeComboBox;
QPushButton *nextButton;
QPushButton *previousButton;
QStandardItemModel *model;
QStringListModel *typeModel;
QList<Person> data;
QRangeModel *model;
QDataWidgetMapper *mapper;
};
//! [Window definition]
@@ -10,7 +10,7 @@ qt_standard_project_setup()
qt_add_executable(customsortfiltermodel
filterwidget.cpp filterwidget.h
main.cpp
main.cpp mailheader.h
mysortfilterproxymodel.cpp mysortfilterproxymodel.h
window.cpp window.h
)
@@ -3,7 +3,8 @@ requires(qtConfig(treeview))
HEADERS = mysortfilterproxymodel.h \
window.h \
filterwidget.h
filterwidget.h \
mailheader.h
SOURCES = main.cpp \
mysortfilterproxymodel.cpp \
window.cpp \
@@ -3,13 +3,15 @@
#include "filterwidget.h"
#include <QIcon>
#include <QPixmap>
#include <QMenu>
#include <QAction>
#include <QActionGroup>
#include <QToolButton>
#include <QWidgetAction>
#include <QtWidgets/QMenu>
#include <QtWidgets/QToolButton>
#include <QtWidgets/QWidgetAction>
#include <QtGui/QAction>
#include <QtGui/QActionGroup>
#include <QtGui/QIcon>
using namespace Qt::StringLiterals;
FilterWidget::FilterWidget(QWidget *parent)
: QLineEdit(parent)
@@ -18,30 +20,30 @@ FilterWidget::FilterWidget(QWidget *parent)
setClearButtonEnabled(true);
connect(this, &QLineEdit::textChanged, this, &FilterWidget::filterChanged);
QMenu *menu = new QMenu(this);
auto *menu = new QMenu(this);
m_caseSensitivityAction = menu->addAction(tr("Case Sensitive"));
m_caseSensitivityAction->setCheckable(true);
connect(m_caseSensitivityAction, &QAction::toggled, this, &FilterWidget::filterChanged);
menu->addSeparator();
m_patternGroup->setExclusive(true);
QAction *patternAction = menu->addAction("Regular Expression");
QAction *patternAction = menu->addAction(tr("Regular Expression"));
patternAction->setCheckable(true);
patternAction->setChecked(true);
patternAction->setData(QVariant(int(RegularExpression)));
m_patternGroup->addAction(patternAction);
patternAction = menu->addAction("Wildcard");
patternAction = menu->addAction(tr("Wildcard"));
patternAction->setCheckable(true);
patternAction->setData(QVariant(int(Wildcard)));
m_patternGroup->addAction(patternAction);
patternAction = menu->addAction("Fixed String");
patternAction = menu->addAction(tr("Fixed String"));
patternAction->setData(QVariant(int(FixedString)));
patternAction->setCheckable(true);
m_patternGroup->addAction(patternAction);
connect(m_patternGroup, &QActionGroup::triggered, this, &FilterWidget::filterChanged);
const QIcon icon = QIcon(QPixmap(":/images/find.png"));
QToolButton *optionsButton = new QToolButton;
const QIcon icon = QIcon(u":/images/find.png"_s);
auto *optionsButton = new QToolButton;
#ifndef QT_NO_CURSOR
optionsButton->setCursor(Qt::ArrowCursor);
#endif
@@ -50,7 +52,7 @@ FilterWidget::FilterWidget(QWidget *parent)
optionsButton->setMenu(menu);
optionsButton->setPopupMode(QToolButton::InstantPopup);
QWidgetAction *optionsAction = new QWidgetAction(this);
auto *optionsAction = new QWidgetAction(this);
optionsAction->setDefaultWidget(optionsButton);
addAction(optionsAction, QLineEdit::LeadingPosition);
}
@@ -4,7 +4,7 @@
#ifndef FILTERWIDGET_H
#define FILTERWIDGET_H
#include <QLineEdit>
#include <QtWidgets/QLineEdit>
QT_BEGIN_NAMESPACE
class QAction;
@@ -1,58 +1,76 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "mailheader.h"
#include "window.h"
void addMail(QAbstractItemModel *model, const QString &subject,
const QString &sender, const QDateTime &date)
{
model->insertRow(0);
model->setData(model->index(0, 0), subject);
model->setData(model->index(0, 1), sender);
model->setData(model->index(0, 2), date);
}
#include <QtWidgets/QApplication>
QAbstractItemModel *createMailModel(QObject *parent)
{
QStandardItemModel *model = new QStandardItemModel(0, 3, parent);
#include <QtCore/QRangeModel>
model->setHeaderData(0, Qt::Horizontal, QObject::tr("Subject"));
model->setHeaderData(1, Qt::Horizontal, QObject::tr("Sender"));
model->setHeaderData(2, Qt::Horizontal, QObject::tr("Date"));
#include <array>
addMail(model, "Happy New Year!", "Grace K. <grace@software-inc.com>",
QDateTime(QDate(2006, 12, 31), QTime(17, 03)));
addMail(model, "Radically new concept", "Grace K. <grace@software-inc.com>",
QDateTime(QDate(2006, 12, 22), QTime(9, 44)));
addMail(model, "Accounts", "pascale@nospam.com",
QDateTime(QDate(2006, 12, 31), QTime(12, 50)));
addMail(model, "Expenses", "Joe Bloggs <joe@bloggs.com>",
QDateTime(QDate(2006, 12, 25), QTime(11, 39)));
addMail(model, "Re: Expenses", "Andy <andy@nospam.com>",
QDateTime(QDate(2007, 01, 02), QTime(16, 05)));
addMail(model, "Re: Accounts", "Joe Bloggs <joe@bloggs.com>",
QDateTime(QDate(2007, 01, 03), QTime(14, 18)));
addMail(model, "Re: Accounts", "Andy <andy@nospam.com>",
QDateTime(QDate(2007, 01, 03), QTime(14, 26)));
addMail(model, "Sports", "Linda Smith <linda.smith@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(11, 33)));
addMail(model, "AW: Sports", "Rolf Newschweinstein <rolfn@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(12, 00)));
addMail(model, "RE: Sports", "Petra Schmidt <petras@nospam.com>",
QDateTime(QDate(2007, 01, 05), QTime(12, 01)));
return model;
}
using namespace Qt::StringLiterals;
//! [0]
class MailModel : public QRangeModel
{
public:
explicit MailModel(QObject *parent = nullptr) : QRangeModel(mails, parent) {}
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case 0:
return Window::tr("Subject");
case 1:
return Window::tr("Sender");
case 2:
return Window::tr("Date");
default:
break;
}
}
return QRangeModel::headerData(section, orientation, role);
}
private:
static const std::array<MailHeader, 10> mails;
};
const std::array<MailHeader, 10> MailModel::mails = {
MailHeader{ u"RE: Sports"_s, u"Petra Schmidt <petras@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(12, 01)) },
MailHeader{ u"AW: Sports"_s, u"Rolf Newschweinstein <rolfn@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(12, 00)) },
MailHeader{ u"Sports"_s, u"Linda Smith <linda.smith@nospam.com>"_s,
QDateTime(QDate(2007, 01, 05), QTime(11, 33)) },
MailHeader{ u"Re: Accounts"_s, u"Andy <andy@nospam.com>"_s,
QDateTime(QDate(2007, 01, 03), QTime(14, 26)) },
MailHeader{ u"Re: Accounts"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
QDateTime(QDate(2007, 01, 03), QTime(14, 18)) },
MailHeader{ u"Re: Expenses"_s, u"Andy <andy@nospam.com>"_s,
QDateTime(QDate(2007, 01, 02), QTime(16, 05)) },
MailHeader{ u"Expenses"_s, u"Joe Bloggs <joe@bloggs.com>"_s,
QDateTime(QDate(2006, 12, 25), QTime(11, 39)) },
MailHeader{ u"Accounts"_s, u"pascale@nospam.com"_s,
QDateTime(QDate(2006, 12, 31), QTime(12, 50)) },
MailHeader{ u"Radically new concept"_s, u"Grace K. <grace@software-inc.com>"_s,
QDateTime(QDate(2006, 12, 22), QTime(9, 44)) },
MailHeader{ u"Happy New Year!"_s, u"Grace K. <grace@software-inc.com>"_s,
QDateTime(QDate(2006, 12, 31), QTime(17, 03)) }
};
//! [0]
//! [1]
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.setSourceModel(createMailModel(&window));
window.setSourceModel(new MailModel(&window));
window.show();
return app.exec();
return QCoreApplication::exec();
}
//! [0]
//! [1]
@@ -3,7 +3,10 @@
#include "mysortfilterproxymodel.h"
#include <QtWidgets>
#include <QtCore/QModelIndex>
#include <QtCore/QRegularExpression>
using namespace Qt::StringLiterals;
//! [0]
MySortFilterProxyModel::MySortFilterProxyModel(QObject *parent)
@@ -53,26 +56,25 @@ bool MySortFilterProxyModel::lessThan(const QModelIndex &left,
//! [4]
//! [6]
if (leftData.userType() == QMetaType::QDateTime) {
if (leftData.userType() == QMetaType::QDateTime)
return leftData.toDateTime() < rightData.toDateTime();
} else {
static const QRegularExpression emailPattern("[\\w\\.]*@[\\w\\.]*");
QString leftString = leftData.toString();
if (left.column() == 1) {
const QRegularExpressionMatch match = emailPattern.match(leftString);
if (match.hasMatch())
leftString = match.captured(0);
}
QString rightString = rightData.toString();
if (right.column() == 1) {
const QRegularExpressionMatch match = emailPattern.match(rightString);
if (match.hasMatch())
rightString = match.captured(0);
}
static const QRegularExpression emailPattern(u"[\\w\\.]*@[\\w\\.]*"_s);
return QString::localeAwareCompare(leftString, rightString) < 0;
QString leftString = leftData.toString();
if (left.column() == 1) {
const QRegularExpressionMatch match = emailPattern.match(leftString);
if (match.hasMatch())
leftString = match.captured(0);
}
QString rightString = rightData.toString();
if (right.column() == 1) {
const QRegularExpressionMatch match = emailPattern.match(rightString);
if (match.hasMatch())
rightString = match.captured(0);
}
return QString::localeAwareCompare(leftString, rightString) < 0;
}
//! [5] //! [6]
@@ -4,8 +4,8 @@
#ifndef MYSORTFILTERPROXYMODEL_H
#define MYSORTFILTERPROXYMODEL_H
#include <QDate>
#include <QSortFilterProxyModel>
#include <QtCore/QDate>
#include <QtCore/QSortFilterProxyModel>
//! [0]
class MySortFilterProxyModel : public QSortFilterProxyModel
@@ -5,83 +5,84 @@
#include "mysortfilterproxymodel.h"
#include "filterwidget.h"
#include <QtWidgets>
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDateEdit>
#include <QtWidgets/QFormLayout>
#include <QtWidgets/QGroupBox>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QStyle>
#include <QtWidgets/QTreeView>
#include <QtGui/QScreen>
#include <QtCore/QDate>
//! [0]
Window::Window()
: proxyModel(new MySortFilterProxyModel(this)),
//! [0]
sourceView(new QTreeView),
proxyView(new QTreeView),
filterWidget(new FilterWidget),
fromDateEdit(new QDateEdit),
toDateEdit(new QDateEdit)
{
proxyModel = new MySortFilterProxyModel(this);
//! [0]
//! [1]
sourceView = new QTreeView;
sourceView->setRootIsDecorated(false);
sourceView->setAlternatingRowColors(true);
//! [1]
QHBoxLayout *sourceLayout = new QHBoxLayout;
//! [2]
auto *sourceGroupBox = new QGroupBox(tr("Original Model"));
auto *sourceLayout = new QHBoxLayout(sourceGroupBox);
sourceLayout->addWidget(sourceView);
sourceGroupBox = new QGroupBox(tr("Original Model"));
sourceGroupBox->setLayout(sourceLayout);
//! [2]
//! [3]
filterWidget = new FilterWidget;
filterWidget->setText(tr("Grace|Sports"));
connect(filterWidget, &FilterWidget::filterChanged, this, &Window::textFilterChanged);
filterPatternLabel = new QLabel(tr("&Filter pattern:"));
filterPatternLabel->setBuddy(filterWidget);
fromDateEdit = new QDateEdit;
fromDateEdit->setDate(QDate(1970, 01, 01));
fromLabel = new QLabel(tr("F&rom:"));
fromLabel->setBuddy(fromDateEdit);
toDateEdit = new QDateEdit;
toDateEdit->setDate(QDate(2099, 12, 31));
toLabel = new QLabel(tr("&To:"));
toLabel->setBuddy(toDateEdit);
//! [3]
//! [4]
connect(filterWidget, &FilterWidget::filterChanged,
this, &Window::textFilterChanged);
connect(filterWidget, &QLineEdit::textChanged,
this, &Window::textFilterChanged);
connect(fromDateEdit, &QDateTimeEdit::dateChanged,
this, &Window::dateFilterChanged);
connect(toDateEdit, &QDateTimeEdit::dateChanged,
//! [3] //! [4]
this, &Window::dateFilterChanged);
//! [4]
//! [5]
proxyView = new QTreeView;
proxyView->setRootIsDecorated(false);
proxyView->setAlternatingRowColors(true);
proxyView->setModel(proxyModel);
proxyView->setSortingEnabled(true);
proxyView->sortByColumn(1, Qt::AscendingOrder);
QGridLayout *proxyLayout = new QGridLayout;
proxyLayout->addWidget(proxyView, 0, 0, 1, 3);
proxyLayout->addWidget(filterPatternLabel, 1, 0);
proxyLayout->addWidget(filterWidget, 1, 1);
proxyLayout->addWidget(fromLabel, 3, 0);
proxyLayout->addWidget(fromDateEdit, 3, 1, 1, 2);
proxyLayout->addWidget(toLabel, 4, 0);
proxyLayout->addWidget(toDateEdit, 4, 1, 1, 2);
auto *proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
auto *proxyLayout = new QVBoxLayout(proxyGroupBox);
proxyLayout->addWidget(proxyView);
auto *formLayout = new QFormLayout;
proxyLayout->addLayout(formLayout);
formLayout->addRow(tr("&Filter pattern:"), filterWidget);
formLayout->addRow(tr("F&rom:"), fromDateEdit);
formLayout->addRow(tr("&To:"), toDateEdit);
proxyGroupBox = new QGroupBox(tr("Sorted/Filtered Model"));
proxyGroupBox->setLayout(proxyLayout);
//! [5]
//! [6]
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(sourceGroupBox);
mainLayout->addWidget(proxyGroupBox);
setLayout(mainLayout);
setWindowTitle(tr("Custom Sort/Filter Model"));
resize(500, 450);
auto screenGeometry = screen()->geometry();
resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3);
}
//! [6]
@@ -8,16 +8,13 @@
QT_BEGIN_NAMESPACE
class QAbstractItemModel;
class QCheckBox;
class QComboBox;
class QDateEdit;
class QGroupBox;
class QLabel;
class QLineEdit;
class QTreeView;
QT_END_NAMESPACE
class MySortFilterProxyModel;
class FilterWidget;
//! [0]
class Window : public QWidget
{
@@ -35,13 +32,8 @@ private slots:
private:
MySortFilterProxyModel *proxyModel;
QGroupBox *sourceGroupBox;
QGroupBox *proxyGroupBox;
QTreeView *sourceView;
QTreeView *proxyView;
QLabel *filterPatternLabel;
QLabel *fromLabel;
QLabel *toLabel;
FilterWidget *filterWidget;
QDateEdit *fromDateEdit;
QDateEdit *toDateEdit;
@@ -14,7 +14,7 @@
the files. The \l {QXmlStream Bookmarks Example} provides an alternative
way to read this type of file.
\image screenshot.png
\image screenshot.png {Bookmark tree with Title and Location columns}
\section1 The XbelTree Class Definition
@@ -36,7 +36,11 @@ build_pass|if(single_android_abi:!single_arch) {
else:TARGET = lib$${TARGET}_$${QT_ARCH}.so
}
QMAKE_LFLAGS += -Wl,-soname,$$shell_quote($$TARGET)
contains(ANDROID_TARGET_ARCH, "arm64-v8a|x86_64") {
qtConfig(android_16kb_pages) {
QMAKE_LFLAGS += -Wl,-z,max-page-size=16384
}
}
android_install {
target.path=/libs/$$ANDROID_TARGET_ARCH/
INSTALLS *= target
@@ -12,6 +12,8 @@
#include <qfileinfo.h>
#include <qhash.h>
#include <functional>
QT_BEGIN_NAMESPACE
// -------------------------------------------------------------------------------------------------
@@ -40,7 +42,15 @@ struct FixStringCacheKey
return hash;
}
};
inline size_t qHash(const FixStringCacheKey &f) { return f.hashCode(); }
QT_BEGIN_INCLUDE_NAMESPACE
namespace std {
template <> struct hash<QT_PREPEND_NAMESPACE(FixStringCacheKey)>
{
size_t operator()(const QT_PREPEND_NAMESPACE(FixStringCacheKey) &key) const noexcept
{ return key.hashCode(); }
};
} // namespace std
QT_END_INCLUDE_NAMESPACE
// -------------------------------------------------------------------------------------------------
struct FileInfoCacheKey
@@ -78,7 +88,15 @@ struct FileInfoCacheKey
|| (c0 == QLatin1Char('\\') && c1 == QLatin1Char('\\')));
}
};
inline size_t qHash(const FileInfoCacheKey &f) { return f.hashCode(); }
QT_BEGIN_INCLUDE_NAMESPACE
namespace std {
template <> struct hash<QT_PREPEND_NAMESPACE(FileInfoCacheKey)>
{
size_t operator()(const QT_PREPEND_NAMESPACE(FileInfoCacheKey) &key) const noexcept
{ return key.hashCode(); }
};
} // namespace std
QT_END_INCLUDE_NAMESPACE
// -------------------------------------------------------------------------------------------------
template <typename T>
@@ -2820,20 +2820,22 @@ MakefileGenerator::writeMakeQmake(QTextStream &t, bool noDummyQmakeAll)
QFileInfo
MakefileGenerator::fileInfo(QString file) const
{
Q_CONSTINIT static QHash<FileInfoCacheKey, QFileInfo> *cache = nullptr;
static QFileInfo noInfo = QFileInfo();
using HashMap = std::unordered_map<FileInfoCacheKey, QFileInfo>;
Q_CONSTINIT static HashMap *cache = nullptr;
if(!cache) {
cache = new QHash<FileInfoCacheKey, QFileInfo>;
qmakeAddCacheClear(qmakeDeleteCacheClear<QHash<FileInfoCacheKey, QFileInfo> >, (void**)&cache);
cache = new HashMap;
qmakeAddCacheClear(qmakeDeleteCacheClear<HashMap>, (void**)&cache);
}
FileInfoCacheKey cacheKey(file);
QFileInfo value = cache->value(cacheKey, noInfo);
if (value != noInfo)
return value;
const auto it = cache->find(cacheKey);
if (it != cache->end())
return it->second;
QFileInfo fi(file);
if (fi.exists())
cache->insert(cacheKey, fi);
cache->emplace(cacheKey, fi);
return fi;
}
@@ -306,8 +306,9 @@ struct ReplaceExtraCompilerCacheKey
hash = (size_t)forShell ^ qHash(var) ^ qHash(in) ^ qHash(out) /*^ qHash(pwd)*/;
return hash;
}
friend size_t qHash(const ReplaceExtraCompilerCacheKey &key, size_t seed = 0) noexcept
{ return key.hashCode() ^ seed; }
};
inline size_t qHash(const ReplaceExtraCompilerCacheKey &f) { return f.hashCode(); }
QT_END_NAMESPACE
@@ -92,11 +92,11 @@ size_t ProString::updatedHash() const
return (m_hash = hash(m_string.constData() + m_offset, m_length));
}
size_t qHash(const ProString &str)
size_t qHash(const ProString &str, size_t seed)
{
if (!(str.m_hash & 0x80000000))
return str.m_hash;
return str.updatedHash();
return str.m_hash ^ seed;
return str.updatedHash() ^ seed;
}
ProKey::ProKey(const QString &str) :
@@ -161,7 +161,8 @@ private:
int m_file;
mutable size_t m_hash;
size_t updatedHash() const;
friend size_t qHash(const ProString &str);
friend size_t qHash(const ProString &str, size_t seed);
friend size_t qHash(const ProString &str) { return qHash(str, size_t{0}); }
friend QString operator+(const ProString &one, const ProString &two);
friend class ProKey;
};
@@ -195,6 +196,9 @@ public:
ALWAYS_INLINE const ProString &toString() const { return *(const ProString *)this; }
private:
friend size_t qHash(const ProKey &key, size_t seed = 0) noexcept
{ return qHash(key.toString(), seed); }
ProKey(const ProString &other);
};
Q_DECLARE_TYPEINFO(ProKey, Q_RELOCATABLE_TYPE);
@@ -231,9 +235,6 @@ template <> struct QConcatenable<ProKey> : private QAbstractConcatenable
}
};
size_t qHash(const ProString &str);
inline QString &operator+=(QString &that, const ProString &other)
{ return that += other.toQStringView(); }
@@ -82,9 +82,9 @@ QMakeBaseKey::QMakeBaseKey(const QString &_root, const QString &_stash, bool _ho
{
}
size_t qHash(const QMakeBaseKey &key)
size_t qHash(const QMakeBaseKey &key, size_t seed) noexcept
{
return qHash(key.root) ^ qHash(key.stash) ^ (uint)key.hostBuild;
return qHashMulti(seed, key.root, key.stash, uint{key.hostBuild});
}
bool operator==(const QMakeBaseKey &one, const QMakeBaseKey &two)
@@ -35,9 +35,11 @@ public:
QString root;
QString stash;
bool hostBuild;
friend size_t qHash(const QMakeBaseKey &key, size_t seed) noexcept;
friend size_t qHash(const QMakeBaseKey &key) noexcept { return qHash(key, size_t{0}); }
};
size_t qHash(const QMakeBaseKey &key);
bool operator==(const QMakeBaseKey &one, const QMakeBaseKey &two);
class QMakeBaseEnv
@@ -15,6 +15,8 @@
#include <qtversion.h>
#include <private/qlibraryinfo_p.h>
#include <unordered_map>
QT_BEGIN_NAMESPACE
using namespace QMakeInternal;
@@ -474,18 +476,19 @@ QString
Option::fixString(QString string, uchar flags)
{
//const QString orig_string = string;
static QHash<FixStringCacheKey, QString> *cache = nullptr;
using HashMap = std::unordered_map<FixStringCacheKey, QString>;
Q_CONSTINIT static HashMap *cache = nullptr;
if(!cache) {
cache = new QHash<FixStringCacheKey, QString>;
qmakeAddCacheClear(qmakeDeleteCacheClear<QHash<FixStringCacheKey, QString> >, (void**)&cache);
cache = new HashMap();
qmakeAddCacheClear(qmakeDeleteCacheClear<HashMap>, (void**)&cache);
}
FixStringCacheKey cacheKey(string, flags);
QHash<FixStringCacheKey, QString>::const_iterator it = cache->constFind(cacheKey);
const auto it = cache->find(cacheKey);
if (it != cache->constEnd()) {
if (it != cache->end()) {
//qDebug() << "Fix (cached) " << orig_string << "->" << it.value();
return it.value();
return it->second;
}
//fix the environment variables
@@ -532,7 +535,7 @@ Option::fixString(QString string, uchar flags)
//cache
//qDebug() << "Fix" << orig_string << "->" << string;
cache->insert(cacheKey, string);
cache->emplace(cacheKey, string);
return string;
}
@@ -141,6 +141,7 @@ especially to test compatibility with D3D12_RESOURCE_HEAP_TIER_1 on modern GPUs.
#pragma GCC diagnostic ignored "-Wswitch"
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#if defined(__clang__)
@@ -28,7 +28,6 @@
#include <fcntl.h>
#include <stdint.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <unistd.h> // to get the POSIX flags
#if _POSIX_SPAWN > 0
+12 -9
View File
@@ -1,4 +1,4 @@
FreeType 2.14.2
FreeType 2.14.3
===============
Homepage: https://freetype.org
@@ -32,9 +32,9 @@ sites. Go to
and download one of the following files.
freetype-doc-2.14.2.tar.xz
freetype-doc-2.14.2.tar.gz
ftdoc2142.zip
freetype-doc-2.14.3.tar.xz
freetype-doc-2.14.3.tar.gz
ftdoc2143.zip
To view the documentation online, go to
@@ -66,10 +66,13 @@ Please submit bug reports at
https://gitlab.freedesktop.org/freetype/freetype/-/issues
Alternatively, you might report bugs by e-mail to
`freetype-devel@nongnu.org`. Don't forget to send a detailed
explanation of the problem -- there is nothing worse than receiving a
terse message that only says 'it doesn't work'.
This gitlab instance also supports confidential reports.
Alternatively, but less preferable, you might report bugs by sending
an e-mail to the public mailing list `freetype-devel@nongnu.org`.
Don't forget to submit a detailed explanation of the problem -- there
is nothing worse than receiving a terse message that only says 'it
doesn't work'.
Patches
@@ -94,7 +97,7 @@ Enjoy!
----------------------------------------------------------------------
Copyright (C) 2006-2025 by
Copyright (C) 2006-2026 by
David Turner, Robert Wilhelm, and Werner Lemberg.
This file is part of the FreeType project, and may only be used,
@@ -482,14 +482,14 @@ SPDX-License-Identifier = "FTL OR GPL-2.0-only"
path = ["src/autofit/afgsub.*"]
comment = "Copyright continuation line ignored by reuse for lack of word Copyright at start."
precedence = "override"
SPDX-FileCopyrightText = ["Copyright (C) 2025 by David Turner, Robert Wilhelm, and Werner Lemberg."]
SPDX-FileCopyrightText = ["Copyright (C) 2026 by David Turner, Robert Wilhelm, and Werner Lemberg."]
SPDX-License-Identifier = "FTL OR GPL-2.0-only"
[[annotations]]
path = ["src/autofit/afadjust.*"]
comment = "Copyright continuation line ignored by reuse for lack of word Copyright at start."
precedence = "override"
SPDX-FileCopyrightText = ["Copyright (C) 2023-2025 by David Turner, Robert Wilhelm, and Werner Lemberg and Craig White"]
SPDX-FileCopyrightText = ["Copyright (C) 2023-2026 by David Turner, Robert Wilhelm, and Werner Lemberg and Craig White"]
SPDX-License-Identifier = "FTL OR GPL-2.0-only"
@@ -4,7 +4,7 @@
*
* Unix-specific FreeType low-level system interface (body).
*
* Copyright (C) 1996-2025 by
* Copyright (C) 1996-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* This file is part of the FreeType project, and may only be used,
@@ -4,7 +4,7 @@
*
* Debugging and logging component for Win32 (body).
*
* Copyright (C) 1996-2025 by
* Copyright (C) 1996-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* This file is part of the FreeType project, and may only be used,
@@ -1,3 +1,20 @@
CHANGES BETWEEN 2.14.2 and 2.14.3 (2026-Mar-22)
I. IMPORTANT BUG FIXES
- A bunch of potential security problems have been found. All users
should update.
II. MISCELLANEOUS
- If configuration option `TT_CONFIG_OPTION_GPOS_KERNING` is active,
GPOS-based kerning could miss some value pairs (bug introduced in
version 2.14.0).
======================================================================
CHANGES BETWEEN 2.14.1 and 2.14.2 (2026-Mar-01)
I. IMPORTANT CHANGES
@@ -5867,7 +5884,7 @@ Extensions support:
------------------------------------------------------------------------
Copyright (C) 2000-2025 by
Copyright (C) 2000-2026 by
David Turner, Robert Wilhelm, and Werner Lemberg.
This file is part of the FreeType project, and may only be used,
@@ -139,7 +139,7 @@ IV. Overriding default configuration and module headers
----------------------------------------------------------------------
Copyright (C) 2003-2025 by
Copyright (C) 2003-2026 by
David Turner, Robert Wilhelm, and Werner Lemberg.
This file is part of the FreeType project, and may only be used,
@@ -297,7 +297,7 @@ to access them.
------------------------------------------------------------------------
Copyright (C) 2002-2025 by
Copyright (C) 2002-2026 by
David Turner, Robert Wilhelm, and Werner Lemberg.
This file is part of the FreeType project, and may only be used,
@@ -27,7 +27,7 @@ Other bugs have been registered at the savannah bugzilla of FreeType.
------------------------------------------------------------------------
Copyright (C) 2001-2025 by
Copyright (C) 2001-2026 by
David Turner, Robert Wilhelm, and Werner Lemberg.
This file is part of the FreeType project, and may only be used,
@@ -8,8 +8,8 @@
"Description": "FreeType is a freely available software library to render fonts.",
"Homepage": "http://www.freetype.org",
"Version": "2.14.2",
"DownloadLocation": "https://download.savannah.gnu.org/releases/freetype/freetype-2.14.2.tar.gz",
"Version": "2.14.3",
"DownloadLocation": "https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz",
"PURL": [
"pkg:github/freetype/freetype@VER-$<VERSION_DASHED>"
],
@@ -21,34 +21,34 @@
"LicenseId": "FTL OR GPL-2.0-only",
"LicenseFile": "LICENSE.txt",
"Copyright": ["Copyright (c) 2007-2014 Adobe Systems Incorporated",
"Copyright (c) 2004-2025 Albert Chin-A-Young",
"Copyright (c) 2018-2025 Armin Hasitzka, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2004-2026 Albert Chin-A-Young",
"Copyright (c) 2018-2026 Armin Hasitzka, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2000 Computing Research Labs, New Mexico State University",
"Copyright (c) 1996-2025 David Turner, Robert Wilhelm, Dominik Röttsches, and Werner Lemberg",
"Copyright (c) 2004-2025 David Turner, Robert Wilhelm, Werner Lemberg and George Williams",
"Copyright (c) 2022-2025 David Turner, Robert Wilhelm, Werner Lemberg, and Moazin Khatti",
"Copyright (c) 2008-2025 David Turner, Robert Wilhelm, Werner Lemberg, and suzuki toshiya",
"Copyright (c) 2003-2025 David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2005-2025 David Turner",
"Copyright (c) 2007-2025 Derek Clegg and Michael Toftdal",
"Copyright (c) 1996-2026 David Turner, Robert Wilhelm, Dominik Röttsches, and Werner Lemberg",
"Copyright (c) 2004-2026 David Turner, Robert Wilhelm, Werner Lemberg and George Williams",
"Copyright (c) 2022-2026 David Turner, Robert Wilhelm, Werner Lemberg, and Moazin Khatti",
"Copyright (c) 2008-2026 David Turner, Robert Wilhelm, Werner Lemberg, and suzuki toshiya",
"Copyright (c) 2003-2026 David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2005-2026 David Turner",
"Copyright (c) 2007-2026 Derek Clegg and Michael Toftdal",
"Copyright (c) 2007 Dmitry Timoshkov for Codeweavers",
"Copyright (c) 2001-2015 Francesco Zappa Nardelli",
"Copyright (c) 2005, 2007, 2008, 2013 George Williams",
"Copyright (c) 2013-2025 Google, Inc. Google Author(s) Behdad Esfahbod and Stuart Gill",
"Copyright (c) 2013-2026 Google, Inc. Google Author(s) Behdad Esfahbod and Stuart Gill",
"Copyright (c) 2013-2022 Google, Inc.",
"Copyright (c) 2003 Huw D M Davies for Codeweavers",
"Copyright (c) 2010-2025 Joel Klinghed",
"Copyright (c) 1996-2025 Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2003-2025 Masatake YAMATO and Redhat K.K.",
"Copyright (c) 2004-2025 Masatake YAMATO, Redhat K.K, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2019-2025 Nikhil Ramakrishnan, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2009-2025 Oran Agra and Mickey Gabel",
"Copyright (c) 2007-2025 Rahul Bhalerao <rahul.bhalerao@redhat.com>",
"Copyright (c) 2002-2025 Roberto Alameda",
"Copyright (c) 2015-2025 Werner Lemberg",
"Copyright (c) 2004-2025 suzuki toshiya, Masatake YAMATO, Red Hat K.K., David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2010-2026 Joel Klinghed",
"Copyright (c) 1996-2026 Just van Rossum, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2003-2026 Masatake YAMATO and Redhat K.K.",
"Copyright (c) 2004-2026 Masatake YAMATO, Redhat K.K, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2019-2026 Nikhil Ramakrishnan, David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2009-2026 Oran Agra and Mickey Gabel",
"Copyright (c) 2007-2026 Rahul Bhalerao <rahul.bhalerao@redhat.com>",
"Copyright (c) 2002-2026 Roberto Alameda",
"Copyright (c) 2015-2026 Werner Lemberg",
"Copyright (c) 2004-2026 suzuki toshiya, Masatake YAMATO, Red Hat K.K., David Turner, Robert Wilhelm, and Werner Lemberg",
"Copyright (c) 2019 nyorain",
"Copyright (c) 2022-2025 David Turner, Robert Wilhelm, Werner Lemberg, George Williams, and Dominik Röttsches",
"Copyright (c) 2022-2026 David Turner, Robert Wilhelm, Werner Lemberg, George Williams, and Dominik Röttsches",
"Copyright (C) 2009, 2023 Red Hat, Inc."]
},
{
@@ -4,7 +4,7 @@
*
* Auto-fitter routines to adjust components based on charcode (body).
*
* Copyright (C) 2023-2025 by
* Copyright (C) 2023-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* Written by Craig White <gerzytet@gmail.com>.
@@ -4,7 +4,7 @@
*
* Auto-fitter routines to adjust components based on charcode (header).
*
* Copyright (C) 2023-2025 by
* Copyright (C) 2023-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* Written by Craig White <gerzytet@gmail.com>.
@@ -7,7 +7,7 @@
*
* Auto-fitter data for blue strings (body).
*
* Copyright (C) 2013-2025 by
* Copyright (C) 2013-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* This file is part of the FreeType project, and may only be used,
@@ -4,7 +4,7 @@
*
* Auto-fitter data for blue strings (body).
*
* Copyright (C) 2013-2025 by
* Copyright (C) 2013-2026 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* This file is part of the FreeType project, and may only be used,

Some files were not shown because too many files have changed in this diff Show More