diff --git a/cmake/Qt3rdPartyLibraryHelpers.cmake b/cmake/Qt3rdPartyLibraryHelpers.cmake
index c5719a504b2..17a6afd5551 100644
--- a/cmake/Qt3rdPartyLibraryHelpers.cmake
+++ b/cmake/Qt3rdPartyLibraryHelpers.cmake
@@ -130,23 +130,49 @@ function(qt_internal_add_cmake_library target)
)
endfunction()
+macro(qt_internal_get_3rdparty_library_sbom_options option_args single_args multi_args)
+ set(${option_args} "")
+ set(${single_args}
+ PACKAGE_VERSION
+ CPE_VENDOR
+ CPE_PRODUCT
+ LICENSE_EXPRESSION
+ DOWNLOAD_LOCATION
+ ${__qt_internal_sbom_single_args}
+ )
+ set(${multi_args}
+ COPYRIGHTS
+ CPE # Common Platform Enumeration, free-form
+ ${__qt_internal_sbom_multi_args}
+ )
+endmacro()
+
# This function replaces qmake's qt_helper_lib feature. It is intended to
# compile 3rdparty libraries as part of the build.
#
function(qt_internal_add_3rdparty_library target)
qt_internal_get_add_library_option_args(library_option_args)
+ qt_internal_get_3rdparty_library_sbom_options(
+ sbom_option_args
+ sbom_single_args
+ sbom_multi_args
+ )
+
set(option_args
EXCEPTIONS
INSTALL
SKIP_AUTOMOC
+ ${sbom_option_args}
)
set(single_args
OUTPUT_DIRECTORY
QMAKE_LIB_NAME
+ ${sbom_single_args}
)
set(multi_args
${__default_private_args}
${__default_public_args}
+ ${sbom_multi_args}
)
cmake_parse_arguments(PARSE_ARGV 1 arg
@@ -253,6 +279,12 @@ function(qt_internal_add_3rdparty_library target)
)
if(NOT BUILD_SHARED_LIBS OR arg_INSTALL)
+ set(will_install TRUE)
+ else()
+ set(will_install FALSE)
+ endif()
+
+ if(will_install)
qt_generate_3rdparty_lib_pri_file("${target}" "${arg_QMAKE_LIB_NAME}" pri_file)
if(pri_file)
qt_install(FILES "${pri_file}" DESTINATION "${INSTALL_MKSPECSDIR}/modules")
@@ -327,6 +359,55 @@ function(qt_internal_add_3rdparty_library target)
INTERPROCEDURAL_OPTIMIZATION OFF
)
endif()
+
+ if(QT_GENERATE_SBOM)
+ set(sbom_args "")
+ list(APPEND sbom_args TYPE QT_THIRD_PARTY_MODULE)
+
+ if(NOT will_install)
+ list(APPEND sbom_args NO_INSTALL)
+ endif()
+
+ qt_get_cmake_configurations(configs)
+ foreach(config IN LISTS configs)
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ RUNTIME_PATH
+ "${INSTALL_BINDIR}"
+ "${config}"
+ sbom_args
+ )
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ LIBRARY_PATH
+ "${INSTALL_LIBDIR}"
+ "${config}"
+ sbom_args
+ )
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ ARCHIVE_PATH
+ "${INSTALL_LIBDIR}"
+ "${config}"
+ sbom_args
+ )
+ endforeach()
+
+ _qt_internal_forward_function_args(
+ FORWARD_APPEND
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR sbom_args
+ FORWARD_SINGLE
+ ${sbom_single_args}
+ FORWARD_MULTI
+ ${sbom_multi_args}
+ )
+
+ _qt_internal_extend_sbom(${target} ${sbom_args})
+ endif()
+
+ qt_add_list_file_finalizer(qt_internal_finalize_3rdparty_library ${target})
+endfunction()
+
+function(qt_internal_finalize_3rdparty_library target)
+ _qt_internal_finalize_sbom(${target})
endfunction()
function(qt_install_3rdparty_library_wrap_config_extra_file target)
diff --git a/cmake/QtAppHelpers.cmake b/cmake/QtAppHelpers.cmake
index f0dbd110ab1..1220680688e 100644
--- a/cmake/QtAppHelpers.cmake
+++ b/cmake/QtAppHelpers.cmake
@@ -4,10 +4,27 @@
# This function creates a CMake target for a Qt internal app.
# Such projects had a load(qt_app) command.
function(qt_internal_add_app target)
+ set(option_args
+ NO_INSTALL
+ INSTALL_VERSIONED_LINK
+ EXCEPTIONS
+ NO_UNITY_BUILD
+ )
+ set(single_args
+ ${__default_target_info_args}
+ ${__qt_internal_sbom_single_args}
+ INSTALL_DIR
+ )
+ set(multi_args
+ ${__default_private_args}
+ ${__qt_internal_sbom_multi_args}
+ PUBLIC_LIBRARIES
+ )
+
cmake_parse_arguments(PARSE_ARGV 1 arg
- "NO_INSTALL;INSTALL_VERSIONED_LINK;EXCEPTIONS;NO_UNITY_BUILD"
- "${__default_target_info_args};INSTALL_DIR"
- "${__default_private_args};PUBLIC_LIBRARIES"
+ "${option_args}"
+ "${single_args}"
+ "${multi_args}"
)
_qt_internal_validate_all_args_are_parsed(arg)
@@ -67,6 +84,10 @@ function(qt_internal_add_app target)
MOC_OPTIONS ${arg_MOC_OPTIONS}
ENABLE_AUTOGEN_TOOLS ${arg_ENABLE_AUTOGEN_TOOLS}
DISABLE_AUTOGEN_TOOLS ${arg_DISABLE_AUTOGEN_TOOLS}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
TARGET_VERSION ${arg_TARGET_VERSION}
TARGET_PRODUCT ${arg_TARGET_PRODUCT}
TARGET_DESCRIPTION ${arg_TARGET_DESCRIPTION}
@@ -96,6 +117,38 @@ function(qt_internal_add_app target)
TARGETS ${target})
endif()
+ if(QT_GENERATE_SBOM)
+ set(sbom_args "")
+ list(APPEND sbom_args TYPE QT_APP)
+
+ qt_get_cmake_configurations(cmake_configs)
+ foreach(cmake_config IN LISTS cmake_configs)
+ qt_get_install_target_default_args(
+ OUT_VAR unused_install_targets_default_args
+ OUT_VAR_RUNTIME runtime_install_destination
+ RUNTIME "${arg_INSTALL_DIR}"
+ CMAKE_CONFIG "${cmake_config}"
+ ALL_CMAKE_CONFIGS ${cmake_configs})
+
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ RUNTIME_PATH
+ "${runtime_install_destination}"
+ "${cmake_config}"
+ sbom_args
+ )
+ endforeach()
+
+ _qt_internal_forward_function_args(
+ FORWARD_APPEND
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR sbom_args
+ FORWARD_OPTIONS
+ NO_INSTALL
+ )
+
+ _qt_internal_extend_sbom(${target} ${sbom_args})
+ endif()
+
qt_add_list_file_finalizer(qt_internal_finalize_app ${target})
endfunction()
@@ -143,4 +196,5 @@ function(qt_internal_finalize_app target)
# set after a qt_internal_add_app call.
qt_apply_rpaths(TARGET "${target}" INSTALL_PATH "${INSTALL_BINDIR}" RELATIVE_RPATH)
qt_internal_apply_staging_prefix_build_rpath_workaround()
+ _qt_internal_finalize_sbom(${target})
endfunction()
diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake
index 1e604559ed7..bf8be498a2a 100644
--- a/cmake/QtBaseGlobalTargets.cmake
+++ b/cmake/QtBaseGlobalTargets.cmake
@@ -133,6 +133,10 @@ target_include_directories(GlobalConfigPrivate INTERFACE
)
add_library(Qt::GlobalConfigPrivate ALIAS GlobalConfigPrivate)
add_library(${QT_CMAKE_EXPORT_NAMESPACE}::GlobalConfigPrivate ALIAS GlobalConfigPrivate)
+qt_internal_add_sbom(GlobalConfigPrivate
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
qt_internal_setup_public_platform_target()
diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake
index cce57cd5f6e..e101950cc04 100644
--- a/cmake/QtBuildHelpers.cmake
+++ b/cmake/QtBuildHelpers.cmake
@@ -196,6 +196,7 @@ function(qt_internal_get_qt_build_private_helpers out_var)
QtResourceHelpers
QtRpathHelpers
QtSanitizerHelpers
+ QtSbomHelpers
QtScopeFinalizerHelpers
QtSeparateDebugInfo
QtSimdHelpers
@@ -278,7 +279,10 @@ function(qt_internal_get_qt_build_public_helpers out_var)
QtPublicExternalProjectHelpers
QtPublicFinalizerHelpers
QtPublicFindPackageHelpers
+ QtPublicGitHelpers
QtPublicPluginHelpers
+ QtPublicSbomGenerationHelpers
+ QtPublicSbomHelpers
QtPublicTargetHelpers
QtPublicTestHelpers
QtPublicToolHelpers
diff --git a/cmake/QtBuildPathsHelpers.cmake b/cmake/QtBuildPathsHelpers.cmake
index 6431fa19375..21193db89a7 100644
--- a/cmake/QtBuildPathsHelpers.cmake
+++ b/cmake/QtBuildPathsHelpers.cmake
@@ -193,6 +193,8 @@ macro(qt_internal_setup_configure_install_paths)
qt_configure_process_path(INSTALL_DESCRIPTIONSDIR
"${INSTALL_ARCHDATADIR}/modules"
"Module description files directory")
+ qt_configure_process_path(INSTALL_SBOMDIR "${INSTALL_ARCHDATADIR}/sbom"
+ "SBOM [PREFIX/sbom]")
endmacro()
macro(qt_internal_set_cmake_install_libdir)
diff --git a/cmake/QtBuildRepoHelpers.cmake b/cmake/QtBuildRepoHelpers.cmake
index 8bd0615090b..f35a2adb168 100644
--- a/cmake/QtBuildRepoHelpers.cmake
+++ b/cmake/QtBuildRepoHelpers.cmake
@@ -308,6 +308,12 @@ macro(qt_build_repo_begin)
if(QT_INTERNAL_SYNCED_MODULES)
set_property(GLOBAL PROPERTY _qt_synced_modules ${QT_INTERNAL_SYNCED_MODULES})
endif()
+
+ _qt_internal_sbom_begin_project(
+ INSTALL_PREFIX "${QT_STAGING_PREFIX}"
+ INSTALL_SBOM_DIR "${INSTALL_SBOMDIR}"
+ QT_CPE
+ )
endmacro()
# Runs delayed actions on some of the Qt targets.
@@ -371,6 +377,8 @@ macro(qt_build_repo_end)
set(QT_INTERNAL_FRESH_REQUESTED "FALSE" CACHE INTERNAL "")
endif()
+ _qt_internal_sbom_end_project()
+
if(NOT QT_SUPERBUILD)
qt_internal_qt_configure_end()
endif()
diff --git a/cmake/QtExecutableHelpers.cmake b/cmake/QtExecutableHelpers.cmake
index 08da17b7e00..2e447a601d5 100644
--- a/cmake/QtExecutableHelpers.cmake
+++ b/cmake/QtExecutableHelpers.cmake
@@ -138,6 +138,10 @@ function(qt_internal_add_executable name)
MOC_OPTIONS ${arg_MOC_OPTIONS}
ENABLE_AUTOGEN_TOOLS ${arg_ENABLE_AUTOGEN_TOOLS}
DISABLE_AUTOGEN_TOOLS ${arg_DISABLE_AUTOGEN_TOOLS}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
)
set_target_properties("${name}" PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${arg_OUTPUT_DIRECTORY}"
diff --git a/cmake/QtFindPackageHelpers.cmake b/cmake/QtFindPackageHelpers.cmake
index af6057bf51f..b71226f322b 100644
--- a/cmake/QtFindPackageHelpers.cmake
+++ b/cmake/QtFindPackageHelpers.cmake
@@ -233,8 +233,25 @@ macro(qt_find_package)
qt_find_package_promote_targets_to_global_scope(
"${qt_find_package_target_name}")
endif()
- endif()
+ set(_qt_find_package_sbom_args "")
+
+ if(_qt_find_package_found_version)
+ list(APPEND _qt_find_package_sbom_args
+ PACKAGE_VERSION "${_qt_find_package_found_version}"
+ )
+ endif()
+
+ # Work around: QTBUG-125371
+ if(NOT "${ARGV0}" STREQUAL "Qt6")
+ _qt_internal_sbom_record_system_library_usage(
+ "${qt_find_package_target_name}"
+ TYPE SYSTEM_LIBRARY
+ FRIENDLY_PACKAGE_NAME "${ARGV0}"
+ ${_qt_find_package_sbom_args}
+ )
+ endif()
+ endif()
endforeach()
if(arg_MODULE_NAME AND arg_QMAKE_LIB
diff --git a/cmake/QtInternalTargets.cmake b/cmake/QtInternalTargets.cmake
index d7eadc1a73a..21670973246 100644
--- a/cmake/QtInternalTargets.cmake
+++ b/cmake/QtInternalTargets.cmake
@@ -138,22 +138,42 @@ endfunction()
add_library(PlatformCommonInternal INTERFACE)
qt_internal_add_target_aliases(PlatformCommonInternal)
target_link_libraries(PlatformCommonInternal INTERFACE Platform)
+qt_internal_add_sbom(PlatformCommonInternal
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
add_library(PlatformModuleInternal INTERFACE)
qt_internal_add_target_aliases(PlatformModuleInternal)
target_link_libraries(PlatformModuleInternal INTERFACE PlatformCommonInternal)
+qt_internal_add_sbom(PlatformModuleInternal
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
add_library(PlatformPluginInternal INTERFACE)
qt_internal_add_target_aliases(PlatformPluginInternal)
target_link_libraries(PlatformPluginInternal INTERFACE PlatformCommonInternal)
+qt_internal_add_sbom(PlatformPluginInternal
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
add_library(PlatformAppInternal INTERFACE)
qt_internal_add_target_aliases(PlatformAppInternal)
target_link_libraries(PlatformAppInternal INTERFACE PlatformCommonInternal)
+qt_internal_add_sbom(PlatformAppInternal
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
add_library(PlatformToolInternal INTERFACE)
qt_internal_add_target_aliases(PlatformToolInternal)
target_link_libraries(PlatformToolInternal INTERFACE PlatformAppInternal)
+qt_internal_add_sbom(PlatformToolInternal
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+)
qt_internal_add_global_definition(QT_NO_JAVA_STYLE_ITERATORS)
qt_internal_add_global_definition(QT_NO_QASCONST)
diff --git a/cmake/QtModuleHelpers.cmake b/cmake/QtModuleHelpers.cmake
index ba60cdef444..0a6084a157e 100644
--- a/cmake/QtModuleHelpers.cmake
+++ b/cmake/QtModuleHelpers.cmake
@@ -33,6 +33,7 @@ macro(qt_internal_get_internal_add_module_keywords option_args single_args multi
SSG_HEADER_FILTERS
HEADER_SYNC_SOURCE_DIRECTORY
${__default_target_info_args}
+ ${__qt_internal_sbom_single_args}
)
set(${multi_args}
QMAKE_MODULE_CONFIG
@@ -43,6 +44,7 @@ macro(qt_internal_get_internal_add_module_keywords option_args single_args multi
${__default_private_args}
${__default_public_args}
${__default_private_module_args}
+ ${__qt_internal_sbom_multi_args}
)
endmacro()
@@ -235,6 +237,10 @@ function(qt_internal_add_module target)
set_target_properties(${target} PROPERTIES _qt_is_internal_module TRUE)
set_property(TARGET ${target} APPEND PROPERTY EXPORT_PROPERTIES _qt_is_internal_module)
endif()
+ if(arg_HEADER_MODULE)
+ set_target_properties(${target} PROPERTIES _qt_is_header_module TRUE)
+ set_property(TARGET ${target} APPEND PROPERTY EXPORT_PROPERTIES _qt_is_header_module)
+ endif()
if(NOT arg_CONFIG_MODULE_NAME)
set(arg_CONFIG_MODULE_NAME "${module_lower}")
@@ -635,6 +641,10 @@ function(qt_internal_add_module target)
DISABLE_AUTOGEN_TOOLS ${arg_DISABLE_AUTOGEN_TOOLS}
PRECOMPILED_HEADER ${arg_PRECOMPILED_HEADER}
NO_PCH_SOURCES ${arg_NO_PCH_SOURCES}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
)
# The public module define is not meant to be used when building the module itself,
@@ -909,6 +919,43 @@ set(QT_ALLOW_MISSING_TOOLS_PACKAGES TRUE)")
endif()
qt_describe_module(${target})
+
+ set(sbom_args "")
+
+ if(QT_GENERATE_SBOM)
+ list(APPEND sbom_args TYPE QT_MODULE)
+
+ qt_get_cmake_configurations(configs)
+ foreach(config IN LISTS configs)
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ RUNTIME_PATH
+ "${INSTALL_BINDIR}"
+ "${config}"
+ sbom_args
+ )
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ LIBRARY_PATH
+ "${INSTALL_LIBDIR}"
+ "${config}"
+ sbom_args
+ )
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ ARCHIVE_PATH
+ "${INSTALL_LIBDIR}"
+ "${config}"
+ sbom_args
+ )
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ FRAMEWORK_PATH
+ "${INSTALL_LIBDIR}/${fw_versioned_binary_dir}"
+ "${config}"
+ sbom_args
+ )
+ endforeach()
+
+ _qt_internal_extend_sbom(${target} ${sbom_args})
+ endif()
+
qt_add_list_file_finalizer(qt_finalize_module ${target} ${arg_INTERNAL_MODULE} ${arg_NO_PRIVATE_MODULE})
endfunction()
@@ -958,6 +1005,7 @@ function(qt_finalize_module target)
qt_generate_module_pri_file("${target}" ${ARGN})
qt_internal_generate_pkg_config_file(${target})
qt_internal_apply_apple_privacy_manifest(${target})
+ _qt_internal_finalize_sbom(${target})
endfunction()
# Get a set of Qt module related values based on the target.
diff --git a/cmake/QtPlatformTargetHelpers.cmake b/cmake/QtPlatformTargetHelpers.cmake
index f1976b9975a..a89239b92ac 100644
--- a/cmake/QtPlatformTargetHelpers.cmake
+++ b/cmake/QtPlatformTargetHelpers.cmake
@@ -72,6 +72,11 @@ function(qt_internal_setup_public_platform_target)
# Generate a pkgconfig for Qt::Platform.
qt_internal_generate_pkg_config_file(Platform)
+
+ qt_internal_add_sbom(Platform
+ TYPE QT_MODULE
+ IMMEDIATE_FINALIZATION
+ )
endfunction()
function(qt_internal_get_platform_definition_include_dir install_interface build_interface)
diff --git a/cmake/QtPluginHelpers.cmake b/cmake/QtPluginHelpers.cmake
index a4188b72897..46953d89cb7 100644
--- a/cmake/QtPluginHelpers.cmake
+++ b/cmake/QtPluginHelpers.cmake
@@ -15,10 +15,12 @@ macro(qt_internal_get_internal_add_plugin_keywords option_args single_args multi
INSTALL_DIRECTORY
ARCHIVE_INSTALL_DIRECTORY
${__default_target_info_args}
+ ${__qt_internal_sbom_single_args}
)
set(${multi_args}
${__default_private_args}
${__default_public_args}
+ ${__qt_internal_sbom_multi_args}
DEFAULT_IF
)
endmacro()
@@ -312,6 +314,10 @@ function(qt_internal_add_plugin target)
MOC_OPTIONS ${arg_MOC_OPTIONS}
ENABLE_AUTOGEN_TOOLS ${arg_ENABLE_AUTOGEN_TOOLS}
DISABLE_AUTOGEN_TOOLS ${arg_DISABLE_AUTOGEN_TOOLS}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
)
qt_internal_add_repo_local_defines("${target}")
@@ -414,6 +420,24 @@ function(qt_internal_add_plugin target)
if(NOT arg_SKIP_INSTALL)
list(APPEND finalizer_extra_args INSTALL_PATH "${install_directory}")
endif()
+
+ if(QT_GENERATE_SBOM)
+ set(sbom_args "")
+ list(APPEND sbom_args TYPE QT_PLUGIN)
+
+ qt_get_cmake_configurations(configs)
+ foreach(config IN LISTS configs)
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ INSTALL_PATH
+ "${install_directory}"
+ "${config}"
+ sbom_args
+ )
+ endforeach()
+
+ _qt_internal_extend_sbom(${target} ${sbom_args})
+ endif()
+
qt_add_list_file_finalizer(qt_finalize_plugin ${target} ${finalizer_extra_args})
if(NOT arg_SKIP_INSTALL)
@@ -442,6 +466,8 @@ function(qt_finalize_plugin target)
qt_generate_plugin_pri_file("${target}")
endif()
endif()
+
+ _qt_internal_finalize_sbom(${target})
endfunction()
function(qt_get_sanitized_plugin_type plugin_type out_var)
diff --git a/cmake/QtPostProcessHelpers.cmake b/cmake/QtPostProcessHelpers.cmake
index 9654b186645..5cbee283b66 100644
--- a/cmake/QtPostProcessHelpers.cmake
+++ b/cmake/QtPostProcessHelpers.cmake
@@ -574,7 +574,7 @@ function(qt_generate_install_prefixes out_var)
set(vars INSTALL_BINDIR INSTALL_INCLUDEDIR INSTALL_LIBDIR INSTALL_MKSPECSDIR INSTALL_ARCHDATADIR
INSTALL_PLUGINSDIR INSTALL_LIBEXECDIR INSTALL_QMLDIR INSTALL_DATADIR INSTALL_DOCDIR
INSTALL_TRANSLATIONSDIR INSTALL_SYSCONFDIR INSTALL_EXAMPLESDIR INSTALL_TESTSDIR
- INSTALL_DESCRIPTIONSDIR)
+ INSTALL_DESCRIPTIONSDIR INSTALL_SBOMDIR)
foreach(var ${vars})
get_property(docstring CACHE "${var}" PROPERTY HELPSTRING)
diff --git a/cmake/QtProcessConfigureArgs.cmake b/cmake/QtProcessConfigureArgs.cmake
index df0dfe48de5..94bf5f4bff1 100644
--- a/cmake/QtProcessConfigureArgs.cmake
+++ b/cmake/QtProcessConfigureArgs.cmake
@@ -150,6 +150,8 @@ while(NOT "${configure_args}" STREQUAL "")
elseif(arg STREQUAL "-no-prefix")
set(no_prefix_option TRUE)
push("-DFEATURE_no_prefix=ON")
+ elseif(arg STREQUAL "-sbom")
+ push("-DQT_GENERATE_SBOM=ON")
elseif(arg STREQUAL "-cmake-file-api")
set(cmake_file_api TRUE)
elseif(arg STREQUAL "-no-cmake-file-api")
@@ -261,6 +263,11 @@ defstub(set_package_properties)
defstub(qt_qml_find_python)
defstub(qt_set01)
defstub(qt_internal_check_if_linker_is_available)
+defstub(qt_internal_add_sbom)
+defstub(qt_internal_extend_sbom)
+defstub(qt_internal_sbom_add_license)
+defstub(qt_internal_extend_sbom_dependencies)
+defstub(qt_find_package_extend_sbom)
####################################################################################################
# Define functions/macros that are called in qt_cmdline.cmake files
@@ -934,6 +941,7 @@ endforeach()
translate_path_input(headerdir INSTALL_INCLUDEDIR)
translate_path_input(plugindir INSTALL_PLUGINSDIR)
translate_path_input(translationdir INSTALL_TRANSLATIONSDIR)
+translate_path_input(sbomdir INSTALL_SBOMDIR)
if(NOT "${INPUT_device}" STREQUAL "")
push("-DQT_QMAKE_TARGET_MKSPEC=devices/${INPUT_device}")
diff --git a/cmake/QtPublicCMakeHelpers.cmake b/cmake/QtPublicCMakeHelpers.cmake
index 31e7591b990..bb4601d81db 100644
--- a/cmake/QtPublicCMakeHelpers.cmake
+++ b/cmake/QtPublicCMakeHelpers.cmake
@@ -434,6 +434,10 @@ function(_qt_internal_create_versionless_targets targets install_namespace)
_qt_package_name
_qt_package_version
_qt_private_module_target_name
+ _qt_sbom_spdx_id
+ _qt_sbom_spdx_repo_document_namespace
+ _qt_sbom_spdx_relative_installed_repo_document_path
+ _qt_sbom_spdx_repo_project_name_lowercase
)
set(supported_target_types STATIC_LIBRARY MODULE_LIBRARY SHARED_LIBRARY OBJECT_LIBRARY
diff --git a/cmake/QtPublicDependencyHelpers.cmake b/cmake/QtPublicDependencyHelpers.cmake
index bd8b4a55c42..f426cbc0b48 100644
--- a/cmake/QtPublicDependencyHelpers.cmake
+++ b/cmake/QtPublicDependencyHelpers.cmake
@@ -35,6 +35,37 @@ macro(_qt_internal_find_third_party_dependencies target target_dep_list)
else()
find_dependency(${__qt_${target}_find_package_args})
endif()
+
+ _qt_internal_get_package_components_id(
+ PACKAGE_NAME "${__qt_${target}_pkg}"
+ COMPONENTS ${__qt_${target}_components}
+ OPTIONAL_COMPONENTS ${__qt_${target}_optional_components}
+ OUT_VAR_KEY __qt_${target}_package_components_id
+ )
+ if(${__qt_${target}_pkg}_FOUND
+ AND __qt_${target}_third_party_package_${__qt_${target}_package_components_id}_provided_targets)
+ set(__qt_${target}_sbom_args "")
+
+ if(${__qt_${target}_pkg}_VERSION)
+ list(APPEND __qt_${target}_sbom_args
+ PACKAGE_VERSION "${${__qt_${target}_pkg}_VERSION}"
+ )
+ endif()
+
+ # Work around: QTBUG-125371
+ if(NOT "${ARGV0}" STREQUAL "Qt6")
+ foreach(__qt_${target}_provided_target
+ IN LISTS
+ __qt_${target}_third_party_package_${__qt_${target}_package_components_id}_provided_targets)
+ _qt_internal_sbom_record_system_library_usage(
+ "${__qt_${target}_provided_target}"
+ TYPE SYSTEM_LIBRARY
+ FRIENDLY_PACKAGE_NAME "${__qt_${target}_pkg}"
+ ${__qt_${target}_sbom_args}
+ )
+ endforeach()
+ endif()
+ endif()
endforeach()
endmacro()
diff --git a/cmake/QtPublicGitHelpers.cmake b/cmake/QtPublicGitHelpers.cmake
new file mode 100644
index 00000000000..2326454a15f
--- /dev/null
+++ b/cmake/QtPublicGitHelpers.cmake
@@ -0,0 +1,153 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# Copyright (C) 2023-2024 Jochem Rutgers
+# SPDX-License-Identifier: BSD-3-Clause AND MIT
+
+macro(_qt_internal_find_git_package)
+ find_package(Git)
+endmacro()
+
+# Helper to set the various git version variables in the parent scope across multiple return points.
+macro(_qt_internal_set_git_query_variables)
+ set("${arg_OUT_VAR_PREFIX}git_hash" "${version_git_hash}" PARENT_SCOPE)
+ set("${arg_OUT_VAR_PREFIX}git_hash_short" "${version_git_head}" PARENT_SCOPE)
+ set("${arg_OUT_VAR_PREFIX}git_version" "${git_version}" PARENT_SCOPE)
+
+ # git version sanitized for file paths.
+ string(REGEX REPLACE "[^-a-zA-Z0-9_.]+" "+" git_version_path "${git_version}")
+ set("${arg_OUT_VAR_PREFIX}git_version_path" "${git_version_path}" PARENT_SCOPE)
+endmacro()
+
+# Caches the results per working-directory in global cmake properties.
+# Sets the following variables in the outer scope:
+# - git_hash: Full git hash.
+# - git_hash_short: Short git hash.
+# - git_version: Git version string.
+# - git_version_path: Git version string sanitized for file paths.
+function(_qt_internal_query_git_version)
+ set(opt_args
+ EMPTY_VALUE_WHEN_NOT_GIT_REPO
+ )
+ set(single_args
+ WORKING_DIRECTORY
+ OUT_VAR_PREFIX
+ )
+ 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_EMPTY_VALUE_WHEN_NOT_GIT_REPO)
+ set(version_git_head "")
+ set(version_git_hash "")
+ set(version_git_branch "")
+ set(version_git_tag "")
+ set(git_version "")
+ else()
+ set(version_git_head "unknown")
+ set(version_git_hash "")
+ set(version_git_branch "dev")
+ set(version_git_tag "")
+ set(git_version "${version_git_head}+${version_git_branch}")
+ endif()
+
+ if(NOT Git_FOUND)
+ message(STATUS "Git not found, skipping querying git version.")
+ _qt_internal_set_git_query_variables()
+ return()
+ endif()
+
+ if(arg_WORKING_DIRECTORY)
+ set(working_directory "${arg_WORKING_DIRECTORY}")
+ else()
+ set(working_directory "${PROJECT_SOURCE_DIR}")
+ endif()
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse --is-inside-work-tree
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE is_inside_work_tree_output
+ RESULT_VARIABLE is_inside_work_tree_result
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ if((NOT is_inside_work_tree_result EQUAL 0) OR (NOT is_inside_work_tree_output STREQUAL "true"))
+ message(STATUS "Git repo not found, skipping querying git version.")
+ _qt_internal_set_git_query_variables()
+ return()
+ endif()
+
+ get_cmake_property(git_hash_cache _qt_git_hash_cache_${working_directory})
+ get_cmake_property(git_hash_short_cache _qt_git_hash_short_cache_${working_directory})
+ get_cmake_property(git_version_cache _qt_git_version_cache_${working_directory})
+ get_cmake_property(git_version_path_cache _qt_git_version_path_cache_${working_directory})
+ if(git_hash_cache)
+ set(git_hash "${git_hash_cache}")
+ set(git_hash_short "${git_hash_short_cache}")
+ set(git_version "${git_version_cache}")
+ set(git_version_path "${git_version_path_cache}")
+ _qt_internal_set_git_query_variables()
+ return()
+ endif()
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE version_git_head
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE version_git_hash
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE version_git_branch
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} tag --points-at HEAD
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE version_git_tag
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ string(REGEX REPLACE "[ \t\r\n].*$" "" version_git_tag "${version_git_tag}")
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} status -s
+ WORKING_DIRECTORY "${working_directory}"
+ OUTPUT_VARIABLE version_git_dirty
+ ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ if(NOT "${version_git_dirty}" STREQUAL "")
+ set(version_git_dirty "+dirty")
+ endif()
+
+ if(NOT "${version_git_tag}" STREQUAL "")
+ set(git_version "${version_git_tag}")
+
+ if("${git_version}" MATCHES "^v[0-9]+\.")
+ string(REGEX REPLACE "^v" "" git_version "${git_version}")
+ endif()
+
+ set(git_version "${git_version}${version_git_dirty}")
+ else()
+ set(git_version
+ "${version_git_head}+${version_git_branch}${version_git_dirty}"
+ )
+ endif()
+
+ set_property(GLOBAL PROPERTY _qt_git_hash_cache_${working_directory} "${git_hash}")
+ set_property(GLOBAL PROPERTY _qt_git_hash_short_cache_${working_directory} "${git_hash_short}")
+ set_property(GLOBAL PROPERTY _qt_git_version_cache_${working_directory} "${git_version}")
+ set_property(GLOBAL PROPERTY _qt_git_version_path_cache_${working_directory}
+ "${git_version_path}")
+
+ _qt_internal_set_git_query_variables()
+endfunction()
diff --git a/cmake/QtPublicSbomGenerationHelpers.cmake b/cmake/QtPublicSbomGenerationHelpers.cmake
new file mode 100644
index 00000000000..0ada3ce938c
--- /dev/null
+++ b/cmake/QtPublicSbomGenerationHelpers.cmake
@@ -0,0 +1,1139 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# Copyright (C) 2023-2024 Jochem Rutgers
+# SPDX-License-Identifier: MIT AND BSD-3-Clause
+
+# Helper to set a single arg option to a default value if not set.
+function(qt_internal_sbom_set_default_option_value option_name default)
+ if(NOT arg_${option_name})
+ set(arg_${option_name} "${default}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Helper to set a single arg option to a default value if not set.
+# Errors out if the end value is empty. Including if the default value was empty.
+function(qt_internal_sbom_set_default_option_value_and_error_if_empty option_name default)
+ qt_internal_sbom_set_default_option_value("${option_name}" "${default}")
+ if(NOT arg_${option_name})
+ message(FATAL_ERROR "Specifying a non-empty ${option_name} is required")
+ endif()
+endfunction()
+
+# Computes the current platform CPE.
+# Mostly matches the OS and architecture.
+function(_qt_internal_sbom_get_platform_cpe out_var)
+ set(cpe "")
+
+ if(CMAKE_SYSTEM_PROCESSOR)
+ set(system_processor "${CMAKE_SYSTEM_PROCESSOR}")
+ else()
+ set(system_processor "*")
+ endif()
+
+ if(WIN32)
+ if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64")
+ set(arch "x64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64")
+ set(arch "x64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ARM64")
+ set(arch "arm64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "X86")
+ set(arch "x86")
+ elseif(CMAKE_CXX_COMPILER MATCHES "64")
+ set(arch "x64")
+ elseif(CMAKE_CXX_COMPILER MATCHES "86")
+ set(arch "x86")
+ else()
+ set(arch "*")
+ endif()
+
+ if("${CMAKE_SYSTEM_VERSION}" STREQUAL "6.1")
+ set(cpe "cpe:2.3:o:microsoft:windows_7:-:*:*:*:*:*:${arch}:*")
+ elseif("${CMAKE_SYSTEM_VERSION}" STREQUAL "6.2")
+ set(cpe "cpe:2.3:o:microsoft:windows_8:-:*:*:*:*:*:${arch}:*")
+ elseif("${CMAKE_SYSTEM_VERSION}" STREQUAL "6.3")
+ set(cpe "cpe:2.3:o:microsoft:windows_8.1:-:*:*:*:*:*:${arch}:*")
+ elseif("${CMAKE_SYSTEM_VERSION}" GREATER_EQUAL 10)
+ set(cpe "cpe:2.3:o:microsoft:windows_10:-:*:*:*:*:*:${arch}:*")
+ else()
+ set(cpe "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:${arch}:*")
+ endif()
+ elseif(APPLE)
+ set(cpe "cpe:2.3:o:apple:mac_os:*:*:*:*:*:*:${system_processor}:*")
+ elseif(UNIX)
+ set(cpe "cpe:2.3:o:*:*:-:*:*:*:*:*:${system_processor}:*")
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
+ set(cpe "cpe:2.3:o:arm:arm:-:*:*:*:*:*:*:*")
+ else()
+ message(DEBUG "Can't compute CPE for unsupported platform")
+ set(cpe "cpe:2.3:o:*:*:-:*:*:*:*:*:*:*")
+ endif()
+
+ set(${out_var} "${cpe}" PARENT_SCOPE)
+endfunction()
+
+# Helper that returns the directory where the intermediate sbom files will be generated.
+function(_qt_internal_get_current_project_sbom_dir out_var)
+ set(sbom_dir "${PROJECT_BINARY_DIR}/qt_sbom")
+ set(${out_var} "${sbom_dir}" PARENT_SCOPE)
+endfunction()
+
+# Helper to return the path to staging spdx file, where content will be incrementally appended to.
+function(_qt_internal_get_staging_area_spdx_file_path out_var)
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(staging_area_spdx_file "${sbom_dir}/staging-${repo_project_name_lowercase}.spdx.in")
+ set(${out_var} "${staging_area_spdx_file}" PARENT_SCOPE)
+endfunction()
+
+# Starts recording information for the generation of an sbom for a project.
+# The intermediate files that generate the sbom are generated at cmake generation time, but are only
+# actually run at build time or install time.
+# The files are tracked in cmake global properties.
+function(_qt_internal_sbom_begin_project_generate)
+ set(opt_args "")
+ set(single_args
+ OUTPUT
+ LICENSE
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ PROJECT
+ PROJECT_FOR_SPDX_ID
+ SUPPLIER
+ SUPPLIER_URL
+ NAMESPACE
+ CPE
+ OUT_VAR_PROJECT_SPDX_ID
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ string(TIMESTAMP current_utc UTC)
+ string(TIMESTAMP current_year "%Y" UTC)
+
+ qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
+
+ set(default_sbom_file_name
+ "${arg_PROJECT}/${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.spdx")
+ set(default_install_sbom_path
+ "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/${default_sbom_file_name}")
+
+ qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
+
+ qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX "${PROJECT_NAME}")
+ qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
+ qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
+ qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
+ "${PROJECT_HOMEPAGE_URL}")
+ qt_internal_sbom_set_default_option_value(NAMESPACE
+ "${arg_SUPPLIER}/spdxdocs/${arg_PROJECT}-${QT_SBOM_GIT_VERSION}")
+
+ if(arg_CPE)
+ set(QT_SBOM_CPE "${arg_CPE}")
+ else()
+ _qt_internal_sbom_get_platform_cpe(platform_cpe)
+ set(QT_SBOM_CPE "${platform_cpe}")
+ endif()
+
+ string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+ string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+ # 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}")
+ if(arg_OUT_VAR_PROJECT_SPDX_ID)
+ set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+ endif()
+
+ get_filename_component(doc_name "${arg_OUTPUT}" NAME_WLE)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
+ else()
+ set(cmake_configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
+
+ set(content
+ "SPDXVersion: SPDX-2.3
+DataLicense: CC0-1.0
+SPDXID: SPDXRef-DOCUMENT
+DocumentName: ${doc_name}
+DocumentNamespace: ${arg_NAMESPACE}
+Creator: Organization: ${arg_SUPPLIER}
+Creator: Tool: Qt Build System
+CreatorComment: This SPDX document was created from CMake ${CMAKE_VERSION}, using the qt
+build system from https://code.qt.io/cgit/qt/qtbase.git/tree/cmake/QtPublicSbomHelpers.cmake
+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: The compiler as identified by CMake, running on ${CMAKE_HOST_SYSTEM_NAME} (${CMAKE_HOST_SYSTEM_PROCESSOR})
+PrimaryPackagePurpose: APPLICATION
+Relationship: SPDXRef-compiler BUILD_DEPENDENCY_OF ${project_spdx_id}
+RelationshipComment: ${project_spdx_id} is built by compiler ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) version ${CMAKE_CXX_COMPILER_VERSION}
+
+PackageName: ${arg_PROJECT}
+SPDXID: ${project_spdx_id}
+ExternalRef: SECURITY cpe23Type ${QT_SBOM_CPE}
+ExternalRef: PACKAGE-MANAGER purl pkg:generic/${arg_SUPPLIER}/${arg_PROJECT}@${QT_SBOM_GIT_VERSION}
+PackageVersion: ${QT_SBOM_GIT_VERSION}
+PackageSupplier: Organization: ${arg_SUPPLIER}
+PackageDownloadLocation: ${arg_DOWNLOAD_LOCATION}
+PackageLicenseConcluded: ${arg_LICENSE}
+PackageLicenseDeclared: ${arg_LICENSE}
+PackageCopyrightText: ${arg_COPYRIGHT}
+PackageHomePage: ${arg_SUPPLIER_URL}
+PackageComment: Built by CMake ${CMAKE_VERSION} with ${cmake_configs} configuration for ${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})
+PackageVerificationCode: \${QT_SBOM_VERIFICATION_CODE}
+BuiltDate: ${current_utc}
+Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
+")
+
+ # Create the directory that will contain all sbom related files.
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ file(MAKE_DIRECTORY "${sbom_dir}")
+
+ # Generate project document intro spdx file.
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(document_intro_file_name
+ "${sbom_dir}/SPDXRef-DOCUMENT-${repo_project_name_lowercase}.spdx.in")
+ file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
+
+ # This is the file that will be incrementally assembled by having content appended to it.
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ get_filename_component(computed_sbom_file_name "${arg_OUTPUT}" NAME_WLE)
+ get_filename_component(computed_sbom_file_name_ext "${arg_OUTPUT}" LAST_EXT)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ set(computed_sbom_file_name
+ "${computed_sbom_file_name}${multi_config_suffix}${computed_sbom_file_name_ext}")
+
+ set(build_sbom_path "${sbom_dir}/${computed_sbom_file_name}")
+
+ # Create cmake file to append the document intro spdx to the staging file.
+ set(create_staging_file "${sbom_dir}/append_document_to_staging${multi_config_suffix}.cmake")
+ set(content "
+ cmake_minimum_required(VERSION 3.16)
+ message(STATUS \"Starting SBOM generation in build dir: ${staging_area_spdx_file}\")
+ set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
+ file(READ \"${document_intro_file_name}\" content)
+ # Override any previous file because we're starting from scratch.
+ file(WRITE \"${staging_area_spdx_file}\" \"\${content}\")
+")
+ file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_path "${build_sbom_path}")
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_path "${arg_OUTPUT}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${create_staging_file}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count 0)
+endfunction()
+
+# Signals the end of recording sbom information for a project.
+# 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.
+# Also allows running various post-installation steps like NTIA validation, auditing, json
+# generation, etc
+function(_qt_internal_sbom_end_project_generate)
+ set(opt_args
+ GENERATE_JSON
+ VERIFY
+ SHOW_TABLE
+ AUDIT
+ AUDIT_NO_ERROR
+ )
+ 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)
+
+ get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path)
+ get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path)
+
+ if(NOT sbom_build_output_path)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ if((arg_GENERATE_JSON OR arg_VERIFY) AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ _qt_internal_sbom_find_python()
+ _qt_internal_sbom_find_python_dependencies()
+ endif()
+
+ if(arg_GENERATE_JSON AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ _qt_internal_sbom_generate_json()
+ endif()
+
+ if(arg_VERIFY AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ _qt_internal_sbom_verify_valid_and_ntia_compliant()
+ endif()
+
+ if(arg_SHOW_TABLE AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ _qt_internal_sbom_find_python_dependency_program(NAME sbom2doc REQUIRED)
+ _qt_internal_sbom_show_table()
+ endif()
+
+ if(arg_AUDIT AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ set(audit_no_error_option "")
+ if(arg_AUDIT_NO_ERROR)
+ set(audit_no_error_option NO_ERROR)
+ endif()
+ _qt_internal_sbom_find_python_dependency_program(NAME sbomaudit REQUIRED)
+ _qt_internal_sbom_audit(${audit_no_error_option})
+ endif()
+
+ get_cmake_property(cmake_include_files _qt_sbom_cmake_include_files)
+ get_cmake_property(cmake_end_include_files _qt_sbom_cmake_end_include_files)
+ get_cmake_property(cmake_verify_include_files _qt_sbom_cmake_verify_include_files)
+
+ set(includes "")
+ if(cmake_include_files)
+ foreach(cmake_include_file IN LISTS cmake_include_files)
+ list(APPEND includes "include(\"${cmake_include_file}\")")
+ endforeach()
+ endif()
+
+ if(cmake_end_include_files)
+ foreach(cmake_include_file IN LISTS cmake_end_include_files)
+ list(APPEND includes "include(\"${cmake_include_file}\")")
+ endforeach()
+ endif()
+
+ list(JOIN includes "\n" includes)
+
+ # Verification only makes sense on installation, where the checksums are present.
+ set(verify_includes "")
+ if(cmake_verify_include_files)
+ foreach(cmake_include_file IN LISTS cmake_verify_include_files)
+ list(APPEND verify_includes "include(\"${cmake_include_file}\")")
+ endforeach()
+ endif()
+ list(JOIN verify_includes "\n" verify_includes)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(content "
+ # 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)
+ set(QT_SBOM_BUILD_TIME TRUE)
+ endif()
+ if(NOT QT_SBOM_OUTPUT_PATH)
+ set(QT_SBOM_OUTPUT_PATH \"${sbom_build_output_path}\")
+ endif()
+ set(QT_SBOM_VERIFICATION_CODES \"\")
+ ${includes}
+ if(QT_SBOM_BUILD_TIME)
+ message(STATUS \"Finalizing SBOM generation in build dir: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ endif()
+")
+ set(assemble_sbom "${sbom_dir}/assemble_sbom${multi_config_suffix}.cmake")
+ file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
+
+ if(NOT TARGET sbom)
+ add_custom_target(sbom)
+ endif()
+
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+
+ # Create a build target to create a build-time sbom (no verification codes or sha1s).
+ set(repo_sbom_target "sbom_${repo_project_name_lowercase}")
+ set(comment "")
+ string(APPEND comment "Assembling build time SPDX document without checksums for "
+ "${repo_project_name_lowercase}. Just for testing.")
+ add_custom_target(${repo_sbom_target}
+ COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
+ COMMENT "${comment}"
+ VERBATIM
+ USES_TERMINAL # To avoid running two configs of the command in parallel
+ )
+ add_dependencies(sbom ${repo_sbom_target})
+
+ set(extra_code_begin "")
+ set(extra_code_inner_end "")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+
+ set(install_markers_dir "${sbom_dir}")
+ set(install_marker_path "${install_markers_dir}/finished_install-$.cmake")
+
+ set(install_marker_code "
+ message(STATUS \"Writing install marker for config $: ${install_marker_path} \")
+ file(WRITE \"${install_marker_path}\" \"\")
+")
+
+ install(CODE "${install_marker_code}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${install_marker_code}" COMPONENT "sbom_${repo_project_name_lowercase}"
+ EXCLUDE_FROM_ALL)
+ endif()
+
+ set(install_markers "")
+ foreach(config IN LISTS configs)
+ set(marker_path "${install_markers_dir}/finished_install-${config}.cmake")
+ list(APPEND install_markers "${marker_path}")
+ # Remove the markers on reconfiguration, just in case there are stale ones.
+ if(EXISTS "${marker_path}")
+ file(REMOVE "${marker_path}")
+ endif()
+ endforeach()
+
+ set(extra_code_begin "
+ set(QT_SBOM_INSTALL_MARKERS \"${install_markers}\")
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
+ if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS FALSE)
+ endif()
+ endforeach()
+")
+ set(extra_code_inner_end "
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
+ message(STATUS
+ \"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
+ file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
+ endforeach()
+")
+ endif()
+
+ # Allow skipping checksum computation for testing purposes, while installing just the sbom
+ # documents, without requiring to build and install all the actual files.
+ if(QT_INTERNAL_SBOM_FAKE_CHECKSUM)
+ string(APPEND extra_code_begin "
+ set(QT_SBOM_FAKE_CHECKSUM TRUE)")
+ endif()
+
+ set(assemble_sbom_install "
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS TRUE)
+ ${extra_code_begin}
+ if(QT_SBOM_INSTALLED_ALL_CONFIGS)
+ set(QT_SBOM_BUILD_TIME FALSE)
+ set(QT_SBOM_OUTPUT_PATH \"${sbom_install_output_path}\")
+ include(\"${assemble_sbom}\")
+ list(SORT QT_SBOM_VERIFICATION_CODES)
+ string(REPLACE \";\" \"\" QT_SBOM_VERIFICATION_CODES \"\${QT_SBOM_VERIFICATION_CODES}\")
+ file(WRITE \"${sbom_dir}/verification.txt\" \"\${QT_SBOM_VERIFICATION_CODES}\")
+ file(SHA1 \"${sbom_dir}/verification.txt\" QT_SBOM_VERIFICATION_CODE)
+ message(STATUS \"Finalizing SBOM generation in install dir: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ ${verify_includes}
+ ${extra_code_inner_end}
+ else()
+ message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
+ endif()
+")
+
+ install(CODE "${assemble_sbom_install}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${repo_project_name_lowercase}"
+ EXCLUDE_FROM_ALL)
+ endif()
+
+ # Clean up properties, so that they are empty for possible next repo in a top-level build.
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files "")
+endfunction()
+
+# Helper to add info about a file to the sbom.
+# Targets are backed by multiple files in multi-config builds. To support multi-config,
+# we generate a -$ file for each config, but we only include / install the one that is
+# specified via the CONFIG option.
+# For build time sboms, we skip checking file existence and sha1 computation, because the files
+# are not installed yet.
+function(_qt_internal_sbom_generate_add_file)
+ set(opt_args
+ OPTIONAL
+ )
+ set(single_args
+ FILENAME
+ FILETYPE
+ RELATIONSHIP
+ SPDXID
+ CONFIG
+ LICENSE
+ COPYRIGHT
+ INSTALL_PREFIX
+ )
+ 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_set_default_option_value_and_error_if_empty(FILENAME "")
+ qt_internal_sbom_set_default_option_value_and_error_if_empty(FILETYPE "")
+
+ _qt_internal_sbom_get_and_check_spdx_id(
+ VARIABLE arg_SPDXID
+ CHECK "${arg_SPDXID}"
+ HINTS "SPDXRef-${arg_FILENAME}"
+ )
+
+ qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(COPYRIGHT "NOASSERTION")
+
+ get_property(sbom_project_name GLOBAL PROPERTY _qt_sbom_project_name)
+ if(NOT sbom_project_name)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+ if(NOT arg_RELATIONSHIP)
+ set(arg_RELATIONSHIP "SPDXRef-${sbom_project_name} CONTAINS ${arg_SPDXID}")
+ else()
+ string(REPLACE
+ "@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
+ endif()
+
+ set(fields "")
+
+ if(arg_LICENSE)
+ set(fields "${fields}
+LicenseConcluded: ${arg_LICENSE}"
+ )
+ else()
+ set(fields "${fields}
+LicenseConcluded: NOASSERTION"
+ )
+ endif()
+
+ if(arg_COPYRIGHT)
+ set(fields "${fields}
+FileCopyrightText: ${arg_COPYRIGHT}"
+ )
+ else()
+ set(fields "${fields}
+FileCopyrightText: NOASSERTION"
+ )
+ endif()
+
+ set(file_suffix_to_generate "")
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(file_suffix_to_generate "-$")
+ endif()
+
+ if(arg_CONFIG)
+ set(file_suffix_to_install "-${arg_CONFIG}")
+ else()
+ set(file_suffix_to_install "")
+ endif()
+
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ if(arg_INSTALL_PREFIX)
+ set(install_prefix "${arg_INSTALL_PREFIX}")
+ else()
+ set(install_prefix "${CMAKE_INSTALL_PREFIX}")
+ endif()
+
+ set(content "
+ if(NOT EXISTS $ENV{DESTDIR}${install_prefix}/${arg_FILENAME}
+ AND NOT QT_SBOM_BUILD_TIME AND NOT QT_SBOM_FAKE_CHECKSUM)
+ if(NOT ${arg_OPTIONAL})
+ message(FATAL_ERROR \"Cannot find ${arg_FILENAME}\")
+ endif()
+ else()
+ if(NOT QT_SBOM_BUILD_TIME)
+ if(QT_SBOM_FAKE_CHECKSUM)
+ set(sha1 \"158942a783ee1095eafacaffd93de73edeadbeef\")
+ else()
+ file(SHA1 $ENV{DESTDIR}${install_prefix}/${arg_FILENAME} sha1)
+ endif()
+ list(APPEND QT_SBOM_VERIFICATION_CODES \${sha1})
+ endif()
+ file(APPEND \"${staging_area_spdx_file}\"
+\"
+FileName: ./${arg_FILENAME}
+SPDXID: ${arg_SPDXID}
+FileType: ${arg_FILETYPE}
+FileChecksum: SHA1: \${sha1}${fields}
+LicenseInfoInFile: NOASSERTION
+Relationship: ${arg_RELATIONSHIP}
+\"
+ )
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(file_sbom "${sbom_dir}/${arg_SPDXID}${file_suffix_to_generate}.cmake")
+ file(GENERATE OUTPUT "${file_sbom}" CONTENT "${content}")
+
+ set(file_sbom_to_install "${sbom_dir}/${arg_SPDXID}${file_suffix_to_install}.cmake")
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${file_sbom_to_install}")
+endfunction()
+
+# Helper to add info about an external reference to a different project spdx sbom file.
+function(_qt_internal_sbom_generate_add_external_reference)
+ set(opt_args
+ NO_AUTO_RELATIONSHIP
+ )
+ set(single_args
+ EXTERNAL
+ FILENAME
+ RENAME
+ SPDXID
+ RELATIONSHIP
+
+ )
+ set(multi_args
+ INSTALL_PREFIXES
+ )
+ 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 "")
+ qt_internal_sbom_set_default_option_value_and_error_if_empty(FILENAME "")
+
+ if(NOT arg_SPDXID)
+ get_property(spdx_id_count GLOBAL PROPERTY _qt_sbom_spdx_id_count)
+ set(arg_SPDXID "DocumentRef-${spdx_id_count}")
+ math(EXPR spdx_id_count "${spdx_id_count} + 1")
+ set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count "${spdx_id_count}")
+ endif()
+
+ if(NOT "${arg_SPDXID}" MATCHES "^DocumentRef-[-a-zA-Z0-9]+$")
+ message(FATAL_ERROR "Invalid DocumentRef \"${arg_SPDXID}\"")
+ endif()
+
+ get_property(sbom_project_name GLOBAL PROPERTY _qt_sbom_project_name)
+ if(NOT sbom_project_name)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+ if(NOT arg_RELATIONSHIP)
+ if(NOT arg_NO_AUTO_RELATIONSHIP)
+ set(arg_RELATIONSHIP
+ "SPDXRef-${sbom_project_name} DEPENDS_ON ${arg_SPDXID}:${arg_EXTERNAL}")
+ else()
+ set(arg_RELATIONSHIP "")
+ endif()
+ else()
+ string(REPLACE
+ "@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
+ endif()
+
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ set(install_prefixes "")
+ if(arg_INSTALL_PREFIXES)
+ list(APPEND install_prefixes ${arg_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)
+ set(relationship_content "
+ file(APPEND \"${staging_area_spdx_file}\"
+ \"
+ Relationship: ${arg_RELATIONSHIP}\")
+")
+ endif()
+
+ # Filename may not exist yet, and it could be a generator expression.
+ set(content "
+ set(relative_file_name \"${arg_FILENAME}\")
+ set(document_dir_paths ${install_prefixes})
+ 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} \"
+ )
+ 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()
+
+ string(REGEX REPLACE \"^.*[\\r\\n]DocumentNamespace:[ \\t]*([^#\\r\\n]*).*$\"
+ \"\\\\1\" ext_ns \"\${ext_content}\")
+
+ list(APPEND QT_SBOM_EXTERNAL_DOC_REFS \"
+ExternalDocumentRef: ${arg_SPDXID} \${ext_ns} SHA1: \${ext_sha1}\")
+
+ ${relationship_content}
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(ext_ref_sbom "${sbom_dir}/${arg_SPDXID}.cmake")
+ file(GENERATE OUTPUT "${ext_ref_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files "${ext_ref_sbom}")
+endfunction()
+
+# Helper to add info about a package to the sbom. Usually a package is a mapping to a cmake target.
+function(_qt_internal_sbom_generate_add_package)
+ set(opt_args
+ CONTAINS_FILES
+ )
+ set(single_args
+ PACKAGE
+ VERSION
+ LICENSE_DECLARED
+ LICENSE_CONCLUDED
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ RELATIONSHIP
+ SPDXID
+ SUPPLIER
+ PURPOSE
+ COMMENT
+ )
+ set(multi_args
+ EXTREF
+ CPE
+ )
+ 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(PACKAGE "")
+
+ _qt_internal_sbom_get_and_check_spdx_id(
+ VARIABLE arg_SPDXID
+ CHECK "${arg_SPDXID}"
+ HINTS "SPDXRef-${arg_PACKAGE}"
+ )
+
+ qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(VERSION "unknown")
+ qt_internal_sbom_set_default_option_value(SUPPLIER "Person: Anonymous")
+ qt_internal_sbom_set_default_option_value(LICENSE_DECLARED "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(LICENSE_CONCLUDED "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(COPYRIGHT "NOASSERTION")
+ qt_internal_sbom_set_default_option_value(PURPOSE "OTHER")
+
+ set(fields "")
+
+ if(arg_LICENSE_CONCLUDED)
+ set(fields "${fields}
+PackageLicenseConcluded: ${arg_LICENSE_CONCLUDED}"
+ )
+ else()
+ set(fields "${fields}
+PackageLicenseConcluded: NOASSERTION"
+ )
+ endif()
+
+ if(arg_LICENSE_DECLARED)
+ set(fields "${fields}
+PackageLicenseDeclared: ${arg_LICENSE_DECLARED}"
+ )
+ else()
+ set(fields "${fields}
+PackageLicenseDeclared: NOASSERTION"
+ )
+ endif()
+
+ foreach(ext_ref IN LISTS arg_EXTREF)
+ set(fields "${fields}
+ExternalRef: ${ext_ref}"
+ )
+ endforeach()
+
+ if(arg_CONTAINS_FILES)
+ set(fields "${fields}
+FilesAnalyzed: true"
+ )
+ else()
+ set(fields "${fields}
+FilesAnalyzed: false"
+ )
+ endif()
+
+ if(arg_COPYRIGHT)
+ set(fields "${fields}
+PackageCopyrightText: ${arg_COPYRIGHT}"
+ )
+ else()
+ set(fields "${fields}
+PackageCopyrightText: NOASSERTION"
+ )
+ endif()
+
+ if(arg_PURPOSE)
+ set(fields "${fields}
+PrimaryPackagePurpose: ${arg_PURPOSE}"
+ )
+ else()
+ set(fields "${fields}
+PrimaryPackagePurpose: OTHER"
+ )
+ endif()
+
+ if(arg_COMMENT)
+ set(fields "${fields}
+PackageComment: ${arg_COMMENT}"
+ )
+ endif()
+
+ _qt_internal_sbom_get_platform_cpe(platform_cpe)
+ if(NOT arg_CPE)
+ set(fields "${fields}
+ExternalRef: SECURITY cpe23Type ${platform_cpe}"
+ )
+ endif()
+
+ foreach(cpe IN LISTS arg_CPE)
+ set(fields "${fields}
+ExternalRef: SECURITY cpe23Type ${cpe}"
+ )
+ endforeach()
+
+ get_property(sbom_project_name GLOBAL PROPERTY _qt_sbom_project_name)
+ if(NOT sbom_project_name)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+ if(NOT arg_RELATIONSHIP)
+ set(arg_RELATIONSHIP "SPDXRef-${sbom_project_name} CONTAINS ${arg_SPDXID}")
+ else()
+ string(REPLACE "@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
+ endif()
+
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ set(content "
+ file(APPEND \"${staging_area_spdx_file}\"
+\"
+PackageName: ${arg_PACKAGE}
+SPDXID: ${arg_SPDXID}
+PackageDownloadLocation: ${arg_DOWNLOAD_LOCATION}
+PackageVersion: ${arg_VERSION}
+PackageSupplier: ${arg_SUPPLIER}${fields}
+Relationship: ${arg_RELATIONSHIP}
+\"
+ )
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(package_sbom "${sbom_dir}/${arg_SPDXID}.cmake")
+ file(GENERATE OUTPUT "${package_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${package_sbom}")
+endfunction()
+
+# Helper to add a license text from a file or text into the sbom document.
+function(_qt_internal_sbom_generate_add_license)
+ set(opt_args "")
+ set(single_args
+ LICENSE_ID
+ EXTRACTED_TEXT
+ )
+ 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_set_default_option_value_and_error_if_empty(LICENSE_ID "")
+
+ _qt_internal_sbom_get_and_check_spdx_id(
+ VARIABLE arg_SPDXID
+ CHECK "${arg_SPDXID}"
+ HINTS "SPDXRef-${arg_LICENSE_ID}"
+ )
+
+ if(NOT arg_EXTRACTED_TEXT)
+ set(licenses_dir "${PROJECT_SOURCE_DIR}/LICENSES")
+ file(READ "${licenses_dir}/${arg_LICENSE_ID}.txt" arg_EXTRACTED_TEXT)
+ string(PREPEND arg_EXTRACTED_TEXT "")
+ string(APPEND arg_EXTRACTED_TEXT "")
+ endif()
+
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
+
+ set(content "
+ file(APPEND \"${staging_area_spdx_file}\"
+\"
+LicenseID: ${arg_LICENSE_ID}
+ExtractedText: ${arg_EXTRACTED_TEXT}
+\"
+ )
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(license_sbom "${sbom_dir}/${arg_SPDXID}.cmake")
+ file(GENERATE OUTPUT "${license_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files "${license_sbom}")
+endfunction()
+
+# Helper to retrieve a valid spdx id, given some hints.
+# HINTS can be a list of values, one of which will be sanitized and used as the spdx id.
+# CHECK is expected to be a valid spdx id.
+function(_qt_internal_sbom_get_and_check_spdx_id)
+ set(opt_args "")
+ set(single_args
+ VARIABLE
+ CHECK
+ )
+ set(multi_args
+ HINTS
+ )
+ 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(VARIABLE "")
+
+ if(NOT arg_CHECK)
+ get_property(spdx_id_count GLOBAL PROPERTY _qt_sbom_spdx_id_count)
+ set(suffix "-${spdx_id_count}")
+ math(EXPR spdx_id_count "${spdx_id_count} + 1")
+ set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count "${spdx_id_count}")
+
+ foreach(hint IN LISTS arg_HINTS)
+ _qt_internal_sbom_get_sanitized_spdx_id(id "${hint}")
+ if(id)
+ set(id "${id}${suffix}")
+ break()
+ endif()
+ endforeach()
+
+ if(NOT id)
+ set(id "SPDXRef${suffix}")
+ endif()
+ else()
+ set(id "${arg_CHECK}")
+ endif()
+
+ if("${id}" MATCHES "^SPDXRef-[-]+$"
+ OR (NOT "${id}" MATCHES "^SPDXRef-[-a-zA-Z0-9]+$"))
+ message(FATAL_ERROR "Invalid SPDXID \"${id}\"")
+ endif()
+
+ set(${arg_VARIABLE} "${id}" PARENT_SCOPE)
+endfunction()
+
+# Helper to find the python interpreter, to be able to run post-installation steps like NTIA
+# verification.
+macro(_qt_internal_sbom_find_python)
+ if(QT_INTERNAL_NO_SBOM_FIND_PYTHON_FRAMEWORK)
+ set(__qt_sbom_python_find_framework "${Python_FIND_FRAMEWORK}")
+ set(__qt_sbom_python3_find_framework "${Python3_FIND_FRAMEWORK}")
+ set(Python_FIND_FRAMEWORK NEVER)
+ set(Python3_FIND_FRAMEWORK NEVER)
+ endif()
+
+ if(NOT Python3_EXECUTABLE)
+ # NTIA-compliance checker requires Python 3.9 or later.
+ find_package(Python3 3.9 REQUIRED COMPONENTS Interpreter)
+ endif()
+
+ if(QT_INTERNAL_NO_SBOM_FIND_PYTHON_FRAMEWORK)
+ set(Python_FIND_FRAMEWORK ${__qt_sbom_python_find_framework})
+ set(Python3_FIND_FRAMEWORK ${__qt_sbom_python3_find_framework})
+ endif()
+endmacro()
+
+# Helper to find the various python package dependencies needed to run the post-installation NTIA
+# verification and the spdx format validation step.
+function(_qt_internal_sbom_find_python_dependencies)
+ if(NOT Python3_EXECUTABLE)
+ message(FATAL_ERROR "Python interpreter not found for sbom dependencies.")
+ endif()
+
+ if(QT_SBOM_HAVE_PYTHON_DEPS)
+ return()
+ endif()
+ execute_process(
+ COMMAND
+ ${Python3_EXECUTABLE} -c "
+import spdx_tools.spdx.clitools.pyspdxtools
+import ntia_conformance_checker.main
+"
+ RESULT_VARIABLE res
+ OUTPUT_VARIABLE output
+ ERROR_VARIABLE output
+ )
+
+ if("${res}" STREQUAL "0")
+ set(QT_SBOM_HAVE_PYTHON_DEPS TRUE CACHE INTERNAL "")
+ else()
+ message(FATAL_ERROR "SBOM Python dependencies not found. Error:\n${output}")
+ endif()
+endfunction()
+
+# Helper to find a python installed CLI utility.
+# Expected to be in PATH.
+function(_qt_internal_sbom_find_python_dependency_program)
+ set(opt_args
+ REQUIRED
+ )
+ set(single_args
+ NAME
+ )
+ 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(program_name "${arg_NAME}")
+ string(TOUPPER "${program_name}" upper_name)
+ set(cache_var "QT_SBOM_PROGRAM_${upper_name}")
+
+ find_program(${cache_var}
+ NAMES ${program_name}
+ )
+
+ if(NOT ${cache_var})
+ if(arg_REQUIRED)
+ set(message_type "FATAL_ERROR")
+ set(prefix "Required ")
+ else()
+ set(message_type "STATUS")
+ set(prefix "Optional ")
+ endif()
+ message(${message_type} "${prefix}SBOM python program '${program_name}' not found.")
+ endif()
+endfunction()
+
+# Helper to generate a json file. This also implies some additional validity checks, useful
+# to ensure a proper sbom file.
+function(_qt_internal_sbom_generate_json)
+ if(NOT Python3_EXECUTABLE)
+ message(FATAL_ERROR "Python interpreter not found for generating SBOM json file.")
+ endif()
+
+ set(content "
+ message(STATUS \"Generating JSON: \${QT_SBOM_OUTPUT_PATH}.json\")
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
+ -i \"\${QT_SBOM_OUTPUT_PATH}\" -o \"\${QT_SBOM_OUTPUT_PATH}.json\"
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"SBOM conversion to JSON failed: \${res}\")
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(verify_sbom "${sbom_dir}/convert_to_json.cmake")
+ file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
+endfunction()
+
+# Helper to verify the generated sbom is valid and NTIA compliant.
+function(_qt_internal_sbom_verify_valid_and_ntia_compliant)
+ if(NOT Python3_EXECUTABLE)
+ message(FATAL_ERROR "Python interpreter not found for verifying SBOM file.")
+ endif()
+
+ set(content "
+ message(STATUS \"Verifying: \${QT_SBOM_OUTPUT_PATH}\")
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
+ -i \"\${QT_SBOM_OUTPUT_PATH}\"
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"SBOM verification failed: \${res}\")
+ endif()
+
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -m ntia_conformance_checker.main
+ --file \"\${QT_SBOM_OUTPUT_PATH}\"
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"SBOM NTIA verification failed: \{res}\")
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(verify_sbom "${sbom_dir}/verify_valid_and_ntia.cmake")
+ file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
+endfunction()
+
+# Helper to show the main sbom document info in the form of a CLI table.
+function(_qt_internal_sbom_show_table)
+ set(content "
+ message(STATUS \"Showing main SBOM document info: \${QT_SBOM_OUTPUT_PATH}\")
+ execute_process(
+ COMMAND sbom2doc -i \"\${QT_SBOM_OUTPUT_PATH}\"
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"Showing SBOM document failed: \${res}\")
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(verify_sbom "${sbom_dir}/show_table.cmake")
+ file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
+endfunction()
+
+# Helper to audit the generated sbom.
+function(_qt_internal_sbom_audit)
+ set(opt_args NO_ERROR)
+ 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)
+
+ set(handle_error "")
+ if(NOT arg_NO_ERROR)
+ set(handle_error "
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"SBOM Audit failed: \${res}\")
+ endif()
+")
+ endif()
+
+ set(content "
+ message(STATUS \"Auditing SBOM: \${QT_SBOM_OUTPUT_PATH}\")
+ execute_process(
+ COMMAND sbomaudit -i \"\${QT_SBOM_OUTPUT_PATH}\"
+ --disable-license-check --cpecheck --offline
+ RESULT_VARIABLE res
+ )
+ ${handle_error}
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(verify_sbom "${sbom_dir}/audit.cmake")
+ file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
+endfunction()
diff --git a/cmake/QtPublicSbomHelpers.cmake b/cmake/QtPublicSbomHelpers.cmake
new file mode 100644
index 00000000000..e93f7762985
--- /dev/null
+++ b/cmake/QtPublicSbomHelpers.cmake
@@ -0,0 +1,2644 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Starts repo sbom generation.
+# Should be called before any targets are added to the sbom.
+#
+# INSTALL_PREFIX should be passed a value like CMAKE_INSTALL_PREFIX or QT_STAGING_PREFIX
+# INSTALL_SBOM_DIR should be passed a value like CMAKE_INSTALL_DATAROOTDIR or
+# Qt's INSTALL_SBOMDIR
+# SUPPLIER, SUPPLIER_URL, DOCUMENT_NAMESPACE, COPYRIGHTS are self-explanatory.
+function(_qt_internal_sbom_begin_project)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args
+ QT_CPE
+ )
+ set(single_args
+ INSTALL_PREFIX
+ INSTALL_SBOM_DIR
+ LICENSE_EXPRESSION
+ SUPPLIER
+ SUPPLIER_URL
+ DOWNLOAD_LOCATION
+ DOCUMENT_NAMESPACE
+ VERSION
+ SBOM_PROJECT_NAME
+ CPE
+ )
+ set(multi_args
+ COPYRIGHTS
+ )
+
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(CMAKE_VERSION LESS_EQUAL "3.19")
+ if(QT_IGNORE_MIN_CMAKE_VERSION_FOR_SBOM)
+ message(STATUS
+ "Using CMake version older than 3.19, and QT_IGNORE_MIN_CMAKE_VERSION_FOR_SBOM was "
+ "set to ON. qt_attribution.json files will not be processed.")
+ else()
+ message(FATAL_ERROR
+ "Generating an SBOM requires CMake version 3.19 or newer. You can pass "
+ "-DQT_IGNORE_MIN_CMAKE_VERSION_FOR_SBOM=ON to try to generate the SBOM anyway, "
+ "but it is not officially supported, and the SBOM might be incomplete.")
+ endif()
+ endif()
+
+ # The ntia-conformance-checker insists that a SPDX document contain at least one
+ # relationship that DESCRIBES a package, and that the package contains the string
+ # "Package-" in the spdx id. boot2qt spdx seems to contain the same.
+
+ if(arg_SBOM_PROJECT_NAME)
+ _qt_internal_sbom_set_root_project_name("${arg_SBOM_PROJECT_NAME}")
+ else()
+ _qt_internal_sbom_set_root_project_name("${PROJECT_NAME}")
+ endif()
+ _qt_internal_sbom_get_root_project_name_for_spdx_id(repo_project_name_for_spdx_id)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+
+ if(arg_SUPPLIER_URL)
+ set(repo_supplier_url "${arg_SUPPLIER_URL}")
+ else()
+ _qt_internal_sbom_get_default_supplier_url(repo_supplier_url)
+ endif()
+
+ # Manual override.
+ if(arg_VERSION)
+ set(QT_SBOM_GIT_VERSION "${arg_VERSION}")
+ set(QT_SBOM_GIT_VERSION_PATH "${arg_VERSION}")
+ set(QT_SBOM_GIT_HASH "") # empty on purpose, no source of info
+ set(non_git_version "${arg_VERSION}")
+ else()
+ # Query git version info.
+ _qt_internal_find_git_package()
+ _qt_internal_query_git_version(
+ EMPTY_VALUE_WHEN_NOT_GIT_REPO
+ OUT_VAR_PREFIX __sbom_
+ )
+ set(QT_SBOM_GIT_VERSION "${__sbom_git_version}")
+ set(QT_SBOM_GIT_VERSION_PATH "${__sbom_git_version_path}")
+ set(QT_SBOM_GIT_HASH "${__sbom_git_hash}")
+
+ # Git version might not be available.
+ set(non_git_version "${QT_REPO_MODULE_VERSION}")
+ if(NOT QT_SBOM_GIT_VERSION)
+ set(QT_SBOM_GIT_VERSION "${non_git_version}")
+ endif()
+ if(NOT QT_SBOM_GIT_VERSION_PATH)
+ set(QT_SBOM_GIT_VERSION_PATH "${non_git_version}")
+ endif()
+ endif()
+
+ # Set the variables in the outer scope, so they can be accessed by the generation functions
+ # in QtPublicSbomGenerationHelpers.cmake
+ set(QT_SBOM_GIT_VERSION "${QT_SBOM_GIT_VERSION}" PARENT_SCOPE)
+ set(QT_SBOM_GIT_VERSION_PATH "${QT_SBOM_GIT_VERSION_PATH}" PARENT_SCOPE)
+ set(QT_SBOM_GIT_HASH "${QT_SBOM_GIT_HASH}" PARENT_SCOPE)
+
+ if(arg_DOCUMENT_NAMESPACE)
+ set(repo_spdx_namespace "${arg_DOCUMENT_NAMESPACE}")
+ else()
+ # Used in external refs, either URI + UUID or URI + checksum. We use git version for now
+ # which is probably not conformat to spec.
+ set(repo_name_and_version "${repo_project_name_lowercase}-${QT_SBOM_GIT_VERSION}")
+ set(repo_spdx_namespace
+ "${repo_supplier_url}/spdxdocs/${repo_name_and_version}")
+ endif()
+
+ if(non_git_version)
+ set(version_suffix "-${non_git_version}")
+ else()
+ set(version_suffix "")
+ endif()
+
+ set(repo_spdx_relative_install_path
+ "${arg_INSTALL_SBOM_DIR}/${repo_project_name_lowercase}${version_suffix}.spdx")
+
+ # Prepend DESTDIR, to allow relocating installed sbom. Needed for CI.
+ set(repo_spdx_install_path
+ "\$ENV{DESTDIR}${arg_INSTALL_PREFIX}/${repo_spdx_relative_install_path}")
+
+ if(arg_LICENSE_EXPRESSION)
+ set(repo_license "${arg_LICENSE_EXPRESSION}")
+ else()
+ # Default to NOASSERTION for root repo SPDX packages, because we have some repos
+ # with multiple licenses and AND-ing them together will create a giant unreadable list.
+ # It's better to rely on the more granular package licenses.
+ set(repo_license "")
+ endif()
+
+ if(arg_COPYRIGHTS)
+ list(JOIN arg_COPYRIGHTS "\n" arg_COPYRIGHTS)
+ set(repo_copyright "${arg_COPYRIGHTS}")
+ else()
+ _qt_internal_sbom_get_default_qt_copyright_header(repo_copyright)
+ endif()
+
+ if(arg_SUPPLIER)
+ set(repo_supplier "${arg_SUPPLIER}")
+ else()
+ _qt_internal_sbom_get_default_supplier(repo_supplier)
+ endif()
+
+ if(arg_CPE)
+ set(qt_cpe "${arg_CPE}")
+ elseif(arg_QT_CPE)
+ _qt_internal_sbom_get_cpe_qt_repo(qt_cpe)
+ else()
+ set(qt_cpe "")
+ endif()
+
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location "${arg_DOWNLOAD_LOCATION}")
+ else()
+ _qt_internal_sbom_get_qt_repo_source_download_location(download_location)
+ endif()
+
+ _qt_internal_sbom_begin_project_generate(
+ OUTPUT "${repo_spdx_install_path}"
+ LICENSE "${repo_license}"
+ COPYRIGHT "${repo_copyright}"
+ SUPPLIER "${repo_supplier}" # This must not contain spaces!
+ SUPPLIER_URL "${repo_supplier_url}"
+ DOWNLOAD_LOCATION "${download_location}"
+ PROJECT "${repo_project_name_lowercase}"
+ PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
+ NAMESPACE "${repo_spdx_namespace}"
+ CPE "${qt_cpe}"
+ OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
+ )
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace
+ "${repo_spdx_namespace}")
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path
+ "${repo_spdx_relative_install_path}")
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase
+ "${repo_project_name_lowercase}")
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_install_prefix
+ "${arg_INSTALL_PREFIX}")
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_project_spdx_id
+ "${repo_project_spdx_id}")
+
+ file(GLOB license_files "${PROJECT_SOURCE_DIR}/LICENSES/LicenseRef-*.txt")
+ foreach(license_file IN LISTS license_files)
+ get_filename_component(license_id "${license_file}" NAME_WLE)
+ _qt_internal_sbom_add_license(
+ LICENSE_ID "${license_id}"
+ LICENSE_PATH "${license_file}"
+ NO_LICENSE_REF_PREFIX
+ )
+ endforeach()
+
+ # Make sure that any system library dependencies that have been found via qt_find_package or
+ # _qt_internal_find_third_party_dependencies have their spdx id registered now.
+ _qt_internal_sbom_record_system_library_spdx_ids()
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_begin_called TRUE)
+endfunction()
+
+# Ends repo sbom project generation.
+# Should be called after all relevant targets are added to the sbom.
+# Handles registering sbom info for recorded system libraries and then creates the sbom build
+# and install rules.
+function(_qt_internal_sbom_end_project)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ # Now that we know which system libraries are linked against because we added all
+ # subdirectories, we can add the recorded system libs to the sbom.
+ _qt_internal_sbom_add_recorded_system_libraries()
+
+ # Run sbom finalization for targets that had it scheduled, but haven't run yet.
+ # This can happen when _qt_internal_sbom_end_project is called within the same
+ # subdirectory scope as where the targets are meant to be finalized, but that would be too late
+ # and the targets wouldn't be added to the sbom.
+ # This would mostly happen in user projects, and not Qt repos, because in Qt repos we afaik
+ # never create targets in the root cmakelists (aside from the qtbase Platform targets).
+ get_cmake_property(targets _qt_internal_sbom_targets_waiting_for_finalization)
+ if(targets)
+ foreach(target IN LISTS targets)
+ _qt_internal_finalize_sbom("${target}")
+ endforeach()
+ endif()
+
+ set(end_project_options "")
+ if(QT_INTERNAL_SBOM_VERIFY OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ list(APPEND end_project_options VERIFY)
+ endif()
+ if(QT_INTERNAL_SBOM_SHOW_TABLE OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ list(APPEND end_project_options SHOW_TABLE)
+ endif()
+ if(QT_INTERNAL_SBOM_AUDIT OR QT_INTERNAL_SBOM_AUDIT_NO_ERROR)
+ list(APPEND end_project_options AUDIT)
+ endif()
+ if(QT_INTERNAL_SBOM_AUDIT_NO_ERROR)
+ list(APPEND end_project_options AUDIT_NO_ERROR)
+ endif()
+ if(QT_INTERNAL_SBOM_GENERATE_JSON OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ list(APPEND end_project_options GENERATE_JSON)
+ endif()
+
+ _qt_internal_sbom_end_project_generate(
+ ${end_project_options}
+ )
+
+ # Clean up external document ref properties, because each repo needs to start from scratch
+ # in a top-level build.
+ get_cmake_property(known_external_documents _qt_known_external_documents)
+ 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} "")
+ endforeach()
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_begin_called FALSE)
+endfunction()
+
+# Helper to get the options that _qt_internal_sbom_add_target understands.
+# Also used in qt_find_package_extend_sbom.
+macro(_qt_internal_get_sbom_add_target_options opt_args single_args multi_args)
+ set(${opt_args}
+ NO_INSTALL
+ NO_CURRENT_DIR_ATTRIBUTION
+ NO_DEFAULT_QT_LICENSE
+ NO_DEFAULT_DIRECTORY_QT_LICENSE
+ NO_DEFAULT_QT_COPYRIGHTS
+ NO_DEFAULT_QT_PACKAGE_VERSION
+ NO_DEFAULT_QT_SUPPLIER
+ )
+ set(${single_args}
+ TYPE
+ PACKAGE_VERSION
+ FRIENDLY_PACKAGE_NAME
+ CPE_VENDOR
+ CPE_PRODUCT
+ LICENSE_EXPRESSION
+ DOWNLOAD_LOCATION
+ ATTRIBUTION_ENTRY_INDEX
+ )
+ set(${multi_args}
+ COPYRIGHTS
+ LIBRARIES
+ PUBLIC_LIBRARIES
+ CPE
+ SBOM_DEPENDENCIES
+ ATTRIBUTION_FILE_PATHS
+ ATTRIBUTION_FILE_DIR_PATHS
+ )
+
+ _qt_internal_sbom_get_multi_config_single_args(multi_config_single_args)
+ list(APPEND single_args ${multi_config_single_args})
+endmacro()
+
+# Generate sbom information for a given target.
+# Creates:
+# - a SPDX package for the target
+# - zero or more SPDX file entries for each installed binary file
+# - each binary file entry gets a list of 'generated from source files' section
+# - dependency relationships to other target packages
+# - other relevant information like licenses, copyright, etc.
+# For licenses, copyrights, these can either be passed as options, or read from qt_attribution.json
+# files.
+# For dependencies, these are either specified via options, or read from properties set on the
+# target by qt_internal_extend_target.
+function(_qt_internal_sbom_add_target target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ _qt_internal_get_sbom_add_target_options(opt_args single_args multi_args)
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_target_property(target_type ${target} TYPE)
+
+ # Mark the target as a Qt module for sbom processing purposes.
+ # Needed for non-standard targets like Bootstrap and QtLibraryInfo, that don't have a Qt::
+ # namespace prefix.
+ if(arg_TYPE STREQUAL QT_MODULE)
+ set_target_properties(${target} PROPERTIES _qt_sbom_is_qt_module TRUE)
+ endif()
+
+ set(project_package_options "")
+
+ _qt_internal_sbom_is_qt_entity_type("${arg_TYPE}" is_qt_entity_type)
+
+ if(arg_FRIENDLY_PACKAGE_NAME)
+ set(package_name_for_spdx_id "${arg_FRIENDLY_PACKAGE_NAME}")
+ else()
+ set(package_name_for_spdx_id "${target}")
+ endif()
+
+ set(package_comment "")
+
+ # 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}
+ TYPE "${arg_TYPE}"
+ PACKAGE_NAME "${package_name_for_spdx_id}"
+ OUT_VAR package_spdx_id
+ )
+
+ set(attribution_args
+ PARENT_TARGET "${target}"
+ )
+
+ if(is_qt_entity_type)
+ list(APPEND attribution_args CREATE_SBOM_FOR_EACH_ATTRIBUTION)
+ endif()
+
+ _qt_internal_forward_function_args(
+ FORWARD_APPEND
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR attribution_args
+ FORWARD_SINGLE
+ ATTRIBUTION_ENTRY_INDEX
+ FORWARD_MULTI
+ ATTRIBUTION_FILE_PATHS
+ ATTRIBUTION_FILE_DIR_PATHS
+ )
+
+ if(NOT arg_NO_CURRENT_DIR_ATTRIBUTION
+ AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/qt_attribution.json")
+ list(APPEND attribution_args
+ ATTRIBUTION_FILE_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/qt_attribution.json"
+ )
+ endif()
+
+ _qt_internal_sbom_handle_qt_attribution_files(qa ${attribution_args})
+
+ # Collect license expressions, but each expression needs to be abided, so we AND them.
+ set(license_expression "")
+
+ if(arg_LICENSE_EXPRESSION)
+ set(license_expression "${arg_LICENSE_EXPRESSION}")
+ endif()
+
+ if(is_qt_entity_type AND NOT arg_NO_DEFAULT_QT_LICENSE)
+ _qt_internal_sbom_get_default_qt_license_id(qt_license_expression)
+ _qt_internal_sbom_join_two_license_ids_with_op(
+ "${license_expression}" "AND" "${qt_license_expression}"
+ license_expression)
+ endif()
+
+ # Allow setting a license expression per directory scope via a variable.
+ if(is_qt_entity_type AND QT_SBOM_LICENSE_EXPRESSION AND NOT arg_NO_DEFAULT_DIRECTORY_QT_LICENSE)
+ set(qt_license_expression "${_qt_internal_sbom_get_default_qt_license_id}")
+ _qt_internal_sbom_join_two_license_ids_with_op(
+ "${license_expression}" "AND" "${qt_license_expression}"
+ license_expression)
+ endif()
+
+ if(qa_license_id)
+ if(NOT qa_license_id MATCHES "urn:dje:license")
+ _qt_internal_sbom_join_two_license_ids_with_op(
+ "${license_expression}" "AND" "${qa_license_id}"
+ license_expression)
+ else()
+ message(DEBUG
+ "Attribution license id contains invalid spdx license reference: ${qa_license_id}")
+ set(invalid_license_comment
+ " Attribution license ID with invalid spdx license reference: ")
+ string(APPEND invalid_license_comment "${qa_license_id}\n")
+ string(APPEND package_comment "${invalid_license_comment}")
+ endif()
+ endif()
+
+ if(license_expression)
+ list(APPEND project_package_options LICENSE_CONCLUDED "${license_expression}")
+ if(is_qt_entity_type)
+ list(APPEND project_package_options LICENSE_DECLARED "${license_expression}")
+ endif()
+ endif()
+
+ # Copyrights are additive, so we collect them from all sources that were found.
+ set(copyrights "")
+ if(arg_COPYRIGHTS)
+ list(APPEND copyrights "${arg_COPYRIGHTS}")
+ endif()
+ if(is_qt_entity_type AND NOT arg_NO_DEFAULT_QT_COPYRIGHTS)
+ _qt_internal_sbom_get_default_qt_copyright_header(qt_default_copyright)
+ if(qt_default_copyright)
+ list(APPEND copyrights "${qt_default_copyright}")
+ endif()
+ endif()
+ if(qa_copyrights)
+ list(APPEND copyrights "${qa_copyrights}")
+ endif()
+ if(copyrights)
+ list(JOIN copyrights "\n" copyrights)
+ list(APPEND project_package_options COPYRIGHT "${copyrights}")
+ endif()
+
+ set(package_version "")
+ if(arg_PACKAGE_VERSION)
+ set(package_version "${arg_PACKAGE_VERSION}")
+ elseif(is_qt_entity_type AND NOT arg_NO_DEFAULT_QT_PACKAGE_VERSION)
+ _qt_internal_sbom_get_default_qt_package_version(package_version)
+ elseif(qa_version)
+ set(package_version "${qa_version}")
+ endif()
+ if(package_version)
+ list(APPEND project_package_options VERSION "${package_version}")
+ endif()
+
+ set(supplier "")
+ if((is_qt_entity_type
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES")
+ AND NOT arg_NO_DEFAULT_QT_SUPPLIER)
+ _qt_internal_sbom_get_default_supplier(supplier)
+ endif()
+ if(supplier)
+ list(APPEND project_package_options SUPPLIER "Organization: ${supplier}")
+ endif()
+
+ set(download_location "")
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location "${arg_DOWNLOAD_LOCATION}")
+ elseif(is_qt_entity_type)
+ _qt_internal_sbom_get_qt_repo_source_download_location(download_location)
+ elseif(arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE" OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES")
+ if(qa_download_location)
+ set(download_location "${qa_download_location}")
+ elseif(qa_homepage)
+ set(download_location "${qa_homepage}")
+ endif()
+ elseif(arg_TYPE STREQUAL "SYSTEM_LIBRARY")
+ # Try to get package url that was set using CMake's set_package_properties function.
+ # Relies on querying the internal global property name that CMake sets in its
+ # implementation.
+ get_cmake_property(target_url _CMAKE_${package_name_for_spdx_id}_URL)
+ if(target_url)
+ set(download_location "${target_url}")
+ endif()
+ if(NOT download_location AND qa_download_location)
+ set(download_location "${qa_download_location}")
+ endif()
+ endif()
+
+ if(download_location)
+ list(APPEND project_package_options DOWNLOAD_LOCATION "${download_location}")
+ endif()
+
+ _qt_internal_sbom_get_package_purpose("${arg_TYPE}" package_purpose)
+ list(APPEND project_package_options PURPOSE "${package_purpose}")
+
+ if(arg_CPE)
+ list(APPEND project_package_options CPE "${arg_CPE}")
+ elseif(arg_CPE_VENDOR AND arg_CPE_PRODUCT)
+ _qt_internal_sbom_compute_security_cpe(custom_cpe
+ VENDOR "${arg_CPE_VENDOR}"
+ PRODUCT "${arg_CPE_PRODUCT}"
+ VERSION "${package_version}")
+ list(APPEND project_package_options CPE "${custom_cpe}")
+ elseif(is_qt_entity_type)
+ _qt_internal_sbom_compute_security_cpe_for_qt(cpe_list)
+ list(APPEND project_package_options CPE "${cpe_list}")
+ endif()
+
+ if(is_qt_entity_type
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES"
+ )
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+
+ set(purl_prefix "PACKAGE-MANAGER purl")
+ set(purl_suffix
+ "pkg:generic/${supplier}/${repo_project_name_lowercase}@${QT_SBOM_GIT_VERSION}")
+ set(package_manager_external_ref
+ "${purl_prefix} ${purl_suffix}")
+ list(APPEND project_package_options EXTREF "${package_manager_external_ref}")
+ endif()
+
+ if(arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES"
+ OR arg_TYPE STREQUAL "SYSTEM_LIBRARY"
+ OR arg_TYPE STREQUAL "THIRD_PARTY_LIBRARY"
+ OR arg_TYPE STREQUAL "THIRD_PARTY_LIBRARY_WITH_FILES"
+ )
+ if(qa_attribution_name)
+ string(APPEND package_comment " Name: ${qa_attribution_name}\n")
+ endif()
+
+ if(qa_description)
+ string(APPEND package_comment " Description: ${qa_description}\n")
+ endif()
+
+ if(qa_qt_usage)
+ string(APPEND package_comment " Qt usage: ${qa_qt_usage}\n")
+ endif()
+
+ if(qa_chosen_attribution_file_path)
+ string(APPEND package_comment
+ " Information extracted from:\n ${qa_chosen_attribution_file_path}\n")
+ endif()
+
+ if(NOT "${qa_chosen_attribution_entry_index}" STREQUAL "")
+ string(APPEND package_comment
+ " Entry index: ${qa_chosen_attribution_entry_index}\n")
+ endif()
+ endif()
+
+ if(package_comment)
+ list(APPEND project_package_options 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_RELATIONSHIPS relationships
+ )
+
+ get_cmake_property(project_spdx_id _qt_internal_sbom_project_spdx_id)
+ list(APPEND relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
+
+ list(REMOVE_DUPLICATES relationships)
+ list(JOIN relationships "\nRelationship: " relationships)
+ list(APPEND project_package_options RELATIONSHIP "${relationships}")
+
+ _qt_internal_sbom_generate_add_package(
+ PACKAGE "${package_name_for_spdx_id}"
+ SPDXID "${package_spdx_id}"
+ CONTAINS_FILES
+ ${project_package_options}
+ )
+
+ set(no_install_option "")
+ if(arg_NO_INSTALL)
+ set(no_install_option NO_INSTALL)
+ endif()
+
+ set(framework_option "")
+ if(APPLE AND NOT target_type STREQUAL "INTERFACE_LIBRARY")
+ get_target_property(is_framework ${target} FRAMEWORK)
+ if(is_framework)
+ set(framework_option "FRAMEWORK")
+ endif()
+ endif()
+
+ set(install_prefix_option "")
+ get_cmake_property(install_prefix _qt_internal_sbom_install_prefix)
+ if(install_prefix)
+ set(install_prefix_option INSTALL_PREFIX "${install_prefix}")
+ endif()
+
+ _qt_internal_forward_function_args(
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR target_binary_multi_config_args
+ FORWARD_SINGLE
+ ${multi_config_single_args}
+ )
+
+ _qt_internal_sbom_handle_target_binary_files("${target}"
+ ${no_install_option}
+ ${framework_option}
+ ${install_prefix_option}
+ TYPE "${arg_TYPE}"
+ ${target_binary_multi_config_args}
+ SPDX_ID "${package_spdx_id}"
+ COPYRIGHTS "${copyrights}"
+ LICENSE_EXPRESSION "${license_expression}"
+ )
+endfunction()
+
+# 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.
+function(_qt_internal_sbom_handle_target_dependencies target)
+ set(opt_args "")
+ set(single_args
+ SPDX_ID
+ OUT_RELATIONSHIPS
+ )
+ set(multi_args
+ LIBRARIES
+ PUBLIC_LIBRARIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_SPDX_ID)
+ message(FATAL_ERROR "SPDX_ID must be set")
+ endif()
+ set(package_spdx_id "${arg_SPDX_ID}")
+
+
+ set(libraries "")
+ if(arg_LIBRARIES)
+ list(APPEND libraries "${arg_LIBRARIES}")
+ endif()
+
+ get_target_property(extend_libraries "${target}" _qt_extend_target_libraries)
+ if(extend_libraries)
+ list(APPEND libraries ${extend_libraries})
+ endif()
+
+ get_target_property(target_type ${target} TYPE)
+ set(valid_target_types
+ EXECUTABLE
+ SHARED_LIBRARY
+ MODULE_LIBRARY
+ STATIC_LIBRARY
+ OBJECT_LIBRARY
+ )
+ if(target_type IN_LIST valid_target_types)
+ get_target_property(link_libraries "${target}" LINK_LIBRARIES)
+ if(link_libraries)
+ list(APPEND libraries ${link_Libraries})
+ endif()
+ endif()
+
+ set(public_libraries "")
+ if(arg_PUBLIC_LIBRARIES)
+ list(APPEND public_libraries "${arg_PUBLIC_LIBRARIES}")
+ endif()
+
+ get_target_property(extend_public_libraries "${target}" _qt_extend_target_public_libraries)
+ if(extend_public_libraries)
+ list(APPEND public_libraries ${extend_public_libraries})
+ endif()
+
+ set(sbom_dependencies "")
+ if(arg_SBOM_DEPENDENCIES)
+ list(APPEND sbom_dependencies "${arg_SBOM_DEPENDENCIES}")
+ endif()
+
+ get_target_property(extend_sbom_dependencies "${target}" _qt_extend_target_sbom_dependencies)
+ if(extend_sbom_dependencies)
+ list(APPEND sbom_dependencies ${extend_sbom_dependencies})
+ endif()
+
+ list(REMOVE_DUPLICATES libraries)
+ list(REMOVE_DUPLICATES public_libraries)
+ list(REMOVE_DUPLICATES sbom_dependencies)
+
+ set(all_direct_libraries ${libraries} ${public_libraries} ${sbom_dependencies})
+ list(REMOVE_DUPLICATES all_direct_libraries)
+
+ set(spdx_dependencies "")
+ set(relationships "")
+
+ # Go through each direct linked lib.
+ foreach(direct_lib IN LISTS all_direct_libraries)
+ if(NOT TARGET "${direct_lib}")
+ continue()
+ endif()
+
+ # Some targets are Qt modules, even though they are not prefixed with Qt::, targets
+ # like Bootstrap and QtLibraryInfo. We use the property to differentiate them.
+ get_target_property(is_marked_as_qt_module "${direct_lib}" _qt_sbom_is_qt_module)
+
+ # Custom sbom targets created by _qt_internal_create_sbom_target are always imported, so we
+ # need to differentiate them via this property.
+ get_target_property(is_custom_sbom_target "${direct_lib}" _qt_sbom_is_custom_sbom_target)
+
+ if("${direct_lib}" MATCHES "^(Qt::.*)|(${QT_CMAKE_EXPORT_NAMESPACE}::.*)")
+ set(is_qt_prefixed TRUE)
+ else()
+ set(is_qt_prefixed FALSE)
+ endif()
+
+ # is_qt_dependency is not strictly only a qt dependency, it applies to custom sbom
+ # targets as well. But I'm having a hard time to come up with a better name.
+ if(is_marked_as_qt_module OR is_custom_sbom_target OR is_qt_prefixed)
+ set(is_qt_dependency TRUE)
+ else()
+ set(is_qt_dependency FALSE)
+ endif()
+
+ # Regular Qt dependency, depend on the relevant package, either within the current
+ # document or via an external document.
+ if(is_qt_dependency)
+ _qt_internal_sbom_is_external_target_dependency("${direct_lib}"
+ OUT_VAR is_dependency_in_external_document
+ )
+
+ if(is_dependency_in_external_document)
+ # External document case.
+ _qt_internal_sbom_add_external_target_dependency(
+ "${package_spdx_id}" "${direct_lib}"
+ extra_spdx_dependencies
+ extra_spdx_relationships
+ )
+ if(extra_spdx_dependencies)
+ list(APPEND spdx_dependencies "${extra_spdx_dependencies}")
+ endif()
+ if(extra_spdx_relationships)
+ list(APPEND relationships "${extra_spdx_relationships}")
+ endif()
+ else()
+ # Dependency is part of current repo build.
+ _qt_internal_sbom_get_spdx_id_for_target("${direct_lib}" dep_spdx_id)
+ if(dep_spdx_id)
+ list(APPEND spdx_dependencies "${dep_spdx_id}")
+ else()
+ message(DEBUG "Could not add target dependency on ${direct_lib} "
+ "because no spdx id could be found")
+ endif()
+ endif()
+ else()
+ # If it's not a Qt dependency, then it's most likely a 3rd party dependency.
+ # If we are looking at a FindWrap dependency, we need to depend on either
+ # the system or vendored lib, whichever one the FindWrap script points to.
+ # If we are looking at a non-Wrap dependency, it's 99% a system lib.
+ __qt_internal_walk_libs(
+ "${direct_lib}"
+ lib_walked_targets
+ _discarded_out_var
+ "sbom_targets"
+ "collect_targets")
+
+ # Detect if we are dealing with a vendored / bundled lib.
+ set(bundled_targets_found FALSE)
+ if(lib_walked_targets)
+ 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 spdx_dependencies "${lib_spdx_id}")
+ set(bundled_targets_found TRUE)
+ endif()
+ endforeach()
+ endif()
+
+ # If no bundled libs were found as a result of walking the Wrap lib, we consider this
+ # a system lib, and add a dependency on it directly.
+ if(NOT bundled_targets_found)
+ _qt_internal_sbom_get_spdx_id_for_target("${direct_lib}" lib_spdx_id)
+ _qt_internal_sbom_is_external_target_dependency("${direct_lib}"
+ SYSTEM_LIBRARY
+ OUT_VAR is_dependency_in_external_document
+ )
+
+ if(lib_spdx_id)
+ if(NOT is_dependency_in_external_document)
+ list(APPEND spdx_dependencies "${lib_spdx_id}")
+
+ # Mark the system library is used, so that we later generate an sbom for it.
+ _qt_internal_append_to_cmake_property_without_duplicates(
+ _qt_internal_sbom_consumed_system_library_targets
+ "${direct_lib}"
+ )
+ 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.
+ _qt_internal_sbom_add_external_target_dependency(
+ "${package_spdx_id}" "${direct_lib}"
+ extra_spdx_dependencies
+ extra_spdx_relationships
+ )
+ if(extra_spdx_dependencies)
+ list(APPEND spdx_dependencies "${extra_spdx_dependencies}")
+ endif()
+ if(extra_spdx_relationships)
+ list(APPEND relationships "${extra_spdx_relationships}")
+ endif()
+ endif()
+ else()
+ message(DEBUG "Could not add target dependency on system library ${direct_lib} "
+ "because no spdx id could be found")
+ endif()
+ endif()
+ endif()
+ endforeach()
+
+ foreach(dep_spdx_id IN LISTS spdx_dependencies)
+ set(relationship
+ "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}"
+ )
+ list(APPEND relationships "${relationship}")
+ endforeach()
+
+ set(${arg_OUT_RELATIONSHIPS} "${relationships}" PARENT_SCOPE)
+endfunction()
+
+# Checks whether the current target will have its sbom generated into the current repo sbom
+# document, or whether it is present in an external sbom document.
+function(_qt_internal_sbom_is_external_target_dependency target)
+ set(opt_args
+ SYSTEM_LIBRARY
+ )
+ set(single_args
+ OUT_VAR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_target_property(is_imported "${target}" IMPORTED)
+ get_target_property(is_custom_sbom_target "${target}" _qt_sbom_is_custom_sbom_target)
+
+ _qt_internal_sbom_get_root_project_name_lower_case(current_repo_project_name)
+ get_property(target_repo_project_name TARGET ${target}
+ PROPERTY _qt_sbom_spdx_repo_project_name_lowercase)
+
+ if(NOT "${target_repo_project_name}" STREQUAL ""
+ AND NOT "${target_repo_project_name}" STREQUAL "${current_repo_project_name}")
+ set(part_of_other_repo TRUE)
+ else()
+ set(part_of_other_repo FALSE)
+ endif()
+
+ # A target is in an external document if
+ # 1) it is imported, and not a custom sbom target, and not a system library
+ # 2) it was created as part of another repo in a top-level build
+ if((is_imported AND NOT is_custom_sbom_target AND NOT arg_SYSTEM_LIBRARY)
+ OR part_of_other_repo)
+ set(is_dependency_in_external_document TRUE)
+ else()
+ set(is_dependency_in_external_document FALSE)
+ endif()
+
+ set(${arg_OUT_VAR} "${is_dependency_in_external_document}" PARENT_SCOPE)
+endfunction()
+
+# Handles generating an external document reference SDPX element for each target package that is
+# located in a different spdx document.
+function(_qt_internal_sbom_add_external_target_dependency
+ current_package_spdx_id
+ target_dep
+ out_spdx_dependencies
+ out_spdx_relationships
+ )
+ set(target "${target_dep}")
+
+ _qt_internal_sbom_get_spdx_id_for_target("${target}" dep_spdx_id)
+
+ if(NOT dep_spdx_id)
+ message(DEBUG "Could not add external target dependency on ${target} "
+ "because no spdx id could be found")
+ set(${out_spdx_dependencies} "" PARENT_SCOPE)
+ set(${out_spdx_relationships} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ set(spdx_dependencies "")
+ set(spdx_relationships "")
+
+ # Get the external document path and the repo it belongs to for the given target.
+ get_property(relative_installed_repo_document_path TARGET ${target}
+ PROPERTY _qt_sbom_spdx_relative_installed_repo_document_path)
+
+ get_property(project_name_lowercase TARGET ${target}
+ PROPERTY _qt_sbom_spdx_repo_project_name_lowercase)
+
+ 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)
+
+ get_cmake_property(known_external_document
+ _qt_known_external_documents_${external_document_ref})
+
+ set(relationship
+ "${current_package_spdx_id} DEPENDS_ON ${external_document_ref}:${dep_spdx_id}")
+
+ list(APPEND spdx_relationships "${relationship}")
+
+ # Only add a reference to the external document package, if we haven't done so already.
+ if(NOT known_external_document)
+ get_cmake_property(install_prefix _qt_internal_sbom_install_prefix)
+
+ set(external_document "${relative_installed_repo_document_path}")
+
+ _qt_internal_sbom_generate_add_external_reference(
+ NO_AUTO_RELATIONSHIP
+ EXTERNAL "${dep_spdx_id}"
+ FILENAME "${external_document}"
+ SPDXID "${external_document_ref}"
+ INSTALL_PREFIXES ${install_prefix}
+ )
+
+ set_property(GLOBAL PROPERTY
+ _qt_known_external_documents_${external_document_ref} TRUE)
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_known_external_documents "${external_document_ref}")
+ endif()
+ else()
+ message(WARNING "Missing spdx document path for external ref: "
+ "package_name_for_spdx_id ${package_name_for_spdx_id} direct_lib ${direct_lib}")
+ endif()
+
+ set(${out_spdx_dependencies} "${spdx_dependencies}" PARENT_SCOPE)
+ set(${out_spdx_relationships} "${spdx_relationships}" PARENT_SCOPE)
+endfunction()
+
+# Handles addition of binary files SPDX entries for a given target.
+# Is multi-config aware.
+function(_qt_internal_sbom_handle_target_binary_files target)
+ set(opt_args
+ NO_INSTALL
+ FRAMEWORK
+ )
+ set(single_args
+ TYPE
+ SPDX_ID
+ LICENSE_EXPRESSION
+ INSTALL_PREFIX
+ )
+ set(multi_args
+ COPYRIGHTS
+ )
+
+ _qt_internal_sbom_get_multi_config_single_args(multi_config_single_args)
+ list(APPEND single_args ${multi_config_single_args})
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_NO_INSTALL)
+ message(DEBUG "Skipping sbom target file processing ${target} because NO_INSTALL is set")
+ return()
+ endif()
+
+ set(supported_types
+ QT_MODULE
+ QT_PLUGIN
+ QT_APP
+ QT_TOOL
+ QT_THIRD_PARTY_MODULE
+ QT_THIRD_PARTY_SOURCES
+ SYSTEM_LIBRARY
+
+ # This will be meant for user projects, and are not currently used by Qt's sbom.
+ THIRD_PARTY_LIBRARY
+ THIRD_PARTY_LIBRARY_WITH_FILES
+ EXECUTABLE
+ LIBRARY
+ )
+
+ if(NOT arg_TYPE IN_LIST supported_types)
+ message(FATAL_ERROR "Unsupported target TYPE for SBOM creation: ${arg_TYPE}")
+ endif()
+
+ set(types_without_files
+ SYSTEM_LIBRARY
+ QT_THIRD_PARTY_SOURCES
+ THIRD_PARTY_LIBRARY
+ )
+
+ get_target_property(target_type ${target} TYPE)
+
+ if(arg_TYPE IN_LIST types_without_files)
+ message(DEBUG "Target ${target} has no binary files to reference in the SBOM "
+ "because it has the ${arg_TYPE} type.")
+ return()
+ endif()
+
+ if(target_type STREQUAL "INTERFACE_LIBRARY")
+ message(DEBUG "Target ${target} has no binary files to reference in the SBOM "
+ "because it is an INTERFACE_LIBRARY.")
+ return()
+ endif()
+
+ if(NOT arg_SPDX_ID)
+ message(FATAL_ERROR "SPDX_ID must be set")
+ endif()
+ set(package_spdx_id "${arg_SPDX_ID}")
+
+ set(file_common_options "")
+
+ list(APPEND file_common_options PACKAGE_SPDX_ID "${package_spdx_id}")
+ list(APPEND file_common_options PACKAGE_TYPE "${arg_TYPE}")
+
+ if(arg_COPYRIGHTS)
+ list(APPEND file_common_options COPYRIGHTS "${arg_COPYRIGHTS}")
+ endif()
+
+ if(arg_LICENSE_EXPRESSION)
+ list(APPEND file_common_options LICENSE_EXPRESSION "${arg_LICENSE_EXPRESSION}")
+ endif()
+
+ if(arg_INSTALL_PREFIX)
+ list(APPEND file_common_options INSTALL_PREFIX "${arg_INSTALL_PREFIX}")
+ endif()
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+ else()
+ set(configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ set(path_suffix "$")
+
+ if(arg_FRAMEWORK)
+ set(library_path_kind FRAMEWORK_PATH)
+ else()
+ set(library_path_kind LIBRARY_PATH)
+ endif()
+
+ if(arg_TYPE STREQUAL "QT_TOOL"
+ OR arg_TYPE STREQUAL "QT_APP"
+ OR arg_TYPE STREQUAL "EXECUTABLE")
+ if(NOT target_type STREQUAL "EXECUTABLE")
+ message(FATAL_ERROR "Unsupported target type: ${target_type}")
+ endif()
+
+ get_target_property(app_is_bundle ${target} MACOSX_BUNDLE)
+ if(app_is_bundle)
+ _qt_internal_get_executable_bundle_info(bundle "${target}")
+ _qt_internal_path_join(path_suffix "${bundle_contents_binary_dir}" "${path_suffix}")
+ endif()
+
+ _qt_internal_sbom_handle_multi_config_target_binary_file(${target}
+ PATH_KIND RUNTIME_PATH
+ PATH_SUFFIX "${path_suffix}"
+ OPTIONS ${file_common_options}
+ )
+ elseif(arg_TYPE STREQUAL "QT_PLUGIN")
+ if(NOT (target_type STREQUAL "SHARED_LIBRARY"
+ OR target_type STREQUAL "STATIC_LIBRARY"
+ OR target_type STREQUAL "MODULE_LIBRARY"))
+ message(FATAL_ERROR "Unsupported target type: ${target_type}")
+ endif()
+
+ _qt_internal_sbom_handle_multi_config_target_binary_file(${target}
+ PATH_KIND INSTALL_PATH
+ PATH_SUFFIX "${path_suffix}"
+ OPTIONS ${file_common_options}
+ )
+ elseif(arg_TYPE STREQUAL "QT_MODULE"
+ OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
+ OR arg_TYPE STREQUAL "LIBRARY"
+ OR arg_TYPE STREQUAL "THIRD_PARTY_LIBRARY_WITH_FILES"
+ )
+ if(WIN32 AND target_type STREQUAL "SHARED_LIBRARY")
+ _qt_internal_sbom_handle_multi_config_target_binary_file(${target}
+ PATH_KIND RUNTIME_PATH
+ PATH_SUFFIX "${path_suffix}"
+ OPTIONS ${file_common_options}
+ )
+
+ _qt_internal_sbom_handle_multi_config_target_binary_file(${target}
+ PATH_KIND ARCHIVE_PATH
+ PATH_SUFFIX "$"
+ OPTIONS
+ ${file_common_options}
+ IMPORT_LIBRARY
+ # OPTIONAL because on Windows the import library might not always be present,
+ # because no symbols are exported.
+ OPTIONAL
+ )
+ elseif(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "STATIC_LIBRARY")
+ _qt_internal_sbom_handle_multi_config_target_binary_file(${target}
+ PATH_KIND "${library_path_kind}"
+ PATH_SUFFIX "${path_suffix}"
+ OPTIONS ${file_common_options}
+ )
+ else()
+ message(FATAL_ERROR "Unsupported target type: ${target_type}")
+ endif()
+ endif()
+endfunction()
+
+# Add a binary file of a target to the sbom (e.g a shared library or an executable).
+# Adds relationships to the SBOM that the binary file was generated from its source files,
+# as well as relationship to the owning package.
+function(_qt_internal_sbom_add_binary_file target file_path)
+ set(opt_args
+ OPTIONAL
+ IMPORT_LIBRARY
+ )
+ set(single_args
+ PACKAGE_SPDX_ID
+ PACKAGE_TYPE
+ LICENSE_EXPRESSION
+ CONFIG
+ INSTALL_PREFIX
+ )
+ set(multi_args
+ COPYRIGHTS
+ )
+ cmake_parse_arguments(PARSE_ARGV 2 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_PACKAGE_SPDX_ID)
+ message(FATAL_ERROR "PACKAGE_SPDX_ID must be set")
+ endif()
+
+ set(file_common_options "")
+
+ if(arg_COPYRIGHTS)
+ list(JOIN arg_COPYRIGHTS "\n" copyrights)
+ list(APPEND file_common_options COPYRIGHT "${copyrights}")
+ endif()
+
+ if(arg_LICENSE_EXPRESSION)
+ list(APPEND file_common_options LICENSE "${arg_LICENSE_EXPRESSION}")
+ endif()
+
+ if(arg_INSTALL_PREFIX)
+ list(APPEND file_common_options INSTALL_PREFIX "${arg_INSTALL_PREFIX}")
+ endif()
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+ else()
+ set(configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ if(is_multi_config)
+ set(spdx_id_suffix "${arg_CONFIG}")
+ set(config_to_install_option CONFIG ${arg_CONFIG})
+ else()
+ set(spdx_id_suffix "")
+ set(config_to_install_option "")
+ endif()
+
+ set(file_infix "")
+ if(arg_IMPORT_LIBRARY)
+ set(file_infix "-ImportLibrary")
+ endif()
+
+ # We kind of have to add the package infix into the file spdx, otherwise we get file system
+ # collisions for cases like the qml tool and Qml library, and apparently cmake's file(GENERATE)
+ # is case insensitive for file names.
+ _qt_internal_sbom_get_package_infix("${arg_PACKAGE_TYPE}" package_infix)
+
+ _qt_internal_sbom_get_file_spdx_id(
+ "${package_infix}-${target}-${file_infix}-${spdx_id_suffix}" spdx_id)
+
+ set(optional "")
+ if(arg_OPTIONAL)
+ set(optional OPTIONAL)
+ endif()
+
+ # Add relationship from owning package.
+ set(relationships "${arg_PACKAGE_SPDX_ID} CONTAINS ${spdx_id}")
+
+ # Add source file relationships from which the binary file was generated.
+ _qt_internal_sbom_add_source_files("${target}" "${spdx_id}" source_relationships)
+ if(source_relationships)
+ list(APPEND relationships "${source_relationships}")
+ endif()
+
+ set(glue "\nRelationship: ")
+ # Replace semicolon with $ to avoid errors when passing into sbom_add.
+ string(REPLACE ";" "$" relationships "${relationships}")
+
+ # Glue the relationships at generation time, because there some source file relationships
+ # will be conditional on genexes, and evaluate to an empty value, and we want to discard
+ # such relationships.
+ set(relationships "$")
+ set(relationship_option RELATIONSHIP "${relationships}")
+
+ # Add the actual binary file to the latest package.
+ _qt_internal_sbom_generate_add_file(
+ FILENAME "${file_path}"
+ FILETYPE BINARY ${optional}
+ SPDXID "${spdx_id}"
+ ${file_common_options}
+ ${config_to_install_option}
+ ${relationship_option}
+ )
+endfunction()
+
+# Adds source file "generated from" relationship comments to the sbom for a given target.
+function(_qt_internal_sbom_add_source_files target spdx_id out_relationships)
+ get_target_property(sources ${target} SOURCES)
+ list(REMOVE_DUPLICATES sources)
+
+ set(relationships "")
+
+ foreach(source IN LISTS sources)
+ # Filter out $.
+ if(source MATCHES "^\\$$")
+ continue()
+ endif()
+
+ # Filter out prl files.
+ if(source MATCHES "\.prl$")
+ continue()
+ endif()
+
+ set(source_entry
+"${spdx_id} GENERATED_FROM NOASSERTION\nRelationshipComment: ${CMAKE_CURRENT_SOURCE_DIR}/${source}"
+ )
+ set(source_non_empty "$")
+ # Some sources are conditional on genexes, so we evaluate them.
+ set(relationship "$<${source_non_empty}:$>")
+ list(APPEND relationships "${relationship}")
+ endforeach()
+
+ set(${out_relationships} "${relationships}" PARENT_SCOPE)
+endfunction()
+
+# Adds a license id and its text to the sbom.
+function(_qt_internal_sbom_add_license)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args
+ NO_LICENSE_REF_PREFIX
+ )
+ set(single_args
+ LICENSE_ID
+ LICENSE_PATH
+ EXTRACTED_TEXT
+ )
+ 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_LICENSE_ID)
+ message(FATAL_ERROR "LICENSE_ID must be set")
+ endif()
+
+ if(NOT arg_TEXT AND NOT arg_LICENSE_PATH)
+ message(FATAL_ERROR "Either TEXT or LICENSE_PATH must be set")
+ endif()
+
+ # Sanitize the content a bit.
+ if(arg_TEXT)
+ set(text "${arg_TEXT}")
+ string(REPLACE ";" "$" text "${text}")
+ string(REPLACE "\"" "\\\"" text "${text}")
+ else()
+ file(READ "${arg_LICENSE_PATH}" text)
+ string(REPLACE ";" "$" text "${text}")
+ string(REPLACE "\"" "\\\"" text "${text}")
+ endif()
+
+ set(license_id "${arg_LICENSE_ID}")
+ if(NOT arg_NO_LICENSE_REF_PREFIX)
+ set(license_id "LicenseRef-${license_id}")
+ endif()
+
+ _qt_internal_sbom_generate_add_license(
+ LICENSE_ID "${license_id}"
+ EXTRACTED_TEXT "${text}"
+ )
+endfunction()
+
+# Records information about a system library target, usually due to a qt_find_package call.
+# This information is later used to generate packages for the system libraries, but only after
+# confirming that the library was used (linked) into any of the Qt targets.
+function(_qt_internal_sbom_record_system_library_usage target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args "")
+ set(single_args
+ TYPE
+ PACKAGE_VERSION
+ FRIENDLY_PACKAGE_NAME
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_TYPE)
+ message(FATAL_ERROR "TYPE must be set")
+ endif()
+
+ # A package might be looked up more than once, make sure to record it once.
+ get_property(already_recorded GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_target_${target})
+
+ if(already_recorded)
+ return()
+ endif()
+
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_target_${target} TRUE)
+
+ # Defer spdx id creation until _qt_internal_sbom_begin_project is called, so we know the
+ # project name. The project name is used in the package infix generation of the system library,
+ # but _qt_internal_sbom_record_system_library_usage might be called before sbom generation
+ # has started, e.g. during _qt_internal_find_third_party_dependencies.
+ set(spdx_options
+ ${target}
+ TYPE "${arg_TYPE}"
+ PACKAGE_NAME "${arg_FRIENDLY_PACKAGE_NAME}"
+ )
+
+ get_cmake_property(sbom_repo_begin_called _qt_internal_sbom_repo_begin_called)
+ if(sbom_repo_begin_called)
+ _qt_internal_sbom_record_system_library_spdx_id(${target} ${spdx_options})
+ else()
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_spdx_options_${target} "${spdx_options}")
+ endif()
+
+ # Defer sbom info creation until we detect usage of the system library (whether the library is
+ # linked into any other target).
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_internal_sbom_recorded_system_library_targets "${target}")
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_options_${target} "${ARGN}")
+endfunction()
+
+# Helper to record spdx ids of all system library targets that were found so far.
+function(_qt_internal_sbom_record_system_library_spdx_ids)
+ get_property(recorded_targets GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets)
+
+ if(NOT recorded_targets)
+ return()
+ endif()
+
+ foreach(target IN LISTS recorded_targets)
+ get_property(args GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_spdx_options_${target})
+ _qt_internal_sbom_record_system_library_spdx_id(${target} ${args})
+ endforeach()
+endfunction()
+
+# Helper to record the spdx id of a system library target.
+function(_qt_internal_sbom_record_system_library_spdx_id target)
+ # Save the spdx id before the sbom info is added, so we can refer to it in relationships.
+ _qt_internal_sbom_record_target_spdx_id(${ARGN} OUT_VAR package_spdx_id)
+
+ if(NOT package_spdx_id)
+ message(FATAL_ERROR "Could not generate spdx id for system library target: ${target}")
+ endif()
+
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_package_${target} "${package_spdx_id}")
+endfunction()
+
+# Goes through the list of consumed system libraries (those that were linked in) and creates
+# sbom packages for them.
+# Uses information from recorded system libraries (calls to qt_find_package).
+function(_qt_internal_sbom_add_recorded_system_libraries)
+ get_property(recorded_targets GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets)
+ get_property(consumed_targets GLOBAL PROPERTY _qt_internal_sbom_consumed_system_library_targets)
+
+ set(unconsumed_targets "${recorded_targets}")
+ set(generated_package_names "")
+
+ foreach(target IN LISTS consumed_targets)
+ get_property(args GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_options_${target})
+ get_property(package_name GLOBAL PROPERTY
+ _qt_internal_sbom_recorded_system_library_package_${target})
+
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_target_${target} "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_options_${target} "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_package_${target} "")
+
+ # Guard against generating a package multiple times. Can happen when multiple targets belong
+ # to the same package.
+ if(sbom_generated_${package_name})
+ continue()
+ endif()
+
+ list(APPEND generated_package_names "${package_name}")
+ set(sbom_generated_${package_name} TRUE)
+
+ _qt_internal_extend_sbom(${target} ${args})
+ _qt_internal_finalize_sbom(${target})
+
+ list(REMOVE_ITEM unconsumed_targets "${target}")
+ endforeach()
+
+ message(DEBUG "System libraries that were recorded, but not consumed: ${unconsumed_targets}")
+ message(DEBUG "Generated SBOMs for the following system packages: ${generated_package_names}")
+
+ # Clean up, before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_consumed_system_library_targets "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets "")
+endfunction()
+
+# Helper to add sbom information for a possibly non-existing target.
+# This will defer the actual sbom generation until the end of the directory scope, unless
+# immediate finalization was requested.
+function(_qt_internal_add_sbom target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args
+ IMMEDIATE_FINALIZATION
+ )
+ set(single_args
+ TYPE
+ FRIENDLY_PACKAGE_NAME
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ # No validation on purpose, the other options will be validated later.
+
+ set(forward_args ${ARGN})
+
+ # Remove the IMMEDIATE_FINALIZATION from the forwarded args.
+ list(REMOVE_ITEM forward_args IMMEDIATE_FINALIZATION)
+
+ # If a target doesn't exist we create it.
+ if(NOT TARGET "${target}")
+ _qt_internal_create_sbom_target("${target}" ${forward_args})
+ endif()
+
+ # Save the passed options.
+ _qt_internal_extend_sbom("${target}" ${forward_args})
+
+ # 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.
+ set(immediate_finalization "")
+ if(arg_IMMEDIATE_FINALIZATION)
+ set(immediate_finalization IMMEDIATE_FINALIZATION)
+ endif()
+ _qt_internal_defer_sbom_finalization("${target}" ${immediate_finalization})
+endfunction()
+
+# Helper to add custom sbom information for some kind of dependency that is not backed by an
+# existing target.
+# Useful for cases like 3rd party dependencies not represented by an already existing imported
+# target, or for 3rd party sources that get compiled into a regular Qt target (PCRE sources compiled
+# into Bootstrap).
+function(_qt_internal_create_sbom_target target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args "")
+ set(single_args
+ TYPE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ # No validation on purpose, the other options will be validated later.
+
+ if(TARGET "${target}")
+ message(FATAL_ERROR "The target ${target} already exists.")
+ endif()
+
+ add_library("${target}" INTERFACE IMPORTED)
+ set_target_properties(${target} PROPERTIES
+ _qt_sbom_is_custom_sbom_target "TRUE"
+ IMPORTED_GLOBAL TRUE
+ )
+
+ if(NOT arg_TYPE)
+ message(FATAL_ERROR "No SBOM TYPE option was provided for target: ${target}")
+ endif()
+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.
+function(_qt_internal_extend_sbom target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ if(NOT TARGET "${target}")
+ message(FATAL_ERROR
+ "The target ${target} does not exist, use qt_internal_add_sbom to create "
+ "a target first, or call the function on any other exsiting target.")
+ endif()
+
+ set(opt_args "")
+ set(single_args
+ TYPE
+ FRIENDLY_PACKAGE_NAME
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ # No validation on purpose, the other options will be validated later.
+
+ # 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(arg_TYPE)
+ # Friendly package name is allowed to be empty.
+ _qt_internal_sbom_record_target_spdx_id(${target}
+ TYPE "${arg_TYPE}"
+ PACKAGE_NAME "${arg_FRIENDLY_PACKAGE_NAME}"
+ )
+ endif()
+
+ set_property(TARGET ${target} APPEND PROPERTY _qt_finalize_sbom_args "${ARGN}")
+endfunction()
+
+# Helper to add additional sbom information to targets created by qt_find_package.
+# If the package was not found, and the targets were not created, the functions does nothing.
+# This is similar to _qt_internal_extend_sbom, but is explicit in the fact that the targets might
+# not exist.
+function(_qt_find_package_extend_sbom)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ _qt_internal_get_sbom_add_target_options(sbom_opt_args sbom_single_args sbom_multi_args)
+
+ set(opt_args
+ ${sbom_opt_args}
+ )
+ set(single_args
+ ${sbom_single_args}
+ )
+ set(multi_args
+ TARGETS
+ ${sbom_multi_args}
+ )
+
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ # Make sure not to forward TARGETS.
+ set(sbom_args "")
+ _qt_internal_forward_function_args(
+ FORWARD_APPEND
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR sbom_args
+ FORWARD_OPTIONS
+ ${sbom_opt_args}
+ FORWARD_SINGLE
+ ${sbom_single_args}
+ FORWARD_MULTI
+ ${sbom_multi_args}
+ )
+
+ foreach(target IN LISTS arg_TARGETS)
+ if(TARGET "${target}")
+ _qt_internal_extend_sbom("${target}" ${sbom_args})
+ else()
+ message(DEBUG "The target ${target} does not exist, skipping extending the sbom info.")
+ endif()
+ endforeach()
+endfunction()
+
+# Helper to defer adding sbom information for a target, at the end of the directory scope.
+function(_qt_internal_defer_sbom_finalization target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args
+ IMMEDIATE_FINALIZATION
+ )
+ set(single_args "")
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_target_property(sbom_finalization_requested ${target} _qt_sbom_finalization_requested)
+ if(sbom_finalization_requested)
+ # Already requested, nothing to do.
+ return()
+ endif()
+ set_target_properties(${target} PROPERTIES _qt_sbom_finalization_requested TRUE)
+
+ _qt_internal_append_to_cmake_property_without_duplicates(
+ _qt_internal_sbom_targets_waiting_for_finalization
+ "${target}"
+ )
+
+ set(func "_qt_internal_finalize_sbom")
+
+ if(arg_IMMEDIATE_FINALIZATION)
+ _qt_internal_finalize_sbom(${target})
+ elseif(QT_BUILDING_QT)
+ qt_add_list_file_finalizer("${func}" "${target}")
+ elseif(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19")
+ cmake_language(EVAL CODE "cmake_language(DEFER CALL \"${func}\" \"${target}\")")
+ else()
+ message(FATAL_ERROR "Defer adding a sbom target requires CMake version 3.19")
+ endif()
+endfunction()
+
+# Finalizer to add sbom information for the target.
+# Expects the target to exist.
+function(_qt_internal_finalize_sbom target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ get_target_property(sbom_finalization_done ${target} _qt_sbom_finalization_done)
+ if(sbom_finalization_done)
+ # Already done, nothing to do.
+ return()
+ endif()
+ set_target_properties(${target} PROPERTIES _qt_sbom_finalization_done TRUE)
+
+ get_target_property(sbom_args ${target} _qt_finalize_sbom_args)
+ if(NOT sbom_args)
+ set(sbom_args "")
+ endif()
+ _qt_internal_sbom_add_target(${target} ${sbom_args})
+endfunction()
+
+# Extends the list of targets that are considered dependencies for target.
+function(_qt_internal_extend_sbom_dependencies target)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ set(opt_args "")
+ set(single_args "")
+ set(multi_args
+ SBOM_DEPENDENCIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT TARGET "${target}")
+ message(FATAL_ERROR "The target ${target} does not exist.")
+ endif()
+
+ _qt_internal_append_to_target_property_without_duplicates(${target}
+ _qt_extend_target_sbom_dependencies "${arg_SBOM_DEPENDENCIES}"
+ )
+endfunction()
+
+# Handles attribution information for a target.
+#
+# If CREATE_SBOM_FOR_EACH_ATTRIBUTION is set, a separate sbom target is created for each parsed
+# attribution entry, and the new targets are added as dependencies to the parent target.
+#
+# If CREATE_SBOM_FOR_EACH_ATTRIBUTION is not set, the information read from the first attribution
+# entry is added directly to the parent target, aka the the values are propagated to the outer
+# function scope to be read.. The rest of the attribution entries are created as separate targets
+# and added as dependencies, as if the option was passed.
+#
+# Handles multiple attribution files and entries within a file.
+# Attribution files can be specified either via directories and direct file paths.
+# If ATTRIBUTION_ENTRY_INDEX is set, only that specific attribution entry will be processed
+# from the given attribution file.
+function(_qt_internal_sbom_handle_qt_attribution_files out_prefix_outer)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ if(CMAKE_VERSION LESS_EQUAL "3.19")
+ message(DEBUG "CMake version is too low, can't parse attribution.json file.")
+ return()
+ endif()
+
+ set(opt_args
+ CREATE_SBOM_FOR_EACH_ATTRIBUTION
+ )
+ set(single_args
+ PARENT_TARGET
+ ATTRIBUTION_ENTRY_INDEX
+ )
+ set(multi_args
+ ATTRIBUTION_FILE_PATHS
+ ATTRIBUTION_FILE_DIR_PATHS
+ )
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(attribution_files "")
+ set(attribution_file_count 0)
+
+ foreach(attribution_file_path IN LISTS arg_ATTRIBUTION_FILE_PATHS)
+ get_filename_component(real_path "${attribution_file_path}" REALPATH)
+ list(APPEND attribution_files "${real_path}")
+ math(EXPR attribution_file_count "${attribution_file_count} + 1")
+ endforeach()
+
+ foreach(attribution_file_dir_path IN LISTS arg_ATTRIBUTION_FILE_DIR_PATHS)
+ get_filename_component(real_path
+ "${attribution_file_dir_path}/qt_attribution.json" REALPATH)
+ list(APPEND attribution_files "${real_path}")
+ math(EXPR attribution_file_count "${attribution_file_count} + 1")
+ endforeach()
+
+ if(arg_CREATE_SBOM_FOR_EACH_ATTRIBUTION)
+ if(NOT arg_PARENT_TARGET)
+ message(FATAL_ERROR "PARENT_TARGET must be set")
+ endif()
+ endif()
+
+ if(arg_ATTRIBUTION_ENTRY_INDEX AND attribution_file_count GREATER 1)
+ message(FATAL_ERROR
+ "ATTRIBUTION_ENTRY_INDEX should only be set if a single attribution "
+ "file is specified."
+ )
+ endif()
+
+ set(file_index 0)
+ set(first_attribution_processed FALSE)
+ foreach(attribution_file_path IN LISTS attribution_files)
+ # Set a unique out_prefix that will not overlap when multiple entries are processed.
+ set(out_prefix_file "${out_prefix_outer}_${file_index}")
+
+ # Get the number of entries in the attribution file.
+ _qt_internal_sbom_read_qt_attribution(${out_prefix_file}
+ GET_ATTRIBUTION_ENTRY_COUNT
+ OUT_VAR_VALUE attribution_entry_count
+ FILE_PATH "${attribution_file_path}"
+ )
+
+ # If a specific entry was specified, we will only process it from the file.
+ if(NOT "${arg_ATTRIBUTION_ENTRY_INDEX}" STREQUAL "")
+ set(entry_index ${arg_ATTRIBUTION_ENTRY_INDEX})
+ else()
+ set(entry_index 0)
+ endif()
+
+ # Go through each entry in the attribution file.
+ while("${entry_index}" LESS "${${out_prefix_file}_attribution_entry_count}")
+ # If this is the first entry to be processed, or if CREATE_SBOM_FOR_EACH_ATTRIBUTION
+ # is not set, we read the attribution file entry directly, and propagate the values
+ # to the parent scope.
+ if(NOT first_attribution_processed AND NOT arg_CREATE_SBOM_FOR_EACH_ATTRIBUTION)
+ # Set a prefix without indices, so that the parent scope add_sbom call can
+ # refer to the values directly with the outer prefix, without any index infix.
+ set(out_prefix "${out_prefix_outer}")
+
+ _qt_internal_sbom_read_qt_attribution(${out_prefix}
+ GET_DEFAULT_KEYS
+ ENTRY_INDEX "${entry_index}"
+ OUT_VAR_ASSIGNED_VARIABLE_NAMES variable_names
+ FILE_PATH "${attribution_file_path}"
+ )
+
+ # Propagate the values to the outer scope.
+ foreach(variable_name IN LISTS variable_names)
+ set(${out_prefix}_${variable_name} "${${out_prefix}_${variable_name}}"
+ PARENT_SCOPE)
+ endforeach()
+
+ get_filename_component(relative_attribution_file_path
+ "${attribution_file_path}" REALPATH)
+
+ set(${out_prefix}_chosen_attribution_file_path "${relative_attribution_file_path}"
+ PARENT_SCOPE)
+ set(${out_prefix}_chosen_attribution_entry_index "${entry_index}"
+ PARENT_SCOPE)
+
+ set(first_attribution_processed TRUE)
+ if(NOT "${arg_ATTRIBUTION_ENTRY_INDEX}" STREQUAL "")
+ # We had a specific index to process, so break right after processing it.
+ break()
+ endif()
+ else()
+ # We are processing the second or later entry, or CREATE_SBOM_FOR_EACH_ATTRIBUTION
+ # was set. Instead of directly reading all the keys from the attribution file,
+ # we get the Id, and create a new sbom target for the entry.
+ # That will recursively call this function with a specific attribution file path
+ # and index, to process the specific entry.
+
+ set(out_prefix "${out_prefix_outer}_${file_index}_${entry_index}")
+
+ # Get the attribution id.
+ _qt_internal_sbom_read_qt_attribution(${out_prefix}
+ GET_KEY
+ KEY Id
+ OUT_VAR_VALUE attribution_id
+ ENTRY_INDEX "${entry_index}"
+ FILE_PATH "${attribution_file_path}"
+ )
+
+ # If no Id was retrieved, just add a numeric one, to make the sbom target
+ # unique.
+ set(attribution_target "${arg_PARENT_TARGET}_Attribution_")
+ if(NOT ${out_prefix}_attribution_id)
+ string(APPEND attribution_target "${file_index}_${entry_index}")
+ else()
+ string(APPEND attribution_target "${${out_prefix}_attribution_id}")
+ endif()
+
+ # Create another sbom target with the id as a hint for the target name,
+ # the attribution file passed, and make the new target a dependency of the
+ # parent one.
+ _qt_internal_add_sbom("${attribution_target}"
+ IMMEDIATE_FINALIZATION
+ TYPE QT_THIRD_PARTY_SOURCES
+ ATTRIBUTION_FILE_PATHS "${attribution_file_path}"
+ ATTRIBUTION_ENTRY_INDEX "${entry_index}"
+ NO_CURRENT_DIR_ATTRIBUTION
+ )
+
+ _qt_internal_extend_sbom_dependencies(${arg_PARENT_TARGET}
+ SBOM_DEPENDENCIES ${attribution_target}
+ )
+ endif()
+
+ math(EXPR entry_index "${entry_index} + 1")
+ endwhile()
+
+ math(EXPR file_index "${file_index} + 1")
+ endforeach()
+endfunction()
+
+# Helper to parse a qt_attribution.json file and do various operations:
+# - GET_DEFAULT_KEYS extracts the license id, copyrights, version, etc.
+# - GET_KEY extracts a single given json key's value, as specified with KEY and saved into
+# OUT_VAR_VALUE
+# - GET_ATTRIBUTION_ENTRY_COUNT returns the number of entries in the json file, set in
+# OUT_VAR_VALUE
+#
+# ENTRY_INDEX can be used to specify the array index to select a specific entry in the json file.
+#
+# Any retrieved value is set in the outer scope.
+# The variables are prefixed with ${out_prefix}.
+# OUT_VAR_ASSIGNED_VARIABLE_NAMES contains the list of variables set in the parent scope, the
+# variables names in this list are not prefixed with ${out_prefix}.
+#
+# Requires cmake 3.19 for json parsing.
+function(_qt_internal_sbom_read_qt_attribution out_prefix)
+ if(NOT QT_GENERATE_SBOM)
+ return()
+ endif()
+
+ if(CMAKE_VERSION LESS_EQUAL "3.19")
+ message(DEBUG "CMake version is too low, can't parse attribution.json file.")
+ return()
+ endif()
+
+ set(opt_args
+ GET_DEFAULT_KEYS
+ GET_KEY
+ GET_ATTRIBUTION_ENTRY_COUNT
+ )
+ set(single_args
+ FILE_PATH
+ KEY
+ ENTRY_INDEX
+ OUT_VAR_VALUE
+ OUT_VAR_ASSIGNED_VARIABLE_NAMES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(file_path "${arg_FILE_PATH}")
+
+ if(NOT file_path)
+ message(FATAL_ERROR "qt attribution file path not given")
+ endif()
+
+ file(READ "${file_path}" contents)
+ if(NOT contents)
+ message(FATAL_ERROR "qt attribution file is empty: ${file_path}")
+ endif()
+
+ if(NOT arg_GET_DEFAULT_KEYS AND NOT arg_GET_KEY AND NOT arg_GET_ATTRIBUTION_ENTRY_COUNT)
+ message(FATAL_ERROR
+ "No valid operation specified to _qt_internal_sbom_read_qt_attribution call.")
+ endif()
+
+ if(arg_GET_KEY)
+ if(NOT arg_KEY)
+ message(FATAL_ERROR "KEY must be set")
+ endif()
+ if(NOT arg_OUT_VAR_VALUE)
+ message(FATAL_ERROR "OUT_VAR_VALUE must be set")
+ endif()
+ endif()
+
+ get_filename_component(attribution_file_dir "${file_path}" DIRECTORY)
+
+ # Parse the json file.
+ # The first element might be an array, or an object. We need to detect which one.
+ # Do that by trying to query index 0 of the potential root array.
+ # If the index is found, that means the root is an array, and elem_error is set to NOTFOUND,
+ # because there was no error.
+ # Otherwise elem_error will be something like 'member '0' not found', and we can assume the
+ # root is an object.
+ string(JSON first_elem_type ERROR_VARIABLE elem_error TYPE "${contents}" 0)
+ if(elem_error STREQUAL "NOTFOUND")
+ # Root is an array. The attribution file might contain multiple entries.
+ # Pick the first one if no specific index was specified, otherwise use the given index.
+ if(NOT "${arg_ENTRY_INDEX}" STREQUAL "")
+ set(indices "${arg_ENTRY_INDEX}")
+ else()
+ set(indices "0")
+ endif()
+ set(is_array TRUE)
+ else()
+ # Root is an object, not an array, which means the file has a single entry.
+ set(indices "")
+ set(is_array FALSE)
+ endif()
+
+ set(variable_names "")
+
+ if(arg_GET_KEY)
+ _qt_internal_sbom_get_attribution_key(${arg_KEY} ${arg_OUT_VAR_VALUE} ${out_prefix})
+ endif()
+
+ if(arg_GET_ATTRIBUTION_ENTRY_COUNT)
+ if(NOT arg_OUT_VAR_VALUE)
+ message(FATAL_ERROR "OUT_VAR_VALUE must be set")
+ endif()
+
+ if(is_array)
+ string(JSON attribution_entry_count ERROR_VARIABLE elem_error LENGTH "${contents}")
+ # There was an error getting the length of the array, so we assume it's empty.
+ if(NOT elem_error STREQUAL "NOTFOUND")
+ set(attribution_entry_count 0)
+ endif()
+ else()
+ set(attribution_entry_count 1)
+ endif()
+
+ set(${out_prefix}_${arg_OUT_VAR_VALUE} "${attribution_entry_count}" PARENT_SCOPE)
+ endif()
+
+ if(arg_GET_DEFAULT_KEYS)
+ # Some calls are currently commented out, to save on json parsing time because we don't have
+ # a usage for them yet.
+ # _qt_internal_sbom_get_attribution_key(License license)
+ _qt_internal_sbom_get_attribution_key(LicenseId license_id "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(Version version "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(Homepage homepage "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(Name attribution_name "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(Description description "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(QtUsage qt_usage "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(DownloadLocation download_location "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(Copyright copyrights "${out_prefix}")
+ _qt_internal_sbom_get_attribution_key(CopyrightFile copyright_file "${out_prefix}")
+
+ # In some attribution files (like harfbuzz) Copyright contains an array of copyrights rather
+ # than a single string. Extract all of them.
+ if(copyrights)
+ string(JSON copyright_type TYPE "${contents}" ${indices} Copyright)
+ if(copyright_type STREQUAL "ARRAY")
+ set(copyright_json_array "${copyrights}")
+ string(JSON array_len LENGTH "${copyright_json_array}")
+
+ set(copyright_list "")
+ set(index 0)
+ while(index LESS array_len)
+ string(JSON copyright GET "${copyright_json_array}" ${index})
+ if(copyright)
+ list(APPEND copyright_list "${copyright}")
+ endif()
+ math(EXPR index "${index} + 1")
+ endwhile()
+
+ if(copyright_list)
+ set(${out_prefix}_copyrights "${copyright_list}" PARENT_SCOPE)
+ list(APPEND variable_names "copyrights")
+ endif()
+ endif()
+ endif()
+
+ # Some attribution files contain a copyright file that contains the actual list of
+ # copyrights. Read it and use it.
+ set(copyright_file_path "${attribution_file_dir}/${copyright_file}")
+ get_filename_component(copyright_file_path "${copyright_file_path}" REALPATH)
+ if(NOT copyrights AND copyright_file AND EXISTS "${copyright_file_path}")
+ file(READ "${copyright_file_path}" copyright_contents)
+ if(copyright_contents)
+ set(copyright_contents "${copyright_contents}")
+ set(copyrights "${copyright_contents}")
+ set(${out_prefix}_copyrights "${copyright_contents}" PARENT_SCOPE)
+ list(APPEND variable_names "copyrights")
+ endif()
+ endif()
+ endif()
+
+ if(arg_OUT_VAR_ASSIGNED_VARIABLE_NAMES)
+ set(${arg_OUT_VAR_ASSIGNED_VARIABLE_NAMES} "${variable_names}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Escapes various characters in json content, so that the generate cmake code to append the content
+# to the spdx document is syntactically valid.
+function(_qt_internal_sbom_escape_json_content content out_var)
+ # Escape backslashes
+ string(REPLACE "\\" "\\\\" escaped_content "${content}")
+
+ # Escape quotes
+ string(REPLACE "\"" "\\\"" escaped_content "${escaped_content}")
+
+ set(${out_var} "${escaped_content}" PARENT_SCOPE)
+endfunction()
+
+# Reads a json key from a qt_attribution.json file, and assigns it to out_var.
+# Also adds the out_var to the parent scope ${variable_names}.
+# Expects contents, indices and out_prefix to be set in parent scope.
+macro(_qt_internal_sbom_get_attribution_key json_key out_var out_prefix)
+ string(JSON "${out_var}" ERROR_VARIABLE get_error GET "${contents}" ${indices} "${json_key}")
+ if(NOT "${${out_var}}" STREQUAL "" AND NOT get_error)
+ _qt_internal_sbom_escape_json_content("${${out_var}}" escaped_content)
+ set(${out_prefix}_${out_var} "${escaped_content}" PARENT_SCOPE)
+ list(APPEND variable_names "${out_var}")
+ endif()
+endmacro()
+
+# Set sbom project name for the root project.
+function(_qt_internal_sbom_set_root_project_name project_name)
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_project_name "${project_name}")
+endfunction()
+
+# Get repo project_name spdx id reference, needs to start with Package- to be NTIA compliant.
+function(_qt_internal_sbom_get_root_project_name_for_spdx_id out_var)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(sbom_repo_project_name "Package-${repo_project_name_lowercase}")
+ set(${out_var} "${sbom_repo_project_name}" PARENT_SCOPE)
+endfunction()
+
+# Just a lower case sbom project name.
+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.")
+ endif()
+
+ string(TOLOWER "${project_name}" repo_project_name_lowercase)
+ set(${out_var} "${repo_project_name_lowercase}" PARENT_SCOPE)
+endfunction()
+
+# Get a spdx id to reference an external document.
+function(_qt_internal_sbom_get_external_document_ref_spdx_id repo_name out_var)
+ set(${out_var} "DocumentRef-${repo_name}" PARENT_SCOPE)
+endfunction()
+
+# Sanitize a given value to be used as a SPDX id.
+function(_qt_internal_sbom_get_sanitized_spdx_id out_var hint)
+ # Only allow alphanumeric characters and dashes.
+ string(REGEX REPLACE "[^a-zA-Z0-9]+" "-" spdx_id "${hint}")
+
+ # Remove all trailing dashes.
+ string(REGEX REPLACE "-+$" "" spdx_id "${spdx_id}")
+
+ set(${out_var} "${spdx_id}" PARENT_SCOPE)
+endfunction()
+
+# Generates a spdx id for a target and saves it its properties.
+function(_qt_internal_sbom_record_target_spdx_id target)
+ set(opt_args "")
+ set(single_args
+ PACKAGE_NAME
+ TYPE
+ OUT_VAR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_sbom_get_spdx_id_for_target("${target}" spdx_id)
+
+ if(spdx_id)
+ # Return early if the target was already recorded and has a spdx id.
+ if(arg_OUT_VAR)
+ set(${arg_OUT_VAR} "${spdx_id}" PARENT_SCOPE)
+ endif()
+ return()
+ endif()
+
+ if(arg_PACKAGE_NAME)
+ set(package_name_for_spdx_id "${arg_PACKAGE_NAME}")
+ else()
+ set(package_name_for_spdx_id "${target}")
+ endif()
+
+ _qt_internal_sbom_generate_target_package_spdx_id(package_spdx_id
+ TYPE "${arg_TYPE}"
+ PACKAGE_NAME "${package_name_for_spdx_id}"
+ )
+ _qt_internal_sbom_save_spdx_id_for_target("${target}" "${package_spdx_id}")
+
+ _qt_internal_sbom_is_qt_entity_type("${arg_TYPE}" is_qt_entity_type)
+ _qt_internal_sbom_save_spdx_id_for_qt_entity_type(
+ "${target}" "${is_qt_entity_type}" "${package_spdx_id}")
+
+ if(arg_OUT_VAR)
+ set(${arg_OUT_VAR} "${package_spdx_id}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Generates a sanitized spdx id for a target (package) of a specific type.
+function(_qt_internal_sbom_generate_target_package_spdx_id out_var)
+ set(opt_args "")
+ set(single_args
+ PACKAGE_NAME
+ TYPE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_PACKAGE_NAME)
+ message(FATAL_ERROR "PACKAGE_NAME must be set")
+ endif()
+ if(NOT arg_TYPE)
+ message(FATAL_ERROR "TYPE must be set")
+ endif()
+
+ _qt_internal_sbom_get_root_project_name_for_spdx_id(repo_project_name_spdx_id)
+ _qt_internal_sbom_get_package_infix("${arg_TYPE}" package_infix)
+
+ _qt_internal_sbom_get_sanitized_spdx_id(spdx_id
+ "SPDXRef-${repo_project_name_spdx_id}-${package_infix}-${arg_PACKAGE_NAME}")
+
+ set(${out_var} "${spdx_id}" PARENT_SCOPE)
+endfunction()
+
+# Save a spdx id for a target inside its target properties.
+# Also saves the repo document namespace and relative installed repo document path.
+# These are used when generating a SPDX external document reference for exported targets, to
+# include them in relationships.
+function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
+ message(DEBUG "Saving spdx id for target ${target}: ${spdx_id}")
+
+ set(target_unaliased "${target}")
+ get_target_property(aliased_target "${target}" ALIASED_TARGET)
+ if(aliased_target)
+ set(target_unaliased ${aliased_target})
+ endif()
+
+ 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)
+
+ get_property(relative_installed_repo_document_path
+ GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path)
+
+ get_property(project_name_lowercase
+ GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase)
+
+ # And save them on the target.
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_spdx_repo_document_namespace
+ "${repo_document_namespace}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_spdx_relative_installed_repo_document_path
+ "${relative_installed_repo_document_path}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_spdx_repo_project_name_lowercase
+ "${project_name_lowercase}")
+
+ # Export the properties, so they can be queried by other repos.
+ # We also do it for versionless targets.
+ set(export_properties
+ _qt_sbom_spdx_id
+ _qt_sbom_spdx_repo_document_namespace
+ _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}")
+endfunction()
+
+# Returns whether the given sbom type is considered to be a Qt type like a module or a tool.
+function(_qt_internal_sbom_is_qt_entity_type sbom_type out_var)
+ set(qt_entity_types
+ QT_MODULE
+ QT_PLUGIN
+ QT_APP
+ QT_TOOL
+ )
+
+ set(is_qt_entity_type FALSE)
+ if(sbom_type IN_LIST qt_entity_types)
+ set(is_qt_entity_type TRUE)
+ endif()
+
+ set(${out_var} ${is_qt_entity_type} PARENT_SCOPE)
+endfunction()
+
+# Save a spdx id for all known related target names of a given Qt target.
+# Related being the namespaced and versionless variants of a Qt target.
+# All the related targets will contain the same spdx id.
+# So Core, CorePrivate, Qt6::Core, Qt6::CorePrivate, Qt::Core, Qt::CorePrivate will all be
+# referred to by the same spdx id.
+function(_qt_internal_sbom_save_spdx_id_for_qt_entity_type target is_qt_entity_type package_spdx_id)
+ # Assign the spdx id to all known related target names of given the given Qt target.
+ set(target_names "")
+
+ if(is_qt_entity_type)
+ set(namespaced_target "${QT_CMAKE_EXPORT_NAMESPACE}::${target}")
+ set(namespaced_private_target "${QT_CMAKE_EXPORT_NAMESPACE}::${target}Private")
+ set(versionless_target "Qt::${target}")
+ set(versionless_private_target "Qt::${target}Private")
+
+ list(APPEND target_names
+ namespaced_target
+ namespaced_private_target
+ versionless_target
+ versionless_private_target
+ )
+ endif()
+
+ foreach(target_name IN LISTS ${target_names})
+ if(TARGET "${target_name}")
+ _qt_internal_sbom_save_spdx_id_for_target("${target_name}" "${package_spdx_id}")
+ endif()
+ endforeach()
+endfunction()
+
+# Retrieves a saved spdx id from the target. Might be empty.
+function(_qt_internal_sbom_get_spdx_id_for_target target out_var)
+ get_target_property(spdx_id ${target} _qt_sbom_spdx_id)
+ set(${out_var} "${spdx_id}" PARENT_SCOPE)
+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}")
+ 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")
+ set(package_infix "qt-module")
+ elseif(type STREQUAL "QT_PLUGIN")
+ set(package_infix "qt-plugin")
+ elseif(type STREQUAL "QML_PLUGIN")
+ set(package_infix "qt-qml-plugin") # not used at the moment
+ elseif(type STREQUAL "QT_TOOL")
+ set(package_infix "qt-tool")
+ elseif(type STREQUAL "QT_APP")
+ set(package_infix "qt-app")
+ elseif(type STREQUAL "QT_THIRD_PARTY_MODULE")
+ set(package_infix "qt-bundled-3rdparty-module")
+ elseif(type STREQUAL "QT_THIRD_PARTY_SOURCES")
+ set(package_infix "qt-3rdparty-sources")
+ elseif(type STREQUAL "SYSTEM_LIBRARY")
+ set(package_infix "system-3rdparty")
+ elseif(type STREQUAL "EXECUTABLE")
+ set(package_infix "executable")
+ elseif(type STREQUAL "LIBRARY")
+ set(package_infix "library")
+ elseif(type STREQUAL "THIRD_PARTY_LIBRARY")
+ set(package_infix "3rdparty-library")
+ elseif(type STREQUAL "THIRD_PARTY_LIBRARY_WITH_FILES")
+ set(package_infix "3rdparty-library-with-files")
+ else()
+ message(DEBUG "No package infix due to unknown type: ${type}")
+ set(package_infix "")
+ endif()
+ set(${out_infix} "${package_infix}" PARENT_SCOPE)
+endfunction()
+
+# Returns a package purpose for a given target sbom type.
+function(_qt_internal_sbom_get_package_purpose type out_purpose)
+ if(type STREQUAL "QT_MODULE")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "QT_PLUGIN")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "QML_PLUGIN")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "QT_TOOL")
+ set(package_purpose "APPLICATION")
+ elseif(type STREQUAL "QT_APP")
+ set(package_purpose "APPLICATION")
+ elseif(type STREQUAL "QT_THIRD_PARTY_MODULE")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "QT_THIRD_PARTY_SOURCES")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "SYSTEM_LIBRARY")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "EXECUTABLE")
+ set(package_purpose "APPLICATION")
+ elseif(type STREQUAL "LIBRARY")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "THIRD_PARTY_LIBRARY")
+ set(package_purpose "LIBRARY")
+ elseif(type STREQUAL "THIRD_PARTY_LIBRARY_WITH_FILES")
+ set(package_purpose "LIBRARY")
+ else()
+ set(package_purpose "OTHER")
+ endif()
+ set(${out_purpose} "${package_purpose}" PARENT_SCOPE)
+endfunction()
+
+# Get the default qt spdx license expression.
+function(_qt_internal_sbom_get_default_qt_license_id out_var)
+ set(${out_var}
+ "LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"
+ PARENT_SCOPE)
+endfunction()
+
+# Get the default qt copyright.
+function(_qt_internal_sbom_get_default_qt_copyright_header out_var)
+ set(${out_var}
+ "Copyright (C) 2024 The Qt Company Ltd."
+ PARENT_SCOPE)
+endfunction()
+
+# Get the default qt package version.
+function(_qt_internal_sbom_get_default_qt_package_version out_var)
+ set(${out_var} "${QT_REPO_MODULE_VERSION}" PARENT_SCOPE)
+endfunction()
+
+# Get the default qt supplier.
+function(_qt_internal_sbom_get_default_supplier out_var)
+ set(${out_var} "TheQtCompany" PARENT_SCOPE)
+endfunction()
+
+# Get the default qt supplier url.
+function(_qt_internal_sbom_get_default_supplier_url out_var)
+ set(${out_var} "https://qt.io" PARENT_SCOPE)
+endfunction()
+
+# Get the default qt download location.
+# If git info is available, includes the hash.
+function(_qt_internal_sbom_get_qt_repo_source_download_location out_var)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(download_location "git://code.qt.io/qt/${repo_project_name_lowercase}.git")
+ if(QT_SBOM_GIT_HASH)
+ string(APPEND download_location "@${QT_SBOM_GIT_HASH}")
+ endif()
+ set(${out_var} "${download_location}" PARENT_SCOPE)
+endfunction()
+
+# Computes a security CPE for a given set of attributes.
+#
+# When a part is not specified, a wildcard is added.
+#
+# References:
+# https://spdx.github.io/spdx-spec/v2.3/external-repository-identifiers/#f22-cpe23type
+# https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf
+# https://nvd.nist.gov/products/cpe
+#
+# Each attribute means:
+# 1. part
+# 2. vendor
+# 3. product
+# 4. version
+# 5. update
+# 6. edition
+# 7. language
+# 8. sw_edition
+# 9. target_sw
+# 10. target_hw
+# 11. other
+function(_qt_internal_sbom_compute_security_cpe out_cpe)
+ set(opt_args "")
+ set(single_args
+ PART
+ VENDOR
+ PRODUCT
+ VERSION
+ UPDATE
+ EDITION
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(cpe_template "cpe:2.3:PART:VENDOR:PRODUCT:VERSION:UPDATE:EDITION:*:*:*:*:*")
+
+ set(cpe "${cpe_template}")
+ foreach(attribute_name IN LISTS single_args)
+ if(arg_${attribute_name})
+ set(${attribute_name}_value "${arg_${attribute_name}}")
+ else()
+ if(attribute_name STREQUAL "PART")
+ set(${attribute_name}_value "a")
+ else()
+ set(${attribute_name}_value "*")
+ endif()
+ endif()
+ string(REPLACE "${attribute_name}" "${${attribute_name}_value}" cpe "${cpe}")
+ endforeach()
+
+ set(${out_cpe} "${cpe}" PARENT_SCOPE)
+endfunction()
+
+# Computes the default security CPE for the Qt framework.
+function(_qt_internal_sbom_get_cpe_qt out_var)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_compute_security_cpe(repo_cpe
+ VENDOR "qt"
+ PRODUCT "${repo_project_name_lowercase}"
+ VERSION "${QT_REPO_MODULE_VERSION}"
+ )
+ set(${out_var} "${repo_cpe}" PARENT_SCOPE)
+endfunction()
+
+# Computes the default security CPE for a given qt repository.
+function(_qt_internal_sbom_get_cpe_qt_repo out_var)
+ _qt_internal_sbom_compute_security_cpe(qt_cpe
+ VENDOR "qt"
+ PRODUCT "qt"
+ VERSION "${QT_REPO_MODULE_VERSION}"
+ )
+ set(${out_var} "${qt_cpe}" PARENT_SCOPE)
+endfunction()
+
+# Computes the list of security CPEs for Qt, including both the repo-specific one and generic one.
+function(_qt_internal_sbom_compute_security_cpe_for_qt out_cpe_list)
+ set(cpe_list "")
+
+ _qt_internal_sbom_get_cpe_qt(repo_cpe)
+ list(APPEND cpe_list "${repo_cpe}")
+
+ _qt_internal_sbom_get_cpe_qt_repo(qt_cpe)
+ list(APPEND cpe_list "${qt_cpe}")
+
+ set(${out_cpe_list} "${cpe_list}" PARENT_SCOPE)
+endfunction()
+
+# Collects app bundle related information and paths from an executable's target properties.
+# Output variables:
+# _name bundle base name, e.g. 'Linguist'.
+# _dir_name bundle dir name, e.g. 'Linguist.app'.
+# _contents_dir bundle contents dir, e.g. 'Linguist.app/Contents'
+# _contents_binary_dir bundle contents dir, e.g. 'Linguist.app/Contents/MacOS'
+function(_qt_internal_get_executable_bundle_info out_var target)
+ get_target_property(target_type ${target} TYPE)
+ if(NOT "${target_type}" STREQUAL "EXECUTABLE")
+ message(FATAL_ERROR "The target ${target} is not an executable")
+ endif()
+
+ get_target_property(output_name ${target} OUTPUT_NAME)
+ if(NOT output_name)
+ set(output_name "${target}")
+ endif()
+
+ set(${out_var}_name "${output_name}")
+ set(${out_var}_dir_name "${${out_var}_name}.app")
+ set(${out_var}_contents_dir "${${out_var}_dir_name}/Contents")
+ set(${out_var}_contents_binary_dir "${${out_var}_contents_dir}/MacOS")
+
+ set(${out_var}_name "${${out_var}_name}" PARENT_SCOPE)
+ set(${out_var}_dir_name "${${out_var}_dir_name}" PARENT_SCOPE)
+ set(${out_var}_contents_dir "${${out_var}_contents_dir}" PARENT_SCOPE)
+ set(${out_var}_contents_binary_dir "${${out_var}_contents_binary_dir}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to add binary file to the sbom, while handling multi-config and different
+# kind of paths.
+# In multi-config builds, we assume that the non-default config file will be optional, because it
+# might not be installed (the case for debug tools and apps in debug-and-release builds).
+function(_qt_internal_sbom_handle_multi_config_target_binary_file target)
+ set(opt_args "")
+ set(single_args
+ PATH_KIND
+ PATH_SUFFIX
+ )
+ set(multi_args
+ OPTIONS
+ )
+
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+ else()
+ set(configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ foreach(config IN LISTS configs)
+ _qt_internal_sbom_get_and_check_multi_config_aware_single_arg_option(
+ arg "${arg_PATH_KIND}" "${config}" resolved_path)
+ _qt_internal_sbom_get_target_file_is_optional_in_multi_config("${config}" is_optional)
+ _qt_internal_path_join(file_path "${resolved_path}" "${arg_PATH_SUFFIX}")
+ _qt_internal_sbom_add_binary_file(
+ "${target}"
+ "${file_path}"
+ ${arg_OPTIONS}
+ ${is_optional}
+ CONFIG ${config}
+ )
+ endforeach()
+endfunction()
+
+# Helper to retrieve a list of multi-config aware option names that can be parsed by the binary
+# file handling function.
+# For example in single config we need to parse RUNTIME_PATH, in multi-config we need to parse
+# RUNTIME_PATH_DEBUG and RUNTIME_PATH_RELEASE.
+#
+# Result is cached in a global property.
+function(_qt_internal_sbom_get_multi_config_single_args out_var)
+ get_cmake_property(single_args
+ _qt_internal_sbom_multi_config_single_args)
+
+ if(single_args)
+ set(${out_var} ${single_args} PARENT_SCOPE)
+ return()
+ endif()
+
+ set(single_args "")
+
+ set(single_args_to_process
+ INSTALL_PATH
+ RUNTIME_PATH
+ LIBRARY_PATH
+ ARCHIVE_PATH
+ FRAMEWORK_PATH
+ )
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+ foreach(config IN LISTS configs)
+ string(TOUPPER ${config} config_upper)
+ foreach(single_arg IN LISTS single_args_to_process)
+ list(APPEND single_args "${single_arg}_${config_upper}")
+ endforeach()
+ endforeach()
+ else()
+ list(APPEND single_args "${single_args_to_process}")
+ endif()
+
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_multi_config_single_args "${single_args}")
+ set(${out_var} ${single_args} PARENT_SCOPE)
+endfunction()
+
+# Helper to apped a an option and a value to a list of options, while being multi-config aware.
+# It appends e.g. either RUNTIME_PATH foo or RUNTIME_PATH_DEBUG foo to the out_var_args variable.
+function(_qt_internal_sbom_append_multi_config_aware_single_arg_option
+ arg_name arg_value config out_var_args)
+ set(values "${${out_var_args}}")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ string(TOUPPER ${config} config_upper)
+ list(APPEND values "${arg_name}_${config_upper}" "${arg_value}")
+ else()
+ list(APPEND values "${arg_name}" "${arg_value}")
+ endif()
+
+ set(${out_var_args} "${values}" PARENT_SCOPE)
+endfunction()
+
+# Helper to check whether a given option was set in the outer scope, while being multi-config
+# aware.
+# It checks e.g. if either arg_RUNTIME_PATH or arg_RUNTIME_PATH_DEBUG is set in the outer scope.
+function(_qt_internal_sbom_get_and_check_multi_config_aware_single_arg_option
+ arg_prefix arg_name config out_var)
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+
+ if(is_multi_config)
+ string(TOUPPER ${config} config_upper)
+ set(outer_scope_var_name "${arg_prefix}_${arg_name}_${config_upper}")
+ set(option_name "${arg_name}_${config_upper}")
+ else()
+ set(outer_scope_var_name "${arg_prefix}_${arg_name}")
+ set(option_name "${arg_name}")
+ endif()
+
+ if(NOT DEFINED ${outer_scope_var_name})
+ message(FATAL_ERROR "Missing ${option_name}")
+ endif()
+
+ set(${out_var} "${${outer_scope_var_name}}" PARENT_SCOPE)
+endfunction()
+
+# Checks if given config is not the first config in a multi-config build, and thus file installation
+# for that config should be optional.
+function(_qt_internal_sbom_is_config_optional_in_multi_config config out_var)
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+
+ if(QT_MULTI_CONFIG_FIRST_CONFIG)
+ set(first_config_type "${QT_MULTI_CONFIG_FIRST_CONFIG}")
+ elseif(CMAKE_CONFIGURATION_TYPES)
+ list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type)
+ endif()
+
+ if(is_multi_config AND NOT (cmake_config STREQUAL first_config_type))
+ set(is_optional TRUE)
+ else()
+ set(is_optional FALSE)
+ endif()
+
+ set(${out_var} "${is_optional}" PARENT_SCOPE)
+endfunction()
+
+# Checks if given config is not the first config in a multi-config build, and thus file installation
+# for that config should be optional, sets the actual option name.
+function(_qt_internal_sbom_get_target_file_is_optional_in_multi_config config out_var)
+ _qt_internal_sbom_is_config_optional_in_multi_config("${config}" is_optional)
+
+ if(is_optional)
+ set(option "OPTIONAL")
+ else()
+ set(option "")
+ endif()
+
+ set(${out_var} "${option}" PARENT_SCOPE)
+endfunction()
+
+# Joins two license IDs with the given ${op}, avoiding parenthesis when possible.
+function(_qt_internal_sbom_join_two_license_ids_with_op left_id op right_id out_var)
+ if(NOT left_id)
+ set(${out_var} "${right_id}" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(NOT right_id)
+ set(${out_var} "${left_id}" PARENT_SCOPE)
+ return()
+ endif()
+
+ set(value "(${left_id}) ${op} (${right_id})")
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
diff --git a/cmake/QtSbomHelpers.cmake b/cmake/QtSbomHelpers.cmake
new file mode 100644
index 00000000000..340354a759b
--- /dev/null
+++ b/cmake/QtSbomHelpers.cmake
@@ -0,0 +1,24 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# For now these are simple internal forwarding wrappers for the public counterparts, which are
+# meant to be used in qt repo CMakeLists.txt files.
+function(qt_internal_add_sbom)
+ _qt_internal_add_sbom(${ARGN})
+endfunction()
+
+function(qt_internal_extend_sbom)
+ _qt_internal_extend_sbom(${ARGN})
+endfunction()
+
+function(qt_internal_sbom_add_license)
+ _qt_internal_sbom_add_license(${ARGN})
+endfunction()
+
+function(qt_internal_extend_sbom_dependencies)
+ _qt_internal_extend_sbom_dependencies(${ARGN})
+endfunction()
+
+function(qt_find_package_extend_sbom)
+ _qt_find_package_extend_sbom(${ARGN})
+endfunction()
diff --git a/cmake/QtScopeFinalizerHelpers.cmake b/cmake/QtScopeFinalizerHelpers.cmake
index 9e13bec26d5..fbce324db6a 100644
--- a/cmake/QtScopeFinalizerHelpers.cmake
+++ b/cmake/QtScopeFinalizerHelpers.cmake
@@ -77,9 +77,17 @@ function(qt_watch_current_list_dir variable access value current_list_file stack
qt_finalize_plugin(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
elseif(func STREQUAL "qt_internal_finalize_app")
qt_internal_finalize_app(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
+ elseif(func STREQUAL "qt_internal_finalize_tool")
+ qt_internal_finalize_tool(${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
+ elseif(func STREQUAL "qt_internal_finalize_3rdparty_library")
+ qt_internal_finalize_3rdparty_library(
+ ${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
elseif(func STREQUAL "qt_internal_export_additional_targets_file_finalizer")
qt_internal_export_additional_targets_file_finalizer(
${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
+ elseif(func STREQUAL "_qt_internal_finalize_sbom")
+ _qt_internal_finalize_sbom(
+ ${a1} ${a2} ${a3} ${a4} ${a5} ${a6} ${a7} ${a8} ${a9})
else()
message(FATAL_ERROR "qt_watch_current_list_dir doesn't know about ${func}. Consider adding it.")
endif()
diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake
index d361a8096d1..b90b95870d9 100644
--- a/cmake/QtTargetHelpers.cmake
+++ b/cmake/QtTargetHelpers.cmake
@@ -45,6 +45,7 @@ function(qt_internal_extend_target target)
set(single_args
PRECOMPILED_HEADER
EXTRA_LINKER_SCRIPT_CONTENT
+ ATTRIBUTION_ENTRY_INDEX
)
set(multi_args
${__default_public_args}
@@ -54,6 +55,9 @@ function(qt_internal_extend_target target)
CONDITION_INDEPENDENT_SOURCES
COMPILE_FLAGS
EXTRA_LINKER_SCRIPT_EXPORTS
+ SBOM_DEPENDENCIES
+ ATTRIBUTION_FILE_PATHS
+ ATTRIBUTION_FILE_DIR_PATHS
)
cmake_parse_arguments(PARSE_ARGV 1 arg
@@ -210,6 +214,42 @@ function(qt_internal_extend_target target)
endif()
endforeach()
+ if(arg_LIBRARIES)
+ _qt_internal_append_to_target_property_without_duplicates(${target}
+ _qt_extend_target_libraries "${arg_LIBRARIES}"
+ )
+ endif()
+
+ if(arg_PUBLIC_LIBRARIES)
+ _qt_internal_append_to_target_property_without_duplicates(${target}
+ _qt_extend_target_public_libraries "${arg_PUBLIC_LIBRARIES}"
+ )
+ endif()
+
+ if(arg_SBOM_DEPENDENCIES)
+ _qt_internal_extend_sbom_dependencies(${target}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
+ )
+ endif()
+
+ if(NOT "${arg_ATTRIBUTION_ENTRY_INDEX}" STREQUAL "")
+ _qt_internal_extend_sbom(${target}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ )
+ endif()
+
+ if(arg_ATTRIBUTION_FILE_PATHS)
+ _qt_internal_extend_sbom(${target}
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ )
+ endif()
+
+ if(arg_ATTRIBUTION_FILE_DIR_PATHS)
+ _qt_internal_extend_sbom(${target}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ )
+ endif()
+
set(target_private "${target}Private")
get_target_property(is_internal_module ${target} _qt_is_internal_module)
# Internal modules don't have Private targets but we still need to
@@ -298,7 +338,7 @@ endfunction()
function(qt_get_install_target_default_args)
cmake_parse_arguments(PARSE_ARGV 0 arg
""
- "OUT_VAR;CMAKE_CONFIG;RUNTIME;LIBRARY;ARCHIVE;INCLUDES;BUNDLE"
+ "OUT_VAR;OUT_VAR_RUNTIME;CMAKE_CONFIG;RUNTIME;LIBRARY;ARCHIVE;INCLUDES;BUNDLE"
"ALL_CMAKE_CONFIGS")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -348,6 +388,13 @@ function(qt_get_install_target_default_args)
BUNDLE DESTINATION "${bundle}${suffix}"
INCLUDES DESTINATION "${includes}${suffix}")
set(${arg_OUT_VAR} "${args}" PARENT_SCOPE)
+
+ if(arg_OUT_VAR_RUNTIME)
+ set(args
+ "${runtime}${suffix}"
+ )
+ set(${arg_OUT_VAR_RUNTIME} "${args}" PARENT_SCOPE)
+ endif()
endfunction()
macro(qt_internal_setup_default_target_function_options)
@@ -391,6 +438,15 @@ macro(qt_internal_setup_default_target_function_options)
TARGET_COPYRIGHT
)
+ set(__qt_internal_sbom_single_args
+ ATTRIBUTION_ENTRY_INDEX
+ )
+ set(__qt_internal_sbom_multi_args
+ SBOM_DEPENDENCIES
+ ATTRIBUTION_FILE_PATHS
+ ATTRIBUTION_FILE_DIR_PATHS
+ )
+
# Collection of arguments so they can be shared across qt_internal_add_executable
# and qt_internal_add_test_helper.
set(__qt_internal_add_executable_optional_args
@@ -408,10 +464,12 @@ macro(qt_internal_setup_default_target_function_options)
INSTALL_DIRECTORY
VERSION
${__default_target_info_args}
+ ${__qt_internal_sbom_single_args}
)
set(__qt_internal_add_executable_multi_args
${__default_private_args}
${__default_public_args}
+ ${__qt_internal_sbom_multi_args}
)
endmacro()
diff --git a/cmake/QtToolHelpers.cmake b/cmake/QtToolHelpers.cmake
index 0aef6a43a3d..dd865c7d9c3 100644
--- a/cmake/QtToolHelpers.cmake
+++ b/cmake/QtToolHelpers.cmake
@@ -56,12 +56,16 @@ function(qt_internal_add_tool target_name)
INSTALL_DIR
CORE_LIBRARY
TRY_RUN_FLAGS
- ${__default_target_info_args})
+ ${__default_target_info_args}
+ ${__qt_internal_sbom_single_args}
+ )
set(multi_value_keywords
EXTRA_CMAKE_FILES
EXTRA_CMAKE_INCLUDES
PUBLIC_LIBRARIES
- ${__default_private_args})
+ ${__default_private_args}
+ ${__qt_internal_sbom_multi_args}
+ )
cmake_parse_arguments(PARSE_ARGV 1 arg
"${option_keywords}"
@@ -128,6 +132,10 @@ function(qt_internal_add_tool target_name)
LINK_OPTIONS ${arg_LINK_OPTIONS}
MOC_OPTIONS ${arg_MOC_OPTIONS}
DISABLE_AUTOGEN_TOOLS ${disable_autogen_tools}
+ ATTRIBUTION_ENTRY_INDEX "${arg_ATTRIBUTION_ENTRY_INDEX}"
+ ATTRIBUTION_FILE_PATHS ${arg_ATTRIBUTION_FILE_PATHS}
+ ATTRIBUTION_FILE_DIR_PATHS ${arg_ATTRIBUTION_FILE_DIR_PATHS}
+ SBOM_DEPENDENCIES ${arg_SBOM_DEPENDENCIES}
TARGET_VERSION ${arg_TARGET_VERSION}
TARGET_PRODUCT ${arg_TARGET_PRODUCT}
TARGET_DESCRIPTION ${arg_TARGET_DESCRIPTION}
@@ -186,8 +194,21 @@ function(qt_internal_add_tool target_name)
set_property(GLOBAL APPEND PROPERTY QT_USER_FACING_TOOL_TARGETS ${target_name})
endif()
+ if(QT_GENERATE_SBOM)
+ set(sbom_args "")
+ list(APPEND sbom_args TYPE QT_TOOL)
+ endif()
if(NOT arg_NO_INSTALL AND arg_TOOLS_TARGET)
+ set(will_install TRUE)
+ else()
+ set(will_install FALSE)
+ if(QT_GENERATE_SBOM)
+ list(APPEND sbom_args NO_INSTALL)
+ endif()
+ endif()
+
+ if(will_install)
# Assign a tool to an export set, and mark the module to which the tool belongs.
qt_internal_append_known_modules_with_tools("${arg_TOOLS_TARGET}")
@@ -202,6 +223,7 @@ function(qt_internal_add_tool target_name)
foreach(cmake_config ${cmake_configs})
qt_get_install_target_default_args(
OUT_VAR install_targets_default_args
+ OUT_VAR_RUNTIME runtime_install_destination
RUNTIME "${install_dir}"
CMAKE_CONFIG "${cmake_config}"
ALL_CMAKE_CONFIGS ${cmake_configs})
@@ -214,6 +236,15 @@ function(qt_internal_add_tool target_name)
unset(install_optional_arg)
endif()
+ if(QT_GENERATE_SBOM)
+ _qt_internal_sbom_append_multi_config_aware_single_arg_option(
+ RUNTIME_PATH
+ "${runtime_install_destination}"
+ "${cmake_config}"
+ sbom_args
+ )
+ endif()
+
qt_install(TARGETS "${target_name}"
${install_initial_call_args}
${install_optional_arg}
@@ -240,6 +271,16 @@ function(qt_internal_add_tool target_name)
qt_enable_separate_debug_info(${target_name} "${install_dir}" QT_EXECUTABLE)
qt_internal_install_pdb_files(${target_name} "${install_dir}")
+
+ if(QT_GENERATE_SBOM)
+ _qt_internal_extend_sbom(${target_name} ${sbom_args})
+ endif()
+
+ qt_add_list_file_finalizer(qt_internal_finalize_tool ${target_name})
+endfunction()
+
+function(qt_internal_finalize_tool target)
+ _qt_internal_finalize_sbom(${target})
endfunction()
function(_qt_internal_add_try_run_post_build target try_run_flags)
diff --git a/cmake/configure-cmake-mapping.md b/cmake/configure-cmake-mapping.md
index 6a184f61192..2a265198ba7 100644
--- a/cmake/configure-cmake-mapping.md
+++ b/cmake/configure-cmake-mapping.md
@@ -45,6 +45,7 @@ The following table describes the mapping of configure options to CMake argument
| -device-option | -DQT_QMAKE_DEVICE_OPTIONS=key1=value1;key2=value2 | Only used for generation qmake-compatibility files. |
| | | The device options are written into mkspecs/qdevice.pri. |
| -appstore-compliant | -DFEATURE_appstore_compliant=ON | |
+| -sbom | -DQT_GENERATE_SBOM=ON | Enables generation and installation of an SBOM |
| -qtinlinenamespace | -DQT_INLINE_NAMESPACE=ON | Make the namespace specified by -qtnamespace an inline one. |
| -qtnamespace | -DQT_NAMESPACE= | |
| -qtlibinfix | -DQT_LIBINFIX= | |
diff --git a/config_help.txt b/config_help.txt
index 263195d7fdd..276cb550231 100644
--- a/config_help.txt
+++ b/config_help.txt
@@ -25,6 +25,8 @@ except -sysconfdir should be located under -prefix:
-libexecdir ..... Helper programs [ARCHDATADIR/bin on Windows,
ARCHDATADIR/libexec otherwise]
-qmldir ......... QML imports [ARCHDATADIR/qml]
+ -sbomdir ....... Software Bill of Materials (SBOM)
+ installation directory [ARCHDATADIR/sbom]
-datadir ........ Arch-independent data [PREFIX]
-docdir ......... Documentation [DATADIR/doc]
-translationdir . Translations [DATADIR/translations]
@@ -99,6 +101,9 @@ Build options:
through an app store by default, in particular Android,
iOS, tvOS, and watchOS. [auto]
+ -sbom ................ Enable Software Bill of Materials (SBOM) generation
+ [no]
+
-qt-host-path . Specify path to a Qt host build for cross-compiling.
-qtnamespace .. Wrap all Qt library code in 'namespace {...}'.
-qtinlinenamespace ... Make -qtnamespace an inline namespace