diff --git a/cmake/QtPublicSbomHelpers.cmake b/cmake/QtPublicSbomHelpers.cmake index 2d3d52a4cad..e3c7106d764 100644 --- a/cmake/QtPublicSbomHelpers.cmake +++ b/cmake/QtPublicSbomHelpers.cmake @@ -286,11 +286,12 @@ function(_qt_internal_sbom_end_project) set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_begin_called FALSE) endfunction() -# Helper to get purl related options. -macro(_qt_internal_get_sbom_purl_options opt_args single_args multi_args) +# Helper to get purl parsing options. +macro(_qt_internal_get_sbom_purl_parsing_options opt_args single_args multi_args) set(${opt_args} NO_PURL NO_DEFAULT_QT_PURL + PURL_USE_PACKAGE_VERSION ) set(${single_args} PURL_TYPE @@ -298,12 +299,49 @@ macro(_qt_internal_get_sbom_purl_options opt_args single_args multi_args) PURL_NAME PURL_VERSION PURL_SUBPATH + PURL_VCS_URL ) set(${multi_args} PURL_QUALIFIERS ) endmacro() +# Helper to get the purl variant option names that should be recongized by sbom functions like +# _qt_internal_sbom_add_target. +macro(_qt_internal_get_sbom_purl_add_target_options opt_args single_args multi_args) + set(${opt_args} "") + set(${single_args} + PURL_QT_VALUE + PURL_3RDPARTY_UPSTREAM_VALUE + PURL_MIRROR_VALUE + ) + set(${multi_args} + PURL_QT_ARGS + PURL_3RDPARTY_UPSTREAM_ARGS + PURL_MIRROR_ARGS + ) +endmacro() + +# Helper to get purl options that should be forwarded from _qt_internal_sbom_add_target to +# _qt_internal_sbom_handle_purl_values. +macro(_qt_internal_get_sbom_purl_handling_options opt_args single_args multi_args) + set(${opt_args} + IS_QT_ENTITY_TYPE + ) + set(${single_args} + SUPPLIER + TYPE + VERSION + ) + set(${multi_args} "") + + _qt_internal_get_sbom_purl_add_target_options( + purl_add_target_opt_args purl_add_target_single_args purl_add_target_multi_args) + list(APPEND ${opt_args} ${purl_add_target_opt_args}) + list(APPEND ${single_args} ${purl_add_target_single_args}) + list(APPEND ${multi_args} ${purl_add_target_multi_args}) +endmacro() + # Helper to get the options that _qt_internal_sbom_add_target understands, but that are also # a safe subset for qt_internal_add_module, qt_internal_extend_target, etc to understand. macro(_qt_internal_get_sbom_add_target_common_options opt_args single_args multi_args) @@ -339,14 +377,11 @@ macro(_qt_internal_get_sbom_add_target_common_options opt_args single_args multi ATTRIBUTION_FILE_DIR_PATHS ) - _qt_internal_get_sbom_purl_options(purl_opt_args purl_single_args purl_multi_args) - list(APPEND ${opt_args} ${purl_opt_args}) - list(APPEND ${single_args} ${purl_single_args}) - list(APPEND ${multi_args} ${purl_multi_args}) - - unset(purl_opt_args) - unset(purl_single_args) - unset(purl_multi_args) + _qt_internal_get_sbom_purl_add_target_options( + purl_add_target_opt_args purl_add_target_single_args purl_add_target_multi_args) + list(APPEND ${opt_args} ${purl_add_target_opt_args}) + list(APPEND ${single_args} ${purl_add_target_single_args}) + list(APPEND ${multi_args} ${purl_add_target_multi_args}) endmacro() # Helper to get all known SBOM specific options, without the ones that qt_internal_add_module @@ -649,59 +684,33 @@ function(_qt_internal_sbom_add_target target) 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" - ) - set(is_qt_purl_entity_type TRUE) - else() - set(is_qt_purl_entity_type FALSE) + # Assemble arguments to forward to the function that handles purl options. + set(purl_args "") + _qt_internal_get_sbom_purl_add_target_options(purl_opt_args purl_single_args purl_multi_args) + _qt_internal_forward_function_args( + FORWARD_APPEND + FORWARD_PREFIX arg + FORWARD_OUT_VAR purl_args + FORWARD_OPTIONS + ${purl_opt_args} + FORWARD_SINGLE + ${purl_single_args} + TYPE + FORWARD_MULTI + ${purl_multi_args} + ) + + list(APPEND purl_args SUPPLIER "${supplier}") + list(APPEND purl_args VERSION "${package_version}") + if(is_qt_entity_type) + list(APPEND purl_args IS_QT_ENTITY_TYPE) endif() + list(APPEND purl_args OUT_VAR purl_package_options) - if(arg_PURL_TYPE - OR arg_PURL_NAMESPACE - OR arg_PURL_NAME - OR arg_PURL_VERSION - OR arg_PURL_QUALIFIERS - OR arg_PURL_SUBPATH - ) - set(purl_args_available TRUE) - endif() + _qt_internal_sbom_handle_purl_values(${target} ${purl_args}) - if((is_qt_purl_entity_type OR purl_args_available) - AND NOT arg_NO_PURL) - - _qt_internal_get_sbom_purl_options(purl_opt_args purl_single_args purl_multi_args) - set(purl_args "") - _qt_internal_forward_function_args( - FORWARD_APPEND - FORWARD_PREFIX arg - FORWARD_OUT_VAR purl_args - FORWARD_OPTIONS - ${purl_opt_args} - FORWARD_SINGLE - ${purl_single_args} - FORWARD_MULTI - ${purl_multi_args} - ) - - # Qt entity types get a default qt-specific purl. - if(is_qt_purl_entity_type AND NOT arg_NO_DEFAULT_QT_PURL) - _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase) - _qt_internal_sbom_get_qt_entity_purl(${target} - NAME "${repo_project_name_lowercase}-${target}" - SUPPLIER "${supplier}" - VERSION "${QT_SBOM_GIT_VERSION}" - ${purl_args} - OUT_VAR purl_args - ) - endif() - - _qt_internal_sbom_handle_purl(${target} - ${purl_args} - OUT_VAR package_manager_external_ref - ) - list(APPEND project_package_options ${package_manager_external_ref}) + if(purl_package_options) + list(APPEND project_package_options ${purl_package_options}) endif() if(arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE" @@ -2751,20 +2760,274 @@ function(_qt_internal_sbom_compute_security_cpe_for_qt out_cpe_list) set(${out_cpe_list} "${cpe_list}" PARENT_SCOPE) endfunction() -# Gets a list of arguments to pass to _qt_internal_sbom_handle_purl when handling a Qt entity type. -# The purl for Qt entity types have Qt-specific defaults, but can be overridden per purl component. -# The arguments are saved in OUT_VAR. -function(_qt_internal_sbom_get_qt_entity_purl target) +# Parse purl arguments for a specific purl variant, e.g. for parsing all values of arg_PURL_QT_ARGS. +# arguments_var_name is the variable name that contains the args. +macro(_qt_internal_sbom_parse_purl_variant_options prefix arguments_var_name) + _qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args) + + cmake_parse_arguments(arg "${purl_opt_args}" "${purl_single_args}" "${purl_multi_args}" + ${${arguments_var_name}}) + _qt_internal_validate_all_args_are_parsed(arg) +endmacro() + +# Returns a vcs url where for purls where qt entities of the current repo are hosted. +function(_qt_internal_sbom_get_qt_entity_vcs_url target) set(opt_args "") set(single_args - NAME - SUPPLIER + REPO_NAME VERSION 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_get_sbom_purl_options(purl_opt_args purl_single_args purl_multi_args) + if(NOT arg_REPO_NAME) + message(FATAL_ERROR "REPO_NAME must be set") + endif() + + if(NOT arg_OUT_VAR) + message(FATAL_ERROR "OUT_VAR must be set") + endif() + + set(version_part "") + if(arg_VERSION) + set(version_part "@${arg_VERSION}") + endif() + + set(vcs_url "https://code.qt.io/qt/${arg_REPO_NAME}.git${version_part}") + set(${arg_OUT_VAR} "${vcs_url}" PARENT_SCOPE) +endfunction() + +# Returns a relative path to the source where the target was created, to be embedded into a +# mirror purl as a subpath. +function(_qt_internal_sbom_get_qt_entity_repo_source_dir target) + set(opt_args "") + 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) + + if(NOT arg_OUT_VAR) + message(FATAL_ERROR "OUT_VAR must be set") + endif() + + get_target_property(repo_source_dir "${target}" SOURCE_DIR) + + # Get the path relative to the PROJECT_SOURCE_DIR + file(RELATIVE_PATH relative_repo_source_dir "${PROJECT_SOURCE_DIR}" "${repo_source_dir}") + + set(sub_path "${relative_repo_source_dir}") + set(${arg_OUT_VAR} "${sub_path}" PARENT_SCOPE) +endfunction() + +# Handles purl arguments specified to functions like qt_internal_add_sbom. +# Currently accepts arguments for 3 variants of purls, each of which will generate a separate purl. +# If no arguments are specified, for qt entity types, default values will be chosen. +# +# Purl variants: +# - PURL_QT_ARGS +# args to override Qt's generic purl for Qt modules or patched 3rd party libs +# defaults to something like pkg:generic/TheQtCompany/${repo_name}-${target}@SHA1 +# - PURL_MIRROR_ARGS +# args to override Qt's mirror purl, which is hosted on github +# defaults to something like pkg:github/qt/${repo_name}@SHA1 +# - PURL_3RDPARTY_UPSTREAM_ARGS +# args to specify a purl pointing to an upstream repo, usually to github or another forge +# no defaults, but could look like: pkg:github/harfbuzz/harfbuzz@v8.5.0 +# Example values for harfbuzz: +# PURL_3RDPARTY_UPSTREAM_ARGS +# PURL_TYPE "github" +# PURL_NAMESPACE "harfbuzz" +# PURL_NAME "harfbuzz" +# PURL_VERSION "v8.5.0" # tag +function(_qt_internal_sbom_handle_purl_values target) + _qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args) + list(APPEND single_args OUT_VAR) + + cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") + _qt_internal_validate_all_args_are_parsed(arg) + + if(NOT arg_OUT_VAR) + message(FATAL_ERROR "OUT_VAR must be set") + endif() + + # List of purl variants to process. + set(purl_variants "") + + set(third_party_types + QT_THIRD_PARTY_MODULE + QT_THIRD_PARTY_SOURCES + ) + + if(arg_IS_QT_ENTITY_TYPE) + # Qt entities have two purls by default, a QT generic one and a MIRROR hosted on github. + list(APPEND purl_variants MIRROR QT) + elseif(arg_TYPE IN_LIST third_party_types) + # Third party libraries vendored in Qt also have at least two purls, like regular Qt + # libraries, but might also have an upstream one. + + # The order in which the purls are generated matters for tools that consume the SBOM. Some + # tools can only handle one PURL per package, so the first one should be the important one. + # For now, I deem that the upstream one if present. Otherwise the github mirror. + if(arg_PURL_3RDPARTY_UPSTREAM_ARGS) + list(APPEND purl_variants 3RDPARTY_UPSTREAM) + endif() + + list(APPEND purl_variants MIRROR QT) + else() + # If handling another entity type, handle based on whether any of the purl arguments are + # set. + set(known_purl_variants QT MIRROR 3RDPARTY_UPSTREAM) + foreach(known_purl_variant IN_LIST known_purl_variants) + if(arg_PURL_${known_purl_variant}_ARGS) + list(APPEND purl_variants ${known_purl_variant}) + endif() + endforeach() + endif() + + if(arg_IS_QT_ENTITY_TYPE + OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE" + OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES" + ) + set(is_qt_purl_entity_type TRUE) + else() + set(is_qt_purl_entity_type FALSE) + endif() + + _qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args) + + set(project_package_options "") + + foreach(purl_variant IN LISTS purl_variants) + # Clear previous values. + foreach(option_name IN LISTS purl_opt_args purl_single_args purl_multi_args) + unset(arg_${option_name}) + endforeach() + + _qt_internal_sbom_parse_purl_variant_options(arg arg_PURL_${purl_variant}_ARGS) + + # Check if custom purl args were specified. + set(purl_args_available FALSE) + if(arg_PURL_${purl_variant}_ARGS) + set(purl_args_available TRUE) + endif() + + # We want to create a purl either if it's one of Qt's entities and one of it's default + # purl types, or if custom args were specified. + set(consider_purl_processing FALSE) + if((purl_args_available OR is_qt_purl_entity_type) AND NOT arg_NO_PURL) + set(consider_purl_processing TRUE) + endif() + + if(consider_purl_processing) + set(purl_args "") + + # Override the purl version with the package version. + if(arg_PURL_USE_PACKAGE_VERSION AND arg_VERSION) + set(arg_PURL_VERSION "${arg_VERSION}") + endif() + + # Append a vcs_url to the qualifiers if specified. + if(arg_PURL_VCS_URL) + list(APPEND arg_PURL_QUALIFIERS "vcs_url=${arg_PURL_VCS_URL}") + endif() + + _qt_internal_forward_function_args( + FORWARD_APPEND + FORWARD_PREFIX arg + FORWARD_OUT_VAR purl_args + FORWARD_OPTIONS + ${purl_opt_args} + FORWARD_SINGLE + ${purl_single_args} + FORWARD_MULTI + ${purl_multi_args} + ) + + # Qt entity types get special treatment purl. + if(is_qt_purl_entity_type AND NOT arg_NO_DEFAULT_QT_PURL AND + (purl_variant STREQUAL "QT" OR purl_variant STREQUAL "MIRROR")) + _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase) + + # Add a vcs_url to the generic QT variant. + if(purl_variant STREQUAL "QT") + _qt_internal_sbom_get_qt_entity_vcs_url(${target} + REPO_NAME "${repo_project_name_lowercase}" + VERSION "${QT_SBOM_GIT_HASH_SHORT}" # can be empty + OUT_VAR vcs_url) + list(APPEND purl_args PURL_QUALIFIERS "vcs_url=${vcs_url}") + endif() + + # Add the subdirectory path where the target was created as a custom qualifier. + _qt_internal_sbom_get_qt_entity_repo_source_dir(${target} OUT_VAR sub_path) + list(APPEND purl_args PURL_SUBPATH "${sub_path}") + + # Add the target name as a custom qualifer. + list(APPEND purl_args PURL_QUALIFIERS "library_name=${target}") + + # Get purl args the Qt entity type, taking into account defaults. + _qt_internal_sbom_get_qt_entity_purl_args(${target} + NAME "${repo_project_name_lowercase}-${target}" + REPO_NAME "${repo_project_name_lowercase}" + SUPPLIER "${arg_SUPPLIER}" + VERSION "${QT_SBOM_GIT_HASH_SHORT}" # can be empty + PURL_VARIANT "${purl_variant}" + ${purl_args} + OUT_VAR purl_args + ) + endif() + + _qt_internal_sbom_assemble_purl(${target} + ${purl_args} + OUT_VAR package_manager_external_ref + ) + list(APPEND project_package_options ${package_manager_external_ref}) + endif() + endforeach() + + set(direct_values + PURL_QT_VALUE + PURL_3RDPARTY_UPSTREAM_VALUE + PURL_MIRROR_VALUE + ) + + foreach(direct_value IN LISTS direct_values) + if(arg_${direct_value}) + _qt_internal_sbom_get_purl_value_extref( + VALUE "${arg_${direct_value}}" OUT_VAR package_manager_external_ref) + + # The order in which the purls are generated matters for tools that consume the SBOM. + # Some tools can only handle one PURL per package, so the first one should be the + # important one. + # For now, I deem that the directly specified one (probably via a qt_attribution.json) + # file is the important one. So we prepend it. + list(PREPEND project_package_options ${package_manager_external_ref}) + endif() + endforeach() + + set(${arg_OUT_VAR} "${project_package_options}" PARENT_SCOPE) +endfunction() + +# Gets a list of arguments to pass to _qt_internal_sbom_assemble_purl when handling a Qt entity +# type. The purl for Qt entity types have Qt-specific defaults, but can be overridden per purl +# component. +# The arguments are saved in OUT_VAR. +function(_qt_internal_sbom_get_qt_entity_purl_args target) + set(opt_args "") + set(single_args + NAME + REPO_NAME + SUPPLIER + VERSION + PURL_VARIANT + OUT_VAR + ) + set(multi_args "") + + _qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args) list(APPEND opt_args ${purl_opt_args}) list(APPEND single_args ${purl_single_args}) list(APPEND multi_args ${purl_multi_args}) @@ -2772,28 +3035,37 @@ function(_qt_internal_sbom_get_qt_entity_purl target) cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}") _qt_internal_validate_all_args_are_parsed(arg) + set(supported_purl_variants QT MIRROR) + if(NOT arg_PURL_VARIANT IN_LIST supported_purl_variants) + message(FATAL_ERROR "PURL_VARIANT unknown: ${arg_PURL_VARIANT}") + endif() + + if(arg_PURL_VARIANT STREQUAL "QT") + set(purl_type "generic") + set(purl_namespace "${arg_SUPPLIER}") + set(purl_name "${arg_NAME}") + set(purl_version "${arg_VERSION}") + elseif(arg_PURL_VARIANT STREQUAL "MIRROR") + set(purl_type "github") + set(purl_namespace "qt") + set(purl_name "${arg_REPO_NAME}") + set(purl_version "${arg_VERSION}") + endif() + if(arg_PURL_TYPE) set(purl_type "${arg_PURL_TYPE}") - else() - set(purl_type "generic") endif() if(arg_PURL_NAMESPACE) set(purl_namespace "${arg_PURL_NAMESPACE}") - else() - set(purl_namespace "${arg_SUPPLIER}") endif() if(arg_PURL_NAME) set(purl_name "${arg_PURL_NAME}") - else() - set(purl_name "${arg_NAME}") endif() if(arg_PURL_VERSION) set(purl_version "${arg_PURL_VERSION}") - else() - set(purl_version "${arg_VERSION}") endif() set(purl_args @@ -2814,17 +3086,24 @@ function(_qt_internal_sbom_get_qt_entity_purl target) set(${arg_OUT_VAR} "${purl_args}" PARENT_SCOPE) endfunction() -# Assembls an external reference purl identifier. +# Assembles an external reference purl identifier. # PURL_TYPE and PURL_NAME are required. # Stores the result in the OUT_VAR. -function(_qt_internal_sbom_handle_purl target) +# Accepted options: +# PURL_TYPE +# PURL_NAME +# PURL_NAMESPACE +# PURL_VERSION +# PURL_SUBPATH +# PURL_QUALIFIERS +function(_qt_internal_sbom_assemble_purl target) set(opt_args "") set(single_args OUT_VAR ) set(multi_args "") - _qt_internal_get_sbom_purl_options(purl_opt_args purl_single_args purl_multi_args) + _qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args) list(APPEND opt_args ${purl_opt_args}) list(APPEND single_args ${purl_single_args}) list(APPEND multi_args ${purl_multi_args}) @@ -2846,9 +3125,6 @@ function(_qt_internal_sbom_handle_purl target) message(FATAL_ERROR "OUT_VAR must be set") endif() - # SPDX SBOM External reference type. - set(ext_ref_prefix "PACKAGE-MANAGER purl") - # https://github.com/package-url/purl-spec # Spec is 'scheme:type/namespace/name@version?qualifiers#subpath' set(purl "${purl_scheme}:${arg_PURL_TYPE}") @@ -2874,8 +3150,33 @@ function(_qt_internal_sbom_handle_purl target) string(APPEND purl "#${arg_PURL_SUBPATH}") endif() - set(external_ref "${ext_ref_prefix} ${purl}") + _qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR result) + set(${arg_OUT_VAR} "${result}" PARENT_SCOPE) +endfunction() + +# Takes a PURL VALUE and returns an SBOM purl external reference in OUT_VAR. +function(_qt_internal_sbom_get_purl_value_extref) + set(opt_args "") + set(single_args + OUT_VAR + VALUE + ) + set(multi_args "") + cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}") + _qt_internal_validate_all_args_are_parsed(arg) + + if(NOT arg_OUT_VAR) + message(FATAL_ERROR "OUT_VAR must be set") + endif() + + if(NOT arg_VALUE) + message(FATAL_ERROR "VALUE must be set") + endif() + + # SPDX SBOM External reference type. + set(ext_ref_prefix "PACKAGE-MANAGER purl") + set(external_ref "${ext_ref_prefix} ${arg_VALUE}") set(result "EXTREF" "${external_ref}") set(${arg_OUT_VAR} "${result}" PARENT_SCOPE) endfunction()