Revision control
Copy as Markdown
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Gets the spdx id of a target to be used in a sbom relationship entry.
# Errors out when either the target does not exist, or when an spdx id cannot be determined for
# the target.
# OPTION_NAME is used for error messages to provide context on which argument is being processed.
function(_qt_internal_sbom_get_spdx_id_for_target_in_relationship out_var)
set(opt_args "")
set(single_args
TARGET
OPTION_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 TARGET "${arg_TARGET}")
message(FATAL_ERROR
"${arg_OPTION_NAME} target \"${arg_TARGET}\" does not exist.")
endif()
_qt_internal_sbom_get_spdx_id_for_target("${arg_TARGET}" target_spdx_id)
if(NOT target_spdx_id)
message(FATAL_ERROR
"Could not determine SPDX ID for OPTION_NAME '${arg_OPTION_NAME}' target "
"\"${arg_TARGET}\".")
endif()
set(${out_var} "${target_spdx_id}" PARENT_SCOPE)
endfunction()
# Parses a relationship entry and outputs an appropriately formatted relationship string.
# FROM and TO should list target names enriched with sbom info.
function(_qt_internal_sbom_serialize_sbom_relationship_entry)
set(opt_args
ESCAPE_CYDX_QUOTES
)
set(single_args
SBOM_RELATIONSHIP_FROM
SBOM_RELATIONSHIP_TYPE
SBOM_RELATIONSHIP_COMMENT
OUTPUT_SBOM_FORMAT
CYDX_TOML_KEY
OUT_VAR_RELATIONSHIP_STRINGS
)
set(multi_args
SBOM_RELATIONSHIP_TO # This is multi-value, to accomodate future spdx v3
SBOM_FORMATS
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_OUTPUT_SBOM_FORMAT)
message(FATAL_ERROR "OUTPUT_SBOM_FORMAT is required.")
endif()
if(NOT arg_OUT_VAR_RELATIONSHIP_STRINGS)
message(FATAL_ERROR "OUT_VAR_RELATIONSHIP_STRINGS is required.")
endif()
if(NOT arg_SBOM_RELATIONSHIP_FROM)
message(FATAL_ERROR "SBOM_RELATIONSHIP_FROM is required.")
endif()
if(NOT arg_SBOM_RELATIONSHIP_TO)
message(FATAL_ERROR "SBOM_RELATIONSHIP_TO is required.")
endif()
if(NOT arg_SBOM_RELATIONSHIP_TYPE)
message(FATAL_ERROR "SBOM_RELATIONSHIP_TYPE is required.")
endif()
# Always return a value.
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "" PARENT_SCOPE)
set(filtered_argn "${ARGN}")
list(FILTER filtered_argn INCLUDE REGEX "SBOM_RELATIONSHIP_FROM")
list(LENGTH filtered_argn from_count)
if(from_count GREATER 1)
message(FATAL_ERROR
"Multiple SBOM_RELATIONSHIP_FROM arguments found. Only one is expected for a single "
"entry. Perhaps you forgot to prepend a new SBOM_RELATIONSHIP_ENTRY before the next "
"relationship? \n"
"Relationship entry values below:\n${ARGN}"
)
endif()
_qt_internal_sbom_validate_spdx_v2_relationship_type(
RELATIONSHIP_TYPE "${arg_SBOM_RELATIONSHIP_TYPE}"
OUT_VAR_IS_VALID relationship_type_is_valid
)
if(NOT relationship_type_is_valid)
message(FATAL_ERROR
"Invalid SPDX v2 SBOM_RELATIONSHIP_TYPE: '${arg_SBOM_RELATIONSHIP_TYPE}'.")
endif()
_qt_internal_sbom_get_spdx_id_for_target_in_relationship(from_spdx_id
TARGET "${arg_SBOM_RELATIONSHIP_FROM}"
OPTION_NAME "SBOM_RELATIONSHIP_FROM"
)
_qt_internal_sbom_is_external_target_dependency("${arg_SBOM_RELATIONSHIP_FROM}"
OUT_VAR from_is_external
)
if(NOT arg_SBOM_FORMATS OR arg_SBOM_FORMATS STREQUAL "ALL")
set(active_formats "SPDX_V2" "CYDX_V1_6")
else()
set(active_formats ${arg_SBOM_FORMATS})
endif()
if(arg_OUTPUT_SBOM_FORMAT STREQUAL "SPDX_V2" AND arg_OUTPUT_SBOM_FORMAT IN_LIST active_formats)
# For SPDX, we build up the relationship string, and its associated comment if relevant.
set(spdx_relationship_list "")
if(from_is_external)
_qt_internal_sbom_get_external_document_ref_spdx_id_from_sbom_target(
TARGET "${arg_SBOM_RELATIONSHIP_FROM}"
OUT_VAR external_reference_from
CREATE_TEMPORARY_REF_WHEN_MISSING
)
if(NOT external_reference_from)
message(FATAL_ERROR
"SBOM_RELATIONSHIP_FROM target '${arg_SBOM_RELATIONSHIP_FROM}' is an external "
"dependency, but its SPDX v2 external document reference id can't be "
"found. Consider limiting the relationship to a non-SPDX SBOM format using "
"the SBOM_FORMATS argument.")
endif()
# If the FROM target is in an external document that was never declared in the current
# SPDX document (e.g. it lives in a CycloneDX-only document), skip this relationship.
get_cmake_property(from_external_document_is_known
_qt_known_external_documents_${external_reference_from})
if(NOT from_external_document_is_known)
message(DEBUG
"Skipping SPDX relationship from external target "
"'${arg_SBOM_RELATIONSHIP_FROM}', because its external document reference "
"'${external_reference_from}' was not recorded in the current SPDX document.")
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "" PARENT_SCOPE)
return()
endif()
string(APPEND external_reference_from ":")
else()
set(external_reference_from "")
endif()
foreach(relationship_to IN LISTS arg_SBOM_RELATIONSHIP_TO)
_qt_internal_sbom_get_spdx_id_for_target_in_relationship(to_spdx_id
TARGET "${relationship_to}"
OPTION_NAME "SBOM_RELATIONSHIP_TO"
)
_qt_internal_sbom_is_external_target_dependency("${relationship_to}"
OUT_VAR to_is_external
)
if(to_is_external)
_qt_internal_sbom_get_external_document_ref_spdx_id_from_sbom_target(
TARGET "${relationship_to}"
OUT_VAR external_reference_to
CREATE_TEMPORARY_REF_WHEN_MISSING
)
if(NOT external_reference_to)
message(FATAL_ERROR
"SBOM_RELATIONSHIP_TO target '${relationship_to}' is an external "
"dependency, but its SPDX v2 external document reference id can't be "
"found. Consider limiting the relationship to a non-SPDX SBOM format using "
"the SBOM_FORMATS argument.")
endif()
# If the target is in an external document that was never declared in the current
# SPDX document (e.g. it lives in a CycloneDX-only document), skip this
# relationship.
get_cmake_property(to_external_document_is_known
_qt_known_external_documents_${external_reference_to})
if(NOT to_external_document_is_known)
message(DEBUG
"Skipping SPDX relationship to external target '${relationship_to}', "
"because its external document reference '${external_reference_to}' was "
"not recorded in the current SPDX document.")
continue()
endif()
string(APPEND external_reference_to ":")
else()
set(external_reference_to "")
endif()
string(CONCAT spdx_relationship
"Relationship:"
" ${external_reference_from}${from_spdx_id}"
" ${arg_SBOM_RELATIONSHIP_TYPE}"
" ${external_reference_to}${to_spdx_id}")
list(APPEND spdx_relationship_list "${spdx_relationship}")
if(arg_SBOM_RELATIONSHIP_COMMENT)
string(CONCAT spdx_relationship_comment
"RelationshipComment: "
"<text>${arg_SBOM_RELATIONSHIP_COMMENT}</text>")
list(APPEND spdx_relationship_list "${spdx_relationship_comment}")
endif()
endforeach()
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "${spdx_relationship_list}" PARENT_SCOPE)
endif()
if(arg_OUTPUT_SBOM_FORMAT STREQUAL "CYDX_V1_6"
AND arg_OUTPUT_SBOM_FORMAT IN_LIST active_formats)
set(cydx_toml_key "")
if(arg_CYDX_TOML_KEY)
set(cydx_toml_key "${arg_CYDX_TOML_KEY}")
endif()
if(arg_ESCAPE_CYDX_QUOTES)
set(quote "\\\"")
else()
set(quote "\"")
endif()
# If the FROM target is in an external document that was never declared in the current
# CycloneDX document (e.g. it lives in a SPDX-only document), skip this relationship.
if(from_is_external)
get_target_property(from_bom_serial_number "${arg_SBOM_RELATIONSHIP_FROM}"
_qt_sbom_cydx_bom_serial_number_uuid)
set(from_cydx_document_is_known FALSE)
if(from_bom_serial_number)
get_cmake_property(from_cydx_document_is_known
_qt_known_external_documents_${from_bom_serial_number}_cydx)
endif()
if(NOT from_cydx_document_is_known)
message(DEBUG
"Skipping CycloneDX relationship from external target "
"'${arg_SBOM_RELATIONSHIP_FROM}', because its external document has no recorded "
"CycloneDX bom serial number in the current CycloneDX document.")
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "" PARENT_SCOPE)
return()
endif()
endif()
set(cydx_relationship_list "")
foreach(relationship_to IN LISTS arg_SBOM_RELATIONSHIP_TO)
_qt_internal_sbom_get_spdx_id_for_target_in_relationship(to_spdx_id
TARGET "${relationship_to}"
OPTION_NAME "SBOM_RELATIONSHIP_TO"
)
# If the target is in an external document that was never declared in the current
# CycloneDX document (e.g. it lives in a SPDX-only document), skip this relationship.
_qt_internal_sbom_is_external_target_dependency("${relationship_to}"
OUT_VAR to_is_external
)
if(to_is_external)
get_target_property(to_bom_serial_number "${relationship_to}"
_qt_sbom_cydx_bom_serial_number_uuid)
set(to_cydx_document_is_known FALSE)
if(to_bom_serial_number)
get_cmake_property(to_cydx_document_is_known
_qt_known_external_documents_${to_bom_serial_number}_cydx)
endif()
if(NOT to_cydx_document_is_known)
message(DEBUG
"Skipping CycloneDX relationship to external target '${relationship_to}', "
"because its external document has no recorded CycloneDX bom serial number "
"in the current CycloneDX document.")
continue()
endif()
endif()
list(APPEND cydx_relationship_list "
[[${cydx_toml_key}relationships]]
relationship_from = ${quote}${from_spdx_id}${quote}
relationship_type = ${quote}${arg_SBOM_RELATIONSHIP_TYPE}${quote}
relationship_to = ${quote}${to_spdx_id}${quote}
relationship_comment = ${quote}${arg_SBOM_RELATIONSHIP_COMMENT}${quote}
")
endforeach()
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "${cydx_relationship_list}" PARENT_SCOPE)
endif()
endfunction()
# Checks if a relationship type is valid, according to the list at
function(_qt_internal_sbom_validate_spdx_v2_relationship_type)
if(NOT QT_GENERATE_SBOM)
if(arg_OUT_VAR_IS_VALID)
set(${arg_OUT_VAR_IS_VALID} "" PARENT_SCOPE)
endif()
return()
endif()
set(opt_args "")
set(single_args
RELATIONSHIP_TYPE
OUT_VAR_IS_VALID
)
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_RELATIONSHIP_TYPE)
message(FATAL_ERROR "RELATIONSHIP_TYPE is required.")
endif()
if(NOT arg_OUT_VAR_IS_VALID)
message(FATAL_ERROR "OUT_VAR_IS_VALID is required.")
endif()
set(valid_types
DESCRIBES
DESCRIBED_BY
CONTAINS
CONTAINED_BY
DEPENDS_ON
DEPENDENCY_OF
DEPENDENCY_MANIFEST_OF
BUILD_DEPENDENCY_OF
DEV_DEPENDENCY_OF
OPTIONAL_DEPENDENCY_OF
PROVIDED_DEPENDENCY_OF
TEST_DEPENDENCY_OF
RUNTIME_DEPENDENCY_OF
EXAMPLE_OF
GENERATES
GENERATED_FROM
ANCESTOR_OF
DESCENDANT_OF
VARIANT_OF
DISTRIBUTION_ARTIFACT
PATCH_FOR
PATCH_APPLIED
COPY_OF
FILE_ADDED
FILE_DELETED
FILE_MODIFIED
EXPANDED_FROM_ARCHIVE
DYNAMIC_LINK
STATIC_LINK
DATA_FILE_OF
TEST_CASE_OF
BUILD_TOOL_OF
DEV_TOOL_OF
TEST_OF
TEST_TOOL_OF
DOCUMENTATION_OF
OPTIONAL_COMPONENT_OF
METAFILE_OF
PACKAGE_OF
AMENDS
PREREQUISITE_FOR
HAS_PREREQUISITE
REQUIREMENT_DESCRIPTION_FOR
SPECIFICATION_FOR
OTHER
)
if(arg_RELATIONSHIP_TYPE IN_LIST valid_types)
set(is_valid TRUE)
else()
set(is_valid FALSE)
endif()
set(${arg_OUT_VAR_IS_VALID} "${is_valid}" PARENT_SCOPE)
endfunction()
# Processes a list of relationships entries and outputs a list of strings.
# ESCAPE_CYDX_QUOTES needs to be passed when the output is meant to be used in a
# file(APPEND staging_area_spdx_file block.
function(_qt_internal_sbom_serialize_relationship_entries)
set(opt_args
ESCAPE_CYDX_QUOTES
)
set(single_args
OUTPUT_SBOM_FORMAT
CYDX_TOML_KEY
OUT_VAR_RELATIONSHIPS_STRINGS
)
set(multi_args
SBOM_RELATIONSHIP_ENTRIES
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_OUTPUT_SBOM_FORMAT)
message(FATAL_ERROR "OUTPUT_SBOM_FORMAT is required.")
endif()
if(NOT arg_OUT_VAR_RELATIONSHIPS_STRINGS)
message(FATAL_ERROR "OUT_VAR_RELATIONSHIPS_STRINGS is required.")
endif()
# Collect each SBOM_RELATIONSHIP_ENTRY args into a separate variable.
set(entry_idx -1)
set(entry_indices "")
foreach(entry_arg IN LISTS arg_SBOM_RELATIONSHIP_ENTRIES)
if(entry_arg STREQUAL "SBOM_RELATIONSHIP_ENTRY")
math(EXPR entry_idx "${entry_idx}+1")
list(APPEND entry_indices "${entry_idx}")
elseif(entry_idx GREATER_EQUAL 0)
list(APPEND entry_${entry_idx}_args "${entry_arg}")
else()
message(FATAL_ERROR "Missing SBOM_RELATIONSHIP_ENTRY keyword.")
endif()
endforeach()
set(relationships_strings "")
set(extra_entry_args "")
if(arg_CYDX_TOML_KEY)
list(APPEND extra_entry_args CYDX_TOML_KEY "${arg_CYDX_TOML_KEY}")
endif()
if(arg_ESCAPE_CYDX_QUOTES)
list(APPEND extra_entry_args ESCAPE_CYDX_QUOTES)
endif()
foreach(entry_idx IN LISTS entry_indices)
set(entry_relationship_strings "")
_qt_internal_sbom_serialize_sbom_relationship_entry(
${entry_${entry_idx}_args}
${extra_entry_args}
OUTPUT_SBOM_FORMAT "${arg_OUTPUT_SBOM_FORMAT}"
OUT_VAR_RELATIONSHIP_STRINGS entry_relationship_strings
)
if(entry_relationship_strings)
list(APPEND relationships_strings ${entry_relationship_strings})
endif()
endforeach()
set(${arg_OUT_VAR_RELATIONSHIPS_STRINGS} "${relationships_strings}" PARENT_SCOPE)
endfunction()
# Collects relationships for a given target, and outputs them for each supported format.
#
# Collects relationships from the following sources:
# - target linkage
# - custom relationship entries
# - always adds a "contains" relationship to the parent project package
function(_qt_internal_sbom_handle_target_relationships target)
set(opt_args
NO_DEFAULT_PROJECT_CONTAINS_RELATIONSHIP
)
set(single_args
SPDX_ID
PROJECT_SPDX_ID
OUT_VAR_SBOM_RELATIONSHIP_ENTRIES
OUT_VAR_SPDX_V2_RELATIONSHIPS # spdx v2, deprecated, still used by WebEngine
)
set(multi_args
LIBRARIES
PUBLIC_LIBRARIES
SBOM_DEPENDENCIES
SBOM_RELATIONSHIPS # spdx v2 input, deprecated, still used by WebEngine
SBOM_RELATIONSHIP_ENTRIES
)
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(sbom_relationship_entries "")
set(external_target_dependencies "")
set(spdx_relationships "") # deprecated, but needed
_qt_internal_forward_function_args(
FORWARD_PREFIX arg
FORWARD_OUT_VAR handle_target_dependencies_args
FORWARD_MULTI
LIBRARIES
PUBLIC_LIBRARIES
SBOM_DEPENDENCIES
)
# Get relationships based on target linkage.
_qt_internal_sbom_handle_target_dependencies("${target}"
SPDX_ID "${arg_SPDX_ID}"
${handle_target_dependencies_args}
OUT_SBOM_RELATIONSHIP_ENTRIES target_relationship_entries
OUT_EXTERNAL_TARGET_DEPENDENCIES external_target_dependencies
)
if(target_relationship_entries)
list(APPEND sbom_relationship_entries ${target_relationship_entries})
endif()
# Get relationships specified manually.
if(arg_SBOM_RELATIONSHIP_ENTRIES)
list(APPEND sbom_relationship_entries ${arg_SBOM_RELATIONSHIP_ENTRIES})
endif()
# CycloneDX external dependencies need to be processed at the end of document generation,
# so record them.
if(external_target_dependencies)
_qt_internal_sbom_record_external_target_dependecies(
TARGETS ${external_target_dependencies}
)
endif()
# Handle deprecated SBOM_RELATIONSHIPS argument.
if(arg_SBOM_RELATIONSHIPS)
# This option is deprecated, but it's still used by older WebEngine versions.
# The values for the option were not expected to be prefixed with 'Relationship:', so make
# sure to add the prefix for each value.
set(extra_spdx_v2_relationships "")
foreach(spdx_v2_relationship IN LISTS arg_SBOM_RELATIONSHIPS)
if(NOT spdx_v2_relationship MATCHES "Relationship:")
string(PREPEND spdx_v2_relationship "Relationship: ")
endif()
list(APPEND extra_spdx_v2_relationships "${spdx_v2_relationship}")
endforeach()
list(APPEND spdx_relationships ${extra_spdx_v2_relationships})
endif()
# Add a default 'project package CONTAINS the target package' spdx relationship, but only for
# targets built as part of the project. Skip it for system libraries, system build tools or
# when opted out.
set(should_add_project_contains_relationship TRUE)
set(entity_types_without_project_contains
SYSTEM_BUILD_TOOL
SYSTEM_LIBRARY
)
get_target_property(sbom_entity_type "${target}" _qt_sbom_entity_type)
if(NOT sbom_entity_type)
message(FATAL_ERROR "Target ${target} does not have a _qt_sbom_entity_type property set.")
endif()
if(sbom_entity_type IN_LIST entity_types_without_project_contains)
set(should_add_project_contains_relationship FALSE)
endif()
if(arg_NO_DEFAULT_PROJECT_CONTAINS_RELATIONSHIP)
set(should_add_project_contains_relationship FALSE)
endif()
if(should_add_project_contains_relationship)
_qt_internal_sbom_get_current_project_target(project_target)
list(PREPEND sbom_relationship_entries
SBOM_RELATIONSHIP_ENTRY
SBOM_RELATIONSHIP_FROM
"${project_target}"
SBOM_RELATIONSHIP_TYPE
CONTAINS
SBOM_RELATIONSHIP_TO
"${target}"
)
endif()
set(${arg_OUT_VAR_SBOM_RELATIONSHIP_ENTRIES} "${sbom_relationship_entries}" PARENT_SCOPE)
set(${arg_OUT_VAR_SPDX_V2_RELATIONSHIPS} "${spdx_relationships}" PARENT_SCOPE)
endfunction()
function(_qt_internal_sbom_get_current_project_relationships out_var)
set(opt_args "")
set(single_args "")
set(multi_args
SBOM_RELATIONSHIP_ENTRIES
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
# No validation on purpose, there might be more options set.
set(values "")
if(arg_SBOM_RELATIONSHIP_ENTRIES)
set(values ${arg_SBOM_RELATIONSHIP_ENTRIES})
endif()
set(${out_var} "${values}" PARENT_SCOPE)
endfunction()
# Collects relationships for the currently active project, and serializes them for each supported
# format.
function(_qt_internal_sbom_handle_project_relationships)
set(opt_args "")
set(single_args
OUTPUT_SBOM_FORMAT
OUT_VAR_RELATIONSHIP_STRINGS
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_OUTPUT_SBOM_FORMAT)
message(FATAL_ERROR "OUTPUT_SBOM_FORMAT is required.")
endif()
if(NOT arg_OUT_VAR_RELATIONSHIP_STRINGS)
message(FATAL_ERROR "OUT_VAR_RELATIONSHIP_STRINGS is required.")
endif()
_qt_internal_sbom_get_current_project_target(project_target)
get_target_property(project_finalize_args "${project_target}" _qt_finalize_sbom_args)
if(NOT project_finalize_args)
set(project_finalize_args "")
endif()
_qt_internal_sbom_get_current_project_relationships(entries
${project_finalize_args}
)
if(NOT entries)
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "" PARENT_SCOPE)
return()
endif()
set(relationship_entries_args "")
if(arg_OUTPUT_SBOM_FORMAT MATCHES "CYDX")
list(APPEND relationship_entries_args CYDX_TOML_KEY "root_component.")
endif()
_qt_internal_sbom_serialize_relationship_entries(
${relationship_entries_args}
OUTPUT_SBOM_FORMAT "${arg_OUTPUT_SBOM_FORMAT}"
OUT_VAR_RELATIONSHIPS_STRINGS relationships_strings
SBOM_RELATIONSHIP_ENTRIES ${entries}
)
set(${arg_OUT_VAR_RELATIONSHIP_STRINGS} "${relationships_strings}" PARENT_SCOPE)
endfunction()