CMake: Rework SBOM PURL handling to handle multiple values

Using a single PURL for a target is not enough to represent all the
information that an SBOM processing tool might want to know about.

For example for the bundled harfbuzz package, we want to inform at
least about two versions: the upstream version that was originally
imported into Qt sources, and the Qt version or sha1 of the sources
that were used, because the 3rdparty code might have been modified and
there might be new Qt-specific vulnerabilities.

To handle this, we need to generate multiple PURLs for a single
target.

Introduce six new options to be understood by qt_internal_add_module
and other SBOM functions:
 - PURL_QT_ARGS
 - PURL_MIRROR_ARGS
 - PURL_3RDPARTY_UPSTREAM_ARGS
 - PURL_QT_VALUE
 - PURL_3RDPARTY_UPSTREAM_VALUE
 - PURL_MIRROR_VALUE

The first three options take multiple arguments, and will generate a
PURL for each used variant, based on the purl parsing options that
were previously handled. For example passing:
  PURL_3RDPARTY_UPSTREAM_ARGS
      PURL_TYPE "github"
      PURL_NAMESPACE "harfbuzz"
      PURL_NAME "harfbuzz"
      PURL_VERSION "v8.5.0" # tag

will generate a PURL pointing to the upstream harfbuzz repo hosted
on github.

The next three options allow specifying a purl value directly as a
single string, rather than as separate parts. This might be useful
when the PURL is pre-constructed and read from a qt_attribution file.
Example:
  PURL_3RDPARTY_UPSTREAM_VALUE "pkg:github/harfbuzz/harfbuzz@v8.5.0"

When no arguments are specified, targets like Qt modules or Qt
3rd party libraries will have automatically generated QT and MIRROR
variant PURLs that point to code.qt.io and github.com, along with
important info like the version and subdir source path for a given
target.

Third party libraries are expected to be manually annotated with a
3RDPARTY_UPSTREAM variant PURL that points to the original upstream.
In a future change, these will be read from a qt_attribution.json
file.

The final set of generated PURLs for the harfbuzz package might look
like:

  pkg:github/harfbuzz/harfbuzz@v8.5.0
  pkg:github/qt/qtbase@5018b71e99f?library_name=BundledHarfbuzz#src/3rdparty/harfbuzz-ng
  pkg:generic/TheQtCompany/qtbase-BundledHarfbuzz@5018b71e99f?vcs_url=https://code.qt.io/qt/qtbase.git@5018b71e99f&library_name=BundledHarfbuzz#src/3rdparty/harfbuzz-ng

Additionally a few more purl parsing options are added.

Add a PURL_USE_PACKAGE_VERSION option that will use the
qt_attribution.json or custom PACKAGE_VERSION value as the PURL
version, so it doesn't have to be manually specified.

This is an opt-in, and not the default, because some attribution
files contain plain text, white-space separated, strings as the
version value (like the 'sha3' 3rd party lib) which ends up generating
a broken PURL and a failing JSON conversion of the SBOM.
So we have to manually annotate targets that should use the
attribution json package version, until a better way to handle this
can be found.

Add a PURL_VCS_URL option that will generate a PURL qualifier (a HTTP
query parameter) with the given value, to indicate to SBOM-processing
tools what is the upstream repo URL for the package.
They can use this information to display known vulnerabilities for the
package hosted at that URL.
This is mostly useful for the generic QT purl variant, and is
automatically generated for Qt entity types.

Task-number: QTBUG-122899
Change-Id: Ie000b01b478bef4bff6f4803dd39e37b7a8055d5
Reviewed-by:  Alexey Edelev <alexey.edelev@qt.io>
(cherry picked from commit f7e1123620b623be0c321b54eaba7a1d618a7ce1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Alexandru Croitor 2024-07-24 17:29:11 +02:00 committed by Qt Cherry-pick Bot
parent bb26830b3c
commit ba4172d0d7

View File

@ -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()