From 43e166bd084f4bae6fd348579ed5c27ebf8320e2 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Thu, 14 Nov 2024 16:43:13 +0100 Subject: [PATCH] CMake: Support manual multiple projects within-a-repo SBOM generation Certain repositories like qtwebengine contain multiple projects from the perspective of online installer packaging. In this case the QtWebEngine and QtPdf projects are expected to have separate SBOM documents. Introduce a new QT_SKIP_SBOM_AUTO_PROJECT variable that can be set before qt_build_repo to disable the auto-generation of an SBOM document for the current repo project. Introduce two new internal functions qt_internal_sbom_begin/end_qt_repo_project to allow to manually start and end the SBOM generation for a project within a repo. Because the intermediate file names that assemble the SBOM use the project name as a key, and the project name would be the same for qtwebengine, allow differentiating between the current project name and the real qt repo project name. The current project name is used for the file names, whereas the real qt repo project name is used to extract the dependencies on other repos, to ensure correct dependency build rules. As a drive-by, improve the document dir path search list when an SBOM document can't be found. Pick-to: 6.8 Task-number: QTBUG-128893 Task-number: QTBUG-122899 Change-Id: I61b68098242e7c49b98420265c29af78303c3233 Reviewed-by: Joerg Bornemann --- cmake/QtBuildRepoHelpers.cmake | 7 +- cmake/QtPublicSbomGenerationHelpers.cmake | 24 +++++- cmake/QtPublicSbomHelpers.cmake | 100 +++++++++++++++++++++- cmake/QtSbomHelpers.cmake | 9 ++ 4 files changed, 130 insertions(+), 10 deletions(-) diff --git a/cmake/QtBuildRepoHelpers.cmake b/cmake/QtBuildRepoHelpers.cmake index e21b7c24d13..fc8945d4c9c 100644 --- a/cmake/QtBuildRepoHelpers.cmake +++ b/cmake/QtBuildRepoHelpers.cmake @@ -352,10 +352,7 @@ macro(qt_build_repo_begin) set_property(GLOBAL PROPERTY _qt_synced_modules ${QT_INTERNAL_SYNCED_MODULES}) endif() - _qt_internal_sbom_begin_project( - INSTALL_SBOM_DIR "${INSTALL_SBOMDIR}" - QT_CPE - ) + _qt_internal_sbom_auto_begin_qt_repo_project() endmacro() # Runs delayed actions on some of the Qt targets. @@ -419,7 +416,7 @@ macro(qt_build_repo_end) set(QT_INTERNAL_FRESH_REQUESTED "FALSE" CACHE INTERNAL "") endif() - _qt_internal_sbom_end_project() + _qt_internal_sbom_auto_end_qt_repo_project() if(NOT QT_SUPERBUILD) qt_internal_qt_configure_end() diff --git a/cmake/QtPublicSbomGenerationHelpers.cmake b/cmake/QtPublicSbomGenerationHelpers.cmake index de632cdc0fe..da59482a72f 100644 --- a/cmake/QtPublicSbomGenerationHelpers.cmake +++ b/cmake/QtPublicSbomGenerationHelpers.cmake @@ -18,9 +18,25 @@ function(qt_internal_sbom_set_default_option_value_and_error_if_empty option_nam endif() endfunction() +# Helper that returns the relative sbom build dir. +# To accommodate multiple projects within a qt repo (like qtwebengine), we need to choose separate +# build dirs for each project. +function(_qt_internal_get_current_project_sbom_relative_dir out_var) + _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase) + _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase) + + if(repo_project_name_lowercase STREQUAL real_qt_repo_project_name_lowercase) + set(sbom_dir "qt_sbom") + else() + set(sbom_dir "qt_sbom/${repo_project_name_lowercase}") + endif() + set(${out_var} "${sbom_dir}" 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") + _qt_internal_get_current_project_sbom_relative_dir(relative_dir) + set(sbom_dir "${PROJECT_BINARY_DIR}/${relative_dir}") set(${out_var} "${sbom_dir}" PARENT_SCOPE) endfunction() @@ -414,6 +430,7 @@ function(_qt_internal_sbom_end_project_generate) endif() _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase) + _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase) # Create a build target to create a build-time sbom (no verification codes or sha1s). set(repo_sbom_target "sbom_${repo_project_name_lowercase}") @@ -427,7 +444,7 @@ function(_qt_internal_sbom_end_project_generate) USES_TERMINAL # To avoid running two configs of the command in parallel ) - get_cmake_property(qt_repo_deps _qt_repo_deps_${repo_project_name_lowercase}) + get_cmake_property(qt_repo_deps _qt_repo_deps_${real_qt_repo_project_name_lowercase}) if(qt_repo_deps) foreach(repo_dep IN LISTS qt_repo_deps) set(repo_dep_sbom "sbom_${repo_dep}") @@ -767,6 +784,7 @@ function(_qt_internal_sbom_generate_add_external_reference) set(content " set(relative_file_name \"${arg_FILENAME}\") set(document_dir_paths ${install_prefixes}) + list(JOIN document_dir_paths \"\\n\" document_dir_paths_per_line) foreach(document_dir_path IN LISTS document_dir_paths) set(document_file_path \"\${document_dir_path}/\${relative_file_name}\") if(EXISTS \"\${document_file_path}\") @@ -775,7 +793,7 @@ function(_qt_internal_sbom_generate_add_external_reference) 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} \" + \" in any of the document dir paths: \${document_dir_paths_per_line} \" ) endif() file(SHA1 \"\${document_file_path}\" ext_sha1) diff --git a/cmake/QtPublicSbomHelpers.cmake b/cmake/QtPublicSbomHelpers.cmake index f1fce0fffa0..9e4a855c062 100644 --- a/cmake/QtPublicSbomHelpers.cmake +++ b/cmake/QtPublicSbomHelpers.cmake @@ -38,6 +38,7 @@ function(_qt_internal_sbom_begin_project) DOCUMENT_NAMESPACE VERSION SBOM_PROJECT_NAME + QT_REPO_PROJECT_NAME CPE ) set(multi_args @@ -69,6 +70,13 @@ function(_qt_internal_sbom_begin_project) else() _qt_internal_sbom_set_root_project_name("${PROJECT_NAME}") endif() + + if(arg_QT_REPO_PROJECT_NAME) + _qt_internal_sbom_set_qt_repo_project_name("${arg_QT_REPO_PROJECT_NAME}") + else() + _qt_internal_sbom_set_qt_repo_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) @@ -376,6 +384,75 @@ function(_qt_internal_sbom_end_project) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${attribution_files}") endfunction() +# Automatically begins sbom generation for a qt git repo unless QT_SKIP_SBOM_AUTO_PROJECT is TRUE. +function(_qt_internal_sbom_auto_begin_qt_repo_project) + # Allow skipping auto generation of sbom project, in case it needs to be manually adjusted with + # extra parameters. + if(QT_SKIP_SBOM_AUTO_PROJECT) + return() + endif() + + _qt_internal_sbom_begin_qt_repo_project() +endfunction() + +# Sets up sbom generation for a qt git repo or qt-git-repo-sub-project (e.g. qtpdf in qtwebengine). +# +# In the case of a qt-git-repo-sub-project, the function expects the following options: +# - SBOM_PROJECT_NAME (e.g. QtPdf) +# - QT_REPO_PROJECT_NAME (e.g. QtWebEngine) +# +# Expects the following variables to always be set before the function call: +# - QT_STAGING_PREFIX +# - INSTALL_SBOMDIR +function(_qt_internal_sbom_begin_qt_repo_project) + set(opt_args "") + set(single_args + SBOM_PROJECT_NAME + QT_REPO_PROJECT_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(sbom_project_args "") + + _qt_internal_forward_function_args( + FORWARD_APPEND + FORWARD_PREFIX arg + FORWARD_OUT_VAR sbom_project_args + FORWARD_OPTIONS + ${opt_args} + FORWARD_SINGLE + ${single_args} + FORWARD_MULTI + ${multi_args} + ) + + _qt_internal_sbom_begin_project( + INSTALL_SBOM_DIR "${INSTALL_SBOMDIR}" + QT_CPE + ${sbom_project_args} + ) +endfunction() + +# Automatically ends sbom generation for a qt git repo unless QT_SKIP_SBOM_AUTO_PROJECT is TRUE. +function(_qt_internal_sbom_auto_end_qt_repo_project) + # Allow skipping auto generation of sbom project, in case it needs to be manually adjusted with + # extra parameters. + if(QT_SKIP_SBOM_AUTO_PROJECT) + return() + endif() + + _qt_internal_sbom_end_qt_repo_project() +endfunction() + +# Endssbom generation for a qt git repo or qt-git-repo-sub-project. + +function(_qt_internal_sbom_end_qt_repo_project) + _qt_internal_sbom_end_project() +endfunction() + # Helper to get purl parsing options. macro(_qt_internal_get_sbom_purl_parsing_options opt_args single_args multi_args) set(${opt_args} @@ -2608,11 +2685,17 @@ macro(_qt_internal_sbom_get_attribution_key json_key out_var out_prefix) endif() endmacro() -# Set sbom project name for the root project. +# Sets the 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() +# Sets the real qt repo project name for a given project (e.g. set QtWebEngine for project QtPdf). +# This is needed to be able to extract the qt repo dependencies in a top-level build. +function(_qt_internal_sbom_set_qt_repo_project_name project_name) + set_property(GLOBAL PROPERTY _qt_internal_sbom_qt_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) @@ -2620,7 +2703,7 @@ function(_qt_internal_sbom_get_root_project_name_for_spdx_id out_var) set(${out_var} "${sbom_repo_project_name}" PARENT_SCOPE) endfunction() -# Just a lower case sbom project name. +# Returns the 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) @@ -2632,6 +2715,19 @@ function(_qt_internal_sbom_get_root_project_name_lower_case out_var) set(${out_var} "${repo_project_name_lowercase}" PARENT_SCOPE) endfunction() +# Returns the lower case real qt repo project name (e.g. returns 'qtwebengine' when building the +# project qtpdf). +function(_qt_internal_sbom_get_qt_repo_project_name_lower_case out_var) + get_cmake_property(project_name _qt_internal_sbom_qt_repo_project_name) + + if(NOT project_name) + message(FATAL_ERROR "No real Qt repo 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) diff --git a/cmake/QtSbomHelpers.cmake b/cmake/QtSbomHelpers.cmake index 340354a759b..c46740b5acc 100644 --- a/cmake/QtSbomHelpers.cmake +++ b/cmake/QtSbomHelpers.cmake @@ -22,3 +22,12 @@ endfunction() function(qt_find_package_extend_sbom) _qt_find_package_extend_sbom(${ARGN}) endfunction() + +function(qt_internal_sbom_begin_qt_repo_project) + _qt_internal_sbom_begin_qt_repo_project(${ARGN}) +endfunction() + +function(qt_internal_sbom_end_qt_repo_project) + _qt_internal_sbom_end_qt_repo_project(${ARGN}) +endfunction() +