# 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() 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() 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() 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: " "${arg_SBOM_RELATIONSHIP_COMMENT}") 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() 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" ) 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 # https://spdx.github.io/spdx-spec/v2.3/relationships-between-SPDX-elements/ 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) _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 "") 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 default "contains" spdx relationship to the project package. _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}" ) 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()