qtbase/cmake/QtPublicSbomFileHelpers.cmake
Moss Heim aa494a4de7 CMake: Fix typo in comment
Pick-to: 6.8
Change-Id: I6151bd27d5310ac153c7c2c8fbb97a9de68a4f01
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
(cherry picked from commit 21bd32b2cc8995d8624cd0fea6f6685827c741c1)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
2025-01-13 15:38:58 +00:00

1084 lines
37 KiB
CMake

# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
# Handles addition of binary files SPDX entries for a given target.
# Is multi-config aware.
function(_qt_internal_sbom_handle_target_binary_files target)
set(opt_args
NO_INSTALL
FRAMEWORK
)
set(single_args
TYPE
SPDX_ID
LICENSE_EXPRESSION
INSTALL_PREFIX
)
set(multi_args
COPYRIGHTS
)
_qt_internal_sbom_get_multi_config_single_args(multi_config_single_args)
list(APPEND single_args ${multi_config_single_args})
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(arg_NO_INSTALL)
message(DEBUG "Skipping sbom target file processing ${target} because NO_INSTALL is set")
return()
endif()
set(supported_types
QT_MODULE
QT_PLUGIN
QT_APP
QT_TOOL
QT_THIRD_PARTY_MODULE
QT_THIRD_PARTY_SOURCES
SYSTEM_LIBRARY
QT_TRANSLATIONS
QT_RESOURCES
QT_CUSTOM
QT_CUSTOM_NO_INFIX
# This will be meant for user projects, and are not currently used by Qt's sbom.
THIRD_PARTY_LIBRARY
THIRD_PARTY_LIBRARY_WITH_FILES
EXECUTABLE
LIBRARY
TRANSLATIONS
RESOURCES
CUSTOM
CUSTOM_NO_INFIX
)
if(NOT arg_TYPE IN_LIST supported_types)
message(FATAL_ERROR "Unsupported target TYPE for SBOM creation: ${arg_TYPE}")
endif()
set(types_without_binary_files
QT_THIRD_PARTY_SOURCES
QT_TRANSLATIONS
QT_RESOURCES
QT_CUSTOM
QT_CUSTOM_NO_INFIX
SYSTEM_LIBRARY
THIRD_PARTY_LIBRARY
TRANSLATIONS
RESOURCES
CUSTOM
CUSTOM_NO_INFIX
)
get_target_property(target_type ${target} TYPE)
if(arg_TYPE IN_LIST types_without_binary_files)
message(DEBUG "Target ${target} has no binary files to reference in the SBOM "
"because it has the ${arg_TYPE} type.")
return()
endif()
if(target_type STREQUAL "INTERFACE_LIBRARY")
message(DEBUG "Target ${target} has no binary files to reference in the SBOM "
"because it is an INTERFACE_LIBRARY.")
return()
endif()
get_target_property(excluded ${target} _qt_internal_excluded_from_default_target)
if(excluded)
message(DEBUG "Target ${target} has no binary files to reference in the SBOM "
"because it was excluded from the default 'all' target.")
return()
endif()
if(NOT arg_SPDX_ID)
message(FATAL_ERROR "SPDX_ID must be set")
endif()
set(package_spdx_id "${arg_SPDX_ID}")
set(file_common_options "")
list(APPEND file_common_options PACKAGE_SPDX_ID "${package_spdx_id}")
list(APPEND file_common_options PACKAGE_TYPE "${arg_TYPE}")
if(arg_COPYRIGHTS)
list(APPEND file_common_options COPYRIGHTS "${arg_COPYRIGHTS}")
endif()
if(arg_LICENSE_EXPRESSION)
list(APPEND file_common_options LICENSE_EXPRESSION "${arg_LICENSE_EXPRESSION}")
endif()
if(arg_INSTALL_PREFIX)
list(APPEND file_common_options INSTALL_PREFIX "${arg_INSTALL_PREFIX}")
endif()
set(path_suffix "$<TARGET_FILE_NAME:${target}>")
if(arg_FRAMEWORK)
set(library_path_kind FRAMEWORK_PATH)
else()
set(library_path_kind LIBRARY_PATH)
endif()
if(arg_TYPE STREQUAL "QT_TOOL"
OR arg_TYPE STREQUAL "QT_APP"
OR arg_TYPE STREQUAL "EXECUTABLE")
set(valid_executable_types
"EXECUTABLE"
)
if(ANDROID)
list(APPEND valid_executable_types "MODULE_LIBRARY")
endif()
if(NOT target_type IN_LIST valid_executable_types)
message(FATAL_ERROR "Unsupported target type: ${target_type}")
endif()
get_target_property(app_is_bundle ${target} MACOSX_BUNDLE)
if(app_is_bundle)
_qt_internal_get_executable_bundle_info(bundle "${target}")
_qt_internal_path_join(path_suffix "${bundle_contents_binary_dir}" "${path_suffix}")
endif()
_qt_internal_sbom_handle_multi_config_target_binary_file(${target}
PATH_KIND RUNTIME_PATH
PATH_SUFFIX "${path_suffix}"
OPTIONS ${file_common_options}
)
elseif(arg_TYPE STREQUAL "QT_PLUGIN")
if(NOT (target_type STREQUAL "SHARED_LIBRARY"
OR target_type STREQUAL "STATIC_LIBRARY"
OR target_type STREQUAL "MODULE_LIBRARY"))
message(FATAL_ERROR "Unsupported target type: ${target_type}")
endif()
_qt_internal_sbom_handle_multi_config_target_binary_file(${target}
PATH_KIND INSTALL_PATH
PATH_SUFFIX "${path_suffix}"
OPTIONS ${file_common_options}
)
elseif(arg_TYPE STREQUAL "QT_MODULE"
OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
OR arg_TYPE STREQUAL "LIBRARY"
OR arg_TYPE STREQUAL "THIRD_PARTY_LIBRARY_WITH_FILES"
)
if(WIN32 AND target_type STREQUAL "SHARED_LIBRARY")
_qt_internal_sbom_handle_multi_config_target_binary_file(${target}
PATH_KIND RUNTIME_PATH
PATH_SUFFIX "${path_suffix}"
OPTIONS ${file_common_options}
)
_qt_internal_sbom_handle_multi_config_target_binary_file(${target}
PATH_KIND ARCHIVE_PATH
PATH_SUFFIX "$<TARGET_LINKER_FILE_NAME:${target}>"
OPTIONS
${file_common_options}
IMPORT_LIBRARY
# OPTIONAL because on Windows the import library might not always be present,
# because no symbols are exported.
OPTIONAL
)
elseif(target_type STREQUAL "SHARED_LIBRARY" OR target_type STREQUAL "STATIC_LIBRARY")
_qt_internal_sbom_handle_multi_config_target_binary_file(${target}
PATH_KIND "${library_path_kind}"
PATH_SUFFIX "${path_suffix}"
OPTIONS ${file_common_options}
)
else()
message(FATAL_ERROR "Unsupported target type: ${target_type}")
endif()
endif()
endfunction()
# Add a binary file of a target to the sbom (e.g a shared library or an executable).
# Adds relationships to the SBOM that the binary file was generated from its source files,
# as well as relationship to the owning package.
# TODO: Consider merging the common parts with _qt_internal_sbom_add_custom_file somehow.
function(_qt_internal_sbom_add_binary_file target file_path)
set(opt_args
OPTIONAL
IMPORT_LIBRARY
)
set(single_args
PACKAGE_SPDX_ID
PACKAGE_TYPE
LICENSE_EXPRESSION
CONFIG
INSTALL_PREFIX
)
set(multi_args
COPYRIGHTS
)
cmake_parse_arguments(PARSE_ARGV 2 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_PACKAGE_SPDX_ID)
message(FATAL_ERROR "PACKAGE_SPDX_ID must be set")
endif()
set(file_common_options "")
if(arg_COPYRIGHTS)
list(JOIN arg_COPYRIGHTS "\n" copyrights)
list(APPEND file_common_options COPYRIGHT "<text>${copyrights}</text>")
endif()
if(arg_LICENSE_EXPRESSION)
list(APPEND file_common_options LICENSE "${arg_LICENSE_EXPRESSION}")
endif()
if(arg_INSTALL_PREFIX)
list(APPEND file_common_options INSTALL_PREFIX "${arg_INSTALL_PREFIX}")
endif()
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(spdx_id_suffix "${arg_CONFIG}")
set(config_to_install_option CONFIG ${arg_CONFIG})
else()
set(spdx_id_suffix "")
set(config_to_install_option "")
endif()
set(file_infix "")
if(arg_IMPORT_LIBRARY)
set(file_infix "-ImportLibrary")
endif()
# We kind of have to add the package infix into the file spdx, otherwise we get file system
# collisions for cases like the qml tool and Qml library, and apparently cmake's file(GENERATE)
# is case insensitive for file names.
_qt_internal_sbom_get_package_infix("${arg_PACKAGE_TYPE}" package_infix)
_qt_internal_sbom_get_file_spdx_id(
"${package_infix}-${target}-${file_infix}-${spdx_id_suffix}" spdx_id)
set(optional "")
if(arg_OPTIONAL)
set(optional OPTIONAL)
endif()
# Add relationship from owning package.
set(relationships "${arg_PACKAGE_SPDX_ID} CONTAINS ${spdx_id}")
# Add source file relationships from which the binary file was generated.
_qt_internal_sbom_add_target_source_files("${target}" "${spdx_id}" source_relationships)
if(source_relationships)
list(APPEND relationships "${source_relationships}")
endif()
set(glue "\nRelationship: ")
# Replace semicolon with $<SEMICOLON> to avoid errors when passing into sbom_add.
string(REPLACE ";" "$<SEMICOLON>" relationships "${relationships}")
# Glue the relationships at generation time, because there some source file relationships
# will be conditional on genexes, and evaluate to an empty value, and we want to discard
# such relationships.
set(relationships "$<JOIN:${relationships},${glue}>")
set(relationship_option RELATIONSHIP "${relationships}")
# Add the actual binary file to the latest package.
_qt_internal_sbom_generate_add_file(
FILENAME "${file_path}"
FILETYPE BINARY ${optional}
SPDXID "${spdx_id}"
${file_common_options}
${config_to_install_option}
${relationship_option}
)
endfunction()
# Add a list of to-be-installed files that should appear in the files section of the target's
# SBOM document.
# Supports multiple calls with the same target name.
# Each call is handled as a separate set of files.
# For options that can be passed, see the doc-comment of
# _qt_internal_sbom_handle_target_custom_file_set.
function(_qt_internal_sbom_add_files target)
if(NOT QT_GENERATE_SBOM)
return()
endif()
if(NOT TARGET "${target}")
message(FATAL_ERROR "The target ${target} does not exist.")
endif()
get_target_property(file_sets_count "${target}" _qt_sbom_custom_file_sets_count)
if(NOT file_sets_count)
set(file_sets_count 0)
endif()
set_property(TARGET "${target}"
APPEND PROPERTY _qt_sbom_custom_file_set_${file_sets_count} "${ARGN}")
math(EXPR file_sets_count "${file_sets_count}+1")
set_property(TARGET "${target}" PROPERTY _qt_sbom_custom_file_sets_count "${file_sets_count}")
endfunction()
# Handles addition of custom file SPDX entries for a given target, by processing all the collected
# file sets so far.
# Applies the passed license and copyright info to all collected files.
#
# Options that can be passed.
#
# NO_INSTALL - if set, custom file processing is skipped, because the files will not be installed.
#
# PACKAGE_TYPE - the type of the package that the files belong to, is used to compute an infix
# for the file spdx id.
#
# PACKAGE_SPDX_ID - the package spdx id is used to add a relationship between the package and file.
#
# LICENSE_EXPRESSION - a license expression to apply to the files.
#
# INSTALL_PREFIX - the install prefix for the files, this is usually the install prefix of qt.
#
# COPYRIGHTS - a list of copyright strings to apply to the files.
function(_qt_internal_sbom_handle_target_custom_files target)
set(opt_args
NO_INSTALL
)
set(single_args
PACKAGE_TYPE
PACKAGE_SPDX_ID
LICENSE_EXPRESSION
INSTALL_PREFIX
)
set(multi_args
COPYRIGHTS
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
# Nothing to process if no file sets were defined.
get_target_property(file_sets_count "${target}" _qt_sbom_custom_file_sets_count)
if(NOT file_sets_count)
return()
endif()
if(arg_NO_INSTALL)
message(DEBUG "Skipping sbom custom file processing for ${target} because NO_INSTALL is "
"set")
return()
endif()
if(NOT arg_PACKAGE_SPDX_ID)
message(FATAL_ERROR "PACKAGE_SPDX_ID must be set")
endif()
_qt_internal_forward_function_args(
FORWARD_PREFIX arg
FORWARD_OUT_VAR file_common_options
FORWARD_SINGLE
PACKAGE_TYPE
PACKAGE_SPDX_ID
LICENSE_EXPRESSION
INSTALL_PREFIX
FORWARD_MULTI
COPYRIGHTS
)
# Subtract -1, because foreach(RANGE is inclusive).
math(EXPR file_sets_count "${file_sets_count}-1")
foreach(file_set_index RANGE ${file_sets_count})
get_target_property(file_set_args "${target}" _qt_sbom_custom_file_set_${file_set_index})
if(NOT file_set_args)
message(FATAL_ERROR "No arguments were specified for SBOM custom file set for "
"${target}")
endif()
_qt_internal_sbom_handle_target_custom_file_set("${target}"
${file_common_options} ${file_set_args})
endforeach()
endfunction()
# Processes a file set of custom files for which to include SBOM info.
# The
# NO_INSTALL
# PACKAGE_TYPE
# PACKAGE_SPDX_ID
# LICENSE_EXPRESSION
# INSTALL_PREFIX
# COPYRIGHTS
# options are the same as in _qt_internal_sbom_handle_target_custom_files, and are actually
# forwarded from that function, because they might be set at the target level.
#
# In addition, more options can be passed when called via qt_internal_sbom_add_files:
#
# They are:
#
# FILE_TYPE - the type of each provided file. Supported types can be found in the implementation of
# _qt_internal_sbom_get_spdx_v2_3_file_type_for_file().
# Some examples are QT_TRANSLATION, QT_TRANSLATIONS_CATALOG, QT_RESOURCE.
#
# FILES - a list of file paths to include in the SBOM. Only the file name is currently used.
#
# SOURCE_FILES - which source files were used to generate the custom files. All source files apply
# to each input file.
#
# SOURCE_FILES_PER_INPUT_FILE - for each index i in FILES, the corresponding source files are
# in SOURCE_FILES[i], so that each input gets exactly one source file.
# This is provided as an option, to prevent performance overhead from having to add a
# custom file set for each new source file, when dealing with translations that have a
# 1-to-1 ts->qm relationship.
#
# There is also a set of multi config aware options that can be set, like
# INSTALL_PATH
# INSTALL_PATH_<CONFIG>
# which should be the relative install dir path where the
# files will be installed, relative to $ENV{DESTDIR}/${CMAKE_INSTALL_PREFIX}.
function(_qt_internal_sbom_handle_target_custom_file_set target)
set(opt_args
""
)
set(single_args
FILE_TYPE
PACKAGE_SPDX_ID
PACKAGE_TYPE
LICENSE_EXPRESSION
INSTALL_PREFIX
)
set(multi_args
COPYRIGHTS
FILES
SOURCE_FILES
SOURCE_FILES_PER_INPUT_FILE
)
# Don't explicitly forward the multi config single args, but still parse them.
# They will be accessed from the current scope directly.
set(single_args_without_multi_config_args "${single_args}")
_qt_internal_sbom_get_multi_config_single_args(multi_config_single_args)
list(APPEND single_args ${multi_config_single_args})
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
# No custom files to process.
if(NOT arg_FILES)
return()
endif()
# Don't forward the FILES option.
set(multi_args_without_files "${multi_args}")
list(REMOVE_ITEM multi_args_without_files FILES)
# Handle the case where we have one source file per input file.
if(arg_SOURCE_FILES_PER_INPUT_FILE)
list(LENGTH arg_FILES files_count)
list(LENGTH arg_SOURCE_FILES_PER_INPUT_FILE source_files_count)
if(NOT files_count EQUAL source_files_count)
message(FATAL_ERROR "The number of files passed to SOURCE_FILES must match the number"
"of files passed to SOURCE_FILES_PER_INPUT_FILE.")
endif()
# Don't forward all of the source files, but rather one per input file.
list(REMOVE_ITEM multi_args_without_files SOURCE_FILES_PER_INPUT_FILE)
endif()
_qt_internal_forward_function_args(
FORWARD_PREFIX arg
FORWARD_OUT_VAR forward_args
FORWARD_SINGLE
${single_args_without_multi_config_args}
FORWARD_MULTI
${multi_args_without_files}
)
set(file_index 0)
foreach(file_path IN LISTS arg_FILES)
set(per_file_forward_args "")
# We don't currently use the file path for anything other than getting the file name, to
# embed it into the spdx document entry.
# What matters in the end is the location where the file is installed, which is handled
# by the PATH_KIND option.
get_filename_component(file_name "${file_path}" NAME)
if(arg_SOURCE_FILES_PER_INPUT_FILE)
list(GET arg_SOURCE_FILES_PER_INPUT_FILE "${file_index}" source_file)
list(APPEND per_file_forward_args SOURCE_FILES "${source_file}")
endif()
# The multi_config_single_args are deliberately not forwarded, but are available in this
# function scope, for direct access in the called function scope, because
# cmake_parse_arguments can't handle:
# PATH_KIND "INSTALL_PATH"
# INSTALL_PATH "/some_path"
# the parsing gets confused by what's the option and what's the value.
_qt_internal_sbom_handle_multi_config_custom_file(${target}
PATH_KIND "INSTALL_PATH"
PATH_SUFFIX "${file_name}"
OPTIONS
${forward_args}
${per_file_forward_args}
)
math(EXPR file_index "${file_index}+1")
endforeach()
endfunction()
# Helper function to add a custom file to the sbom, while handling multi-config and different
# kind of paths.
# In multi-config builds, we assume that the non-default config file will be optional, because it
# might not be installed.
#
# Expects the parent scope to contain the ${PATH_KIND} and ${PATH_KIND}_<CONFIG> variables.
# Examples are INSTALL_PATH or INSTALL_PATH_DEBUG.
# They can't be forwarded as options because of cmake_parse_arguments parsing issues when a value
# can be the same as a key. See comment in implementation of
#_qt_internal_sbom_handle_target_custom_file_set.
function(_qt_internal_sbom_handle_multi_config_custom_file target)
set(opt_args "")
set(single_args
PATH_KIND
PATH_SUFFIX
)
set(multi_args
OPTIONS
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(configs ${CMAKE_CONFIGURATION_TYPES})
else()
set(configs "${CMAKE_BUILD_TYPE}")
endif()
foreach(config IN LISTS configs)
_qt_internal_sbom_get_and_check_multi_config_aware_single_arg_option(
arg "${arg_PATH_KIND}" "${config}" resolved_path)
_qt_internal_sbom_get_target_file_is_optional_in_multi_config("${config}" is_optional)
_qt_internal_path_join(file_path "${resolved_path}" "${arg_PATH_SUFFIX}")
_qt_internal_sbom_add_custom_file(
"${target}"
"${file_path}"
${arg_OPTIONS}
${is_optional}
CONFIG ${config}
)
endforeach()
endfunction()
# Adds one custom file with the given relative install path into the SBOM document.
# Will embed GENERATED_FROM source file relationships if a list of source files is specified.
function(_qt_internal_sbom_add_custom_file target installed_file_relative_path)
set(opt_args
OPTIONAL
)
set(single_args
PACKAGE_SPDX_ID
PACKAGE_TYPE
LICENSE_EXPRESSION
INSTALL_PREFIX
FILE_TYPE
CONFIG
)
set(multi_args
COPYRIGHTS
SOURCE_FILES
)
cmake_parse_arguments(PARSE_ARGV 2 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_PACKAGE_SPDX_ID)
message(FATAL_ERROR "PACKAGE_SPDX_ID must be set")
endif()
set(file_common_options "")
if(arg_COPYRIGHTS)
list(JOIN arg_COPYRIGHTS "\n" copyrights)
list(APPEND file_common_options COPYRIGHT "<text>${copyrights}</text>")
endif()
if(arg_LICENSE_EXPRESSION)
list(APPEND file_common_options LICENSE "${arg_LICENSE_EXPRESSION}")
endif()
if(arg_INSTALL_PREFIX)
list(APPEND file_common_options INSTALL_PREFIX "${arg_INSTALL_PREFIX}")
endif()
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(spdx_id_suffix "${arg_CONFIG}")
set(config_to_install_option CONFIG ${arg_CONFIG})
else()
set(spdx_id_suffix "")
set(config_to_install_option "")
endif()
if(arg_FILE_TYPE)
_qt_internal_sbom_get_spdx_v2_3_file_type_for_file(file_type "${arg_FILE_TYPE}")
else()
set(file_type "OTHER")
endif()
get_filename_component(file_name "${installed_file_relative_path}" NAME)
_qt_internal_sbom_get_package_infix("${arg_PACKAGE_TYPE}" package_infix)
_qt_internal_sbom_get_file_spdx_id(
"${package_infix}-${target}-${file_name}-${spdx_id_suffix}" spdx_id)
# Add relationship from owning package.
set(relationships "${arg_PACKAGE_SPDX_ID} CONTAINS ${spdx_id}")
# Add source file relationships from which the custom file was generated.
set(sources_option "")
if(arg_SOURCE_FILES)
set(sources_option SOURCES ${arg_SOURCE_FILES})
endif()
_qt_internal_sbom_add_source_files(
${sources_option}
SPDX_ID "${spdx_id}"
OUT_RELATIONSHIPS_VAR source_relationships
)
if(source_relationships)
list(APPEND relationships "${source_relationships}")
endif()
set(glue "\nRelationship: ")
# Replace semicolon with $<SEMICOLON> to avoid errors when passing into sbom_add.
string(REPLACE ";" "$<SEMICOLON>" relationships "${relationships}")
# Glue the relationships at generation time, because there some source file relationships
# will be conditional on genexes, and evaluate to an empty value, and we want to discard
# such relationships.
set(relationships "$<JOIN:${relationships},${glue}>")
set(relationship_option RELATIONSHIP "${relationships}")
_qt_internal_sbom_generate_add_file(
FILENAME "${installed_file_relative_path}"
FILETYPE "${file_type}" ${optional}
SPDXID "${spdx_id}"
${file_common_options}
${config_to_install_option}
${relationship_option}
)
endfunction()
# Maps an arbitrary file type to a spdx v2.3 file type.
# There is a list of known SPDX types, and custom Qt ones.
# Any other type is mapped to OTHER.
# The mapping might change when we start generating spdx v3.0 documents.
function(_qt_internal_sbom_get_spdx_v2_3_file_type_for_file out_var file_type_in)
set(spdx_v2_3_file_types
SOURCE
BINARY
ARCHIVE
APPLICATION
AUDIO
IMAGE
TEXT
VIDEO
DOCUMENTATION
SPDX
OTHER
)
# No semantic meaning at the moment, but we might want to map the values to something else
# when we port to SPDX v3.0+.
set(qt_file_types
QT_TRANSLATION
QT_TRANSLATIONS_CATALOG
QT_RESOURCE
TRANSLATION
RESOURCE
CUSTOM
)
if(file_type_in IN_LIST spdx_v2_3_file_types)
set(file_type "${file_type_in}")
elseif(file_type_in IN_LIST qt_file_types)
set(file_type OTHER)
else()
set(file_type OTHER)
endif()
set(${out_var} "${file_type}" PARENT_SCOPE)
endfunction()
# Takes a relative or absolute path and maps it to a reproducible path that is relative to
# the project source or build dir.
function(_qt_internal_sbom_map_path_to_reproducible_relative_path out_var)
set(opt_args "")
set(single_args
PATH
REPO_PROJECT_NAME_LOWERCASE
OUT_SUCCESS
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
set(is_in_source_dir FALSE)
set(is_in_build_dir FALSE)
if(NOT arg_REPO_PROJECT_NAME_LOWERCASE)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name)
else()
set(repo_project_name "${arg_REPO_PROJECT_NAME_LOWERCASE}")
endif()
if(NOT DEFINED arg_PATH)
message(FATAL_ERROR "PATH must be set")
endif()
set(path "${arg_PATH}")
set(handled FALSE)
if(path MATCHES "$<.+>")
# TODO: Paths wrapped in genexes are usually absolute paths that we also want to handle,
# but can't at configure time. We'll need a separate processing step at build time.
# Keep these as is for now, but signal failure of handling.
set(path_out "${path}")
else()
if(IS_ABSOLUTE "${path}")
set(path_in "${path}")
string(FIND "${path}" "${PROJECT_SOURCE_DIR}/" src_idx)
string(FIND "${path}" "${PROJECT_BINARY_DIR}/" dest_idx)
if(src_idx EQUAL "0")
set(is_in_source_dir TRUE)
elseif(dest_idx EQUAL "0")
set(is_in_build_dir TRUE)
endif()
else()
# We consider relative paths to be relative to the current source dir.
set(is_in_source_dir TRUE)
set(path_in "${CMAKE_CURRENT_SOURCE_DIR}/${path}")
endif()
# Resolve any .. and replace the absolute path with a path relative to the source dir
# or build dir, prefixed with a root marker.
get_filename_component(path_in_real "${path_in}" REALPATH)
# Replace the absolute prefixes with markers.
if(is_in_source_dir)
set(handled TRUE)
set(marker "/src_dir")
string(REPLACE "${PROJECT_SOURCE_DIR}/" "${marker}/${repo_project_name}/"
path_out "${path_in_real}")
elseif(is_in_build_dir)
set(handled TRUE)
set(marker "/build_dir")
string(REPLACE "${PROJECT_BINARY_DIR}/" "${marker}/${repo_project_name}/"
path_out "${path_in_real}")
else()
# If it's not a source dir or a build dir, it might be some kind of weird genex
# or marker that we don't handle yet.
set(path_out "${path_in_real}")
endif()
endif()
set(${out_var} "${path_out}" PARENT_SCOPE)
if(arg_OUT_SUCCESS)
set(${arg_OUT_SUCCESS} "${handled}" PARENT_SCOPE)
endif()
endfunction()
# Collect source file "generated from" relationship comments for a given target file.
function(_qt_internal_sbom_add_target_source_files target spdx_id out_relationships)
get_target_property(sources ${target} SOURCES)
if(NOT sources)
set(sources "")
endif()
list(REMOVE_DUPLICATES sources)
set(sources_option "")
if(sources)
set(sources_option SOURCES ${sources})
endif()
_qt_internal_sbom_add_source_files(
${sources_option}
SPDX_ID "${spdx_id}"
OUT_RELATIONSHIPS_VAR relationships
)
set(${out_relationships} "${relationships}" PARENT_SCOPE)
endfunction()
# Collect source file "generated from" relationship comments for the given sources.
function(_qt_internal_sbom_add_source_files)
set(opt_args "")
set(single_args
SPDX_ID
OUT_RELATIONSHIPS_VAR
)
set(multi_args
SOURCES
)
cmake_parse_arguments(PARSE_ARGV 0 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()
if(NOT arg_OUT_RELATIONSHIPS_VAR)
message(FATAL_ERROR "OUT_RELATIONSHIPS_VAR must be set")
endif()
if(NOT arg_SOURCES)
set(sources "")
else()
set(sources "${arg_SOURCES}")
endif()
set(relationships "")
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
foreach(source IN LISTS sources)
# Filter out $<TARGET_OBJECTS: genexes>.
if(source MATCHES "^\\$<TARGET_OBJECTS:.*>$")
continue()
endif()
# Filter out prl files.
if(source MATCHES "\.prl$")
continue()
endif()
# Filter out pkg-config. pc files.
if(source MATCHES "\.pc$")
continue()
endif()
# Filter out metatypes .json.gen files.
if(source MATCHES "\.json\.gen$")
continue()
endif()
_qt_internal_sbom_map_path_to_reproducible_relative_path(source_path
PATH "${source}"
REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
)
set(source_entry
"${spdx_id} GENERATED_FROM NOASSERTION\nRelationshipComment: ${source_path}"
)
set(source_non_empty "$<BOOL:${source_path}>")
# Some sources are conditional on genexes, so we evaluate them.
set(relationship "$<${source_non_empty}:$<GENEX_EVAL:${source_entry}>>")
list(APPEND relationships "${relationship}")
endforeach()
set(${arg_OUT_RELATIONSHIPS_VAR} "${relationships}" PARENT_SCOPE)
endfunction()
# Collects app bundle related information and paths from an executable's target properties.
# Output variables:
# <out_var>_name bundle base name, e.g. 'Linguist'.
# <out_var>_dir_name bundle dir name, e.g. 'Linguist.app'.
# <out_var>_contents_dir bundle contents dir, e.g. 'Linguist.app/Contents'
# <out_var>_contents_binary_dir bundle contents dir, e.g. 'Linguist.app/Contents/MacOS'
function(_qt_internal_get_executable_bundle_info out_var target)
get_target_property(target_type ${target} TYPE)
if(NOT "${target_type}" STREQUAL "EXECUTABLE")
message(FATAL_ERROR "The target ${target} is not an executable")
endif()
get_target_property(output_name ${target} OUTPUT_NAME)
if(NOT output_name)
set(output_name "${target}")
endif()
set(${out_var}_name "${output_name}")
set(${out_var}_dir_name "${${out_var}_name}.app")
set(${out_var}_contents_dir "${${out_var}_dir_name}/Contents")
set(${out_var}_contents_binary_dir "${${out_var}_contents_dir}/MacOS")
set(${out_var}_name "${${out_var}_name}" PARENT_SCOPE)
set(${out_var}_dir_name "${${out_var}_dir_name}" PARENT_SCOPE)
set(${out_var}_contents_dir "${${out_var}_contents_dir}" PARENT_SCOPE)
set(${out_var}_contents_binary_dir "${${out_var}_contents_binary_dir}" PARENT_SCOPE)
endfunction()
# Helper function to add binary file to the sbom, while handling multi-config and different
# kind of paths.
# In multi-config builds, we assume that the non-default config file will be optional, because it
# might not be installed (the case for debug tools and apps in debug-and-release builds).
function(_qt_internal_sbom_handle_multi_config_target_binary_file target)
set(opt_args "")
set(single_args
PATH_KIND
PATH_SUFFIX
)
set(multi_args
OPTIONS
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(configs ${CMAKE_CONFIGURATION_TYPES})
else()
set(configs "${CMAKE_BUILD_TYPE}")
endif()
foreach(config IN LISTS configs)
_qt_internal_sbom_get_and_check_multi_config_aware_single_arg_option(
arg "${arg_PATH_KIND}" "${config}" resolved_path)
_qt_internal_sbom_get_target_file_is_optional_in_multi_config("${config}" is_optional)
_qt_internal_path_join(file_path "${resolved_path}" "${arg_PATH_SUFFIX}")
_qt_internal_sbom_add_binary_file(
"${target}"
"${file_path}"
${arg_OPTIONS}
${is_optional}
CONFIG ${config}
)
endforeach()
endfunction()
# Helper to retrieve a list of multi-config aware option names that can be parsed by the
# file handling functions.
# For example in single config we need to parse RUNTIME_PATH, in multi-config we need to parse
# RUNTIME_PATH_DEBUG and RUNTIME_PATH_RELEASE.
#
# Result is cached in a global property.
function(_qt_internal_sbom_get_multi_config_single_args out_var)
get_cmake_property(single_args
_qt_internal_sbom_multi_config_single_args)
if(single_args)
set(${out_var} ${single_args} PARENT_SCOPE)
return()
endif()
set(single_args "")
set(single_args_to_process
INSTALL_PATH
RUNTIME_PATH
LIBRARY_PATH
ARCHIVE_PATH
FRAMEWORK_PATH
)
list(APPEND single_args "${single_args_to_process}")
# We need to process multi config args even in a single config build, because there might be API
# calls that specify them directly. Use a default set of multi config configs.
set(configs Release RelWithDebInfo MinSizeRel Debug)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config AND CMAKE_CONFIGURATION_TYPES)
list(APPEND configs ${CMAKE_CONFIGURATION_TYPES})
endif()
list(REMOVE_DUPLICATES configs)
foreach(config IN LISTS configs)
string(TOUPPER ${config} config_upper)
foreach(single_arg IN LISTS single_args_to_process)
list(APPEND single_args "${single_arg}_${config_upper}")
endforeach()
endforeach()
set_property(GLOBAL PROPERTY
_qt_internal_sbom_multi_config_single_args "${single_args}")
set(${out_var} ${single_args} PARENT_SCOPE)
endfunction()
# Helper to apped a an option and a value to a list of options, while being multi-config aware.
# It appends e.g. either RUNTIME_PATH foo or RUNTIME_PATH_DEBUG foo to the out_var_args variable.
function(_qt_internal_sbom_append_multi_config_aware_single_arg_option
arg_name arg_value config out_var_args)
set(values "${${out_var_args}}")
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
string(TOUPPER ${config} config_upper)
list(APPEND values "${arg_name}_${config_upper}" "${arg_value}")
else()
list(APPEND values "${arg_name}" "${arg_value}")
endif()
set(${out_var_args} "${values}" PARENT_SCOPE)
endfunction()
# Helper to check whether a given option was set in the outer scope, while being multi-config
# aware.
# It checks e.g. if either arg_RUNTIME_PATH or arg_RUNTIME_PATH_DEBUG is set in the outer scope.
function(_qt_internal_sbom_get_and_check_multi_config_aware_single_arg_option
arg_prefix arg_name config out_var)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
# Prefer the multi config option if it is set.
if(is_multi_config)
string(TOUPPER ${config} config_upper)
set(outer_scope_var_name "${arg_prefix}_${arg_name}_${config_upper}")
set(option_name "${arg_name}_${config_upper}")
endif()
if(NOT is_multi_config OR NOT DEFINED ${outer_scope_var_name})
set(outer_scope_var_name "${arg_prefix}_${arg_name}")
set(option_name "${arg_name}")
endif()
if(NOT DEFINED ${outer_scope_var_name})
message(FATAL_ERROR "Missing ${option_name}")
endif()
set(${out_var} "${${outer_scope_var_name}}" PARENT_SCOPE)
endfunction()
# Checks if given config is not the first config in a multi-config build, and thus file installation
# for that config should be optional.
function(_qt_internal_sbom_is_config_optional_in_multi_config config out_var)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(QT_MULTI_CONFIG_FIRST_CONFIG)
set(first_config_type "${QT_MULTI_CONFIG_FIRST_CONFIG}")
elseif(CMAKE_CONFIGURATION_TYPES)
list(GET CMAKE_CONFIGURATION_TYPES 0 first_config_type)
endif()
if(is_multi_config AND NOT (cmake_config STREQUAL first_config_type))
set(is_optional TRUE)
else()
set(is_optional FALSE)
endif()
set(${out_var} "${is_optional}" PARENT_SCOPE)
endfunction()
# Checks if given config is not the first config in a multi-config build, and thus file installation
# for that config should be optional, sets the actual option name.
function(_qt_internal_sbom_get_target_file_is_optional_in_multi_config config out_var)
_qt_internal_sbom_is_config_optional_in_multi_config("${config}" is_optional)
if(is_optional)
set(option "OPTIONAL")
else()
set(option "")
endif()
set(${out_var} "${option}" PARENT_SCOPE)
endfunction()
# Get a sanitized spdx id for a file.
# For consistency, we prefix the id with SPDXRef-PackagedFile-. This is not a requirement.
function(_qt_internal_sbom_get_file_spdx_id target out_var)
_qt_internal_sbom_get_sanitized_spdx_id(spdx_id "SPDXRef-PackagedFile-${target}")
set(${out_var} "${spdx_id}" PARENT_SCOPE)
endfunction()