# Copyright (C) 2024 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause # Walks a target's direct dependencies and assembles a list of relationships between the packages # of the target dependencies. # Currently handles various Qt targets and system libraries. function(_qt_internal_sbom_handle_target_dependencies target) set(opt_args "") set(single_args SPDX_ID OUT_RELATIONSHIPS ) set(multi_args LIBRARIES PUBLIC_LIBRARIES ) 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(package_spdx_id "${arg_SPDX_ID}") set(libraries "") if(arg_LIBRARIES) list(APPEND libraries "${arg_LIBRARIES}") endif() get_target_property(extend_libraries "${target}" _qt_extend_target_libraries) if(extend_libraries) list(APPEND libraries ${extend_libraries}) endif() get_target_property(target_type ${target} TYPE) set(valid_target_types EXECUTABLE SHARED_LIBRARY MODULE_LIBRARY STATIC_LIBRARY OBJECT_LIBRARY ) if(target_type IN_LIST valid_target_types) get_target_property(link_libraries "${target}" LINK_LIBRARIES) if(link_libraries) list(APPEND libraries ${link_libraries}) endif() endif() set(public_libraries "") if(arg_PUBLIC_LIBRARIES) list(APPEND public_libraries "${arg_PUBLIC_LIBRARIES}") endif() get_target_property(extend_public_libraries "${target}" _qt_extend_target_public_libraries) if(extend_public_libraries) list(APPEND public_libraries ${extend_public_libraries}) endif() set(sbom_dependencies "") if(arg_SBOM_DEPENDENCIES) list(APPEND sbom_dependencies "${arg_SBOM_DEPENDENCIES}") endif() get_target_property(extend_sbom_dependencies "${target}" _qt_extend_target_sbom_dependencies) if(extend_sbom_dependencies) list(APPEND sbom_dependencies ${extend_sbom_dependencies}) endif() list(REMOVE_DUPLICATES libraries) list(REMOVE_DUPLICATES public_libraries) list(REMOVE_DUPLICATES sbom_dependencies) set(all_direct_libraries ${libraries} ${public_libraries} ${sbom_dependencies}) list(REMOVE_DUPLICATES all_direct_libraries) set(spdx_dependencies "") set(relationships "") # Go through each direct linked lib. foreach(direct_lib IN LISTS all_direct_libraries) if(NOT TARGET "${direct_lib}") continue() endif() # Some targets are Qt modules, even though they are not prefixed with Qt::, targets # like Bootstrap and QtLibraryInfo. We use the property to differentiate them. get_target_property(is_marked_as_qt_module "${direct_lib}" _qt_sbom_is_qt_module) # Custom sbom targets created by _qt_internal_create_sbom_target are always imported, so we # need to differentiate them via this property. get_target_property(is_custom_sbom_target "${direct_lib}" _qt_sbom_is_custom_sbom_target) if("${direct_lib}" MATCHES "^(Qt::.*)|(${QT_CMAKE_EXPORT_NAMESPACE}::.*)") set(is_qt_prefixed TRUE) else() set(is_qt_prefixed FALSE) endif() # is_qt_dependency is not strictly only a qt dependency, it applies to custom sbom # targets as well. But I'm having a hard time to come up with a better name. if(is_marked_as_qt_module OR is_custom_sbom_target OR is_qt_prefixed) set(is_qt_dependency TRUE) else() set(is_qt_dependency FALSE) endif() # Regular Qt dependency, depend on the relevant package, either within the current # document or via an external document. if(is_qt_dependency) _qt_internal_sbom_is_external_target_dependency("${direct_lib}" OUT_VAR is_dependency_in_external_document ) if(is_dependency_in_external_document) # External document case. _qt_internal_sbom_add_external_target_dependency( "${package_spdx_id}" "${direct_lib}" extra_spdx_dependencies extra_spdx_relationships ) if(extra_spdx_dependencies) list(APPEND spdx_dependencies "${extra_spdx_dependencies}") endif() if(extra_spdx_relationships) list(APPEND relationships "${extra_spdx_relationships}") endif() else() # Dependency is part of current repo build. _qt_internal_sbom_get_spdx_id_for_target("${direct_lib}" dep_spdx_id) if(dep_spdx_id) list(APPEND spdx_dependencies "${dep_spdx_id}") else() message(DEBUG "Could not add target dependency on ${direct_lib} " "because no spdx id could be found") endif() endif() else() # If it's not a Qt dependency, then it's most likely a 3rd party dependency. # If we are looking at a FindWrap dependency, we need to depend on either # the system or vendored lib, whichever one the FindWrap script points to. # If we are looking at a non-Wrap dependency, it's 99% a system lib. __qt_internal_walk_libs( "${direct_lib}" lib_walked_targets _discarded_out_var "sbom_targets" "collect_targets") # Detect if we are dealing with a vendored / bundled lib. set(bundled_targets_found FALSE) if(lib_walked_targets) foreach(lib_walked_target IN LISTS lib_walked_targets) get_target_property(is_3rdparty_bundled_lib "${lib_walked_target}" _qt_module_is_3rdparty_library) _qt_internal_sbom_get_spdx_id_for_target("${lib_walked_target}" lib_spdx_id) # Add a dependency on the vendored lib instead of the Wrap target. if(is_3rdparty_bundled_lib AND lib_spdx_id) list(APPEND spdx_dependencies "${lib_spdx_id}") set(bundled_targets_found TRUE) endif() endforeach() endif() # If no bundled libs were found as a result of walking the Wrap lib, we consider this # a system lib, and add a dependency on it directly. if(NOT bundled_targets_found) _qt_internal_sbom_get_spdx_id_for_target("${direct_lib}" lib_spdx_id) _qt_internal_sbom_is_external_target_dependency("${direct_lib}" SYSTEM_LIBRARY OUT_VAR is_dependency_in_external_document ) if(lib_spdx_id) if(NOT is_dependency_in_external_document) list(APPEND spdx_dependencies "${lib_spdx_id}") # Mark the system library is used, so that we later generate an sbom for it. _qt_internal_append_to_cmake_property_without_duplicates( _qt_internal_sbom_consumed_system_library_targets "${direct_lib}" ) else() # Refer to the package in the external document. This can be the case # in a top-level build, where a system library is reused across repos. _qt_internal_sbom_add_external_target_dependency( "${package_spdx_id}" "${direct_lib}" extra_spdx_dependencies extra_spdx_relationships ) if(extra_spdx_dependencies) list(APPEND spdx_dependencies "${extra_spdx_dependencies}") endif() if(extra_spdx_relationships) list(APPEND relationships "${extra_spdx_relationships}") endif() endif() else() message(DEBUG "Could not add target dependency on system library ${direct_lib} " "because no spdx id could be found") endif() endif() endif() endforeach() foreach(dep_spdx_id IN LISTS spdx_dependencies) set(relationship "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}" ) list(APPEND relationships "${relationship}") endforeach() set(${arg_OUT_RELATIONSHIPS} "${relationships}" PARENT_SCOPE) endfunction() # Checks whether the current target will have its sbom generated into the current repo sbom # document, or whether it is present in an external sbom document. function(_qt_internal_sbom_is_external_target_dependency target) set(opt_args SYSTEM_LIBRARY ) 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) get_target_property(is_imported "${target}" IMPORTED) get_target_property(is_custom_sbom_target "${target}" _qt_sbom_is_custom_sbom_target) _qt_internal_sbom_get_root_project_name_lower_case(current_repo_project_name) get_property(target_repo_project_name TARGET ${target} PROPERTY _qt_sbom_spdx_repo_project_name_lowercase) if(NOT "${target_repo_project_name}" STREQUAL "" AND NOT "${target_repo_project_name}" STREQUAL "${current_repo_project_name}") set(part_of_other_repo TRUE) else() set(part_of_other_repo FALSE) endif() # A target is in an external document if # 1) it is imported, and not a custom sbom target, and not a system library # 2) it was created as part of another repo in a top-level build if((is_imported AND NOT is_custom_sbom_target AND NOT arg_SYSTEM_LIBRARY) OR part_of_other_repo) set(is_dependency_in_external_document TRUE) else() set(is_dependency_in_external_document FALSE) endif() set(${arg_OUT_VAR} "${is_dependency_in_external_document}" PARENT_SCOPE) endfunction() # Handles generating an external document reference SDPX element for each target package that is # located in a different spdx document. function(_qt_internal_sbom_add_external_target_dependency current_package_spdx_id target_dep out_spdx_dependencies out_spdx_relationships ) set(target "${target_dep}") _qt_internal_sbom_get_spdx_id_for_target("${target}" dep_spdx_id) if(NOT dep_spdx_id) message(DEBUG "Could not add external target dependency on ${target} " "because no spdx id could be found") set(${out_spdx_dependencies} "" PARENT_SCOPE) set(${out_spdx_relationships} "" PARENT_SCOPE) return() endif() set(spdx_dependencies "") set(spdx_relationships "") # Get the external document path and the repo it belongs to for the given target. get_property(relative_installed_repo_document_path TARGET ${target} PROPERTY _qt_sbom_spdx_relative_installed_repo_document_path) get_property(project_name_lowercase TARGET ${target} PROPERTY _qt_sbom_spdx_repo_project_name_lowercase) if(relative_installed_repo_document_path AND project_name_lowercase) _qt_internal_sbom_get_external_document_ref_spdx_id( "${project_name_lowercase}" external_document_ref) get_cmake_property(known_external_document _qt_known_external_documents_${external_document_ref}) set(relationship "${current_package_spdx_id} DEPENDS_ON ${external_document_ref}:${dep_spdx_id}") list(APPEND spdx_relationships "${relationship}") # Only add a reference to the external document package, if we haven't done so already. if(NOT known_external_document) set(install_prefixes "") get_cmake_property(install_prefix _qt_internal_sbom_install_prefix) list(APPEND install_prefixes "${install_prefix}") set(external_document "${relative_installed_repo_document_path}") _qt_internal_sbom_generate_add_external_reference( EXTERNAL_DOCUMENT_FILE_PATH "${external_document}" EXTERNAL_DOCUMENT_INSTALL_PREFIXES ${install_prefixes} EXTERNAL_DOCUMENT_SPDX_ID "${external_document_ref}" ) set_property(GLOBAL PROPERTY _qt_known_external_documents_${external_document_ref} TRUE) set_property(GLOBAL APPEND PROPERTY _qt_known_external_documents "${external_document_ref}") endif() else() message(WARNING "Missing spdx document path for external ref: " "package_name_for_spdx_id ${package_name_for_spdx_id} direct_lib ${direct_lib}") endif() set(${out_spdx_dependencies} "${spdx_dependencies}" PARENT_SCOPE) set(${out_spdx_relationships} "${spdx_relationships}" PARENT_SCOPE) endfunction()