# Copyright (C) 2024 The Qt Company Ltd.
# Copyright (C) 2023-2024 Jochem Rutgers
# SPDX-License-Identifier: MIT AND BSD-3-Clause
# Helper to set a single arg option to a default value if not set.
function(_qt_internal_sbom_set_default_option_value option_name default)
if(NOT arg_${option_name})
set(arg_${option_name} "${default}" PARENT_SCOPE)
endif()
endfunction()
# Helper to set a single arg option to a default value if not set.
# Errors out if the end value is empty. Including if the default value was empty.
function(_qt_internal_sbom_set_default_option_value_and_error_if_empty option_name default)
_qt_internal_sbom_set_default_option_value("${option_name}" "${default}")
if(NOT arg_${option_name})
message(FATAL_ERROR "Specifying a non-empty ${option_name} is required")
endif()
endfunction()
# Helper that returns the relative sbom build dir.
# To accommodate multiple projects within a qt repo (like qtwebengine), we need to choose separate
# build dirs for each project.
function(_qt_internal_get_current_project_sbom_relative_dir out_var)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
if(repo_project_name_lowercase STREQUAL real_qt_repo_project_name_lowercase)
set(sbom_dir "qt_sbom")
else()
set(sbom_dir "qt_sbom/${repo_project_name_lowercase}")
endif()
set(${out_var} "${sbom_dir}" PARENT_SCOPE)
endfunction()
# Helper that returns the directory where the intermediate sbom files will be generated.
function(_qt_internal_get_current_project_sbom_dir out_var)
_qt_internal_get_current_project_sbom_relative_dir(relative_dir)
set(sbom_dir "${PROJECT_BINARY_DIR}/${relative_dir}")
set(${out_var} "${sbom_dir}" PARENT_SCOPE)
endfunction()
# Helper to return the path to staging spdx file, where content will be incrementally appended to.
function(_qt_internal_get_staging_area_spdx_file_path out_var)
_qt_internal_get_current_project_sbom_dir(sbom_dir)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
set(staging_area_spdx_file "${sbom_dir}/staging-${repo_project_name_lowercase}.spdx.in")
set(${out_var} "${staging_area_spdx_file}" PARENT_SCOPE)
endfunction()
# Starts recording information for the generation of an sbom for a project.
# The intermediate files that generate the sbom are generated at cmake generation time, but are only
# actually run at build time or install time.
# The files are tracked in cmake global properties.
function(_qt_internal_sbom_begin_project_generate)
set(opt_args "")
set(single_args
OUTPUT
OUTPUT_RELATIVE_PATH
LICENSE
COPYRIGHT
DOWNLOAD_LOCATION
PROJECT
PROJECT_COMMENT
PROJECT_FOR_SPDX_ID
SUPPLIER
SUPPLIER_URL
NAMESPACE
CPE
OUT_VAR_PROJECT_SPDX_ID
)
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(QT_SBOM_FAKE_TIMESTAMP)
set(current_utc "2590-01-01T11:33:55Z")
set(current_year "2590")
else()
string(TIMESTAMP current_utc UTC)
string(TIMESTAMP current_year "%Y" UTC)
endif()
_qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
_qt_internal_sbom_get_git_version_vars()
set(default_sbom_file_name
"${arg_PROJECT}/${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.spdx")
set(default_install_sbom_path
"\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/${default_sbom_file_name}")
_qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
_qt_internal_sbom_set_default_option_value(OUTPUT_RELATIVE_PATH
"${default_sbom_file_name}")
_qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
_qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX_ID "Package-${arg_PROJECT}")
_qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
_qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
_qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
"${PROJECT_HOMEPAGE_URL}")
_qt_internal_sbom_set_default_option_value(NAMESPACE
"${arg_SUPPLIER}/spdxdocs/${arg_PROJECT}-${QT_SBOM_GIT_VERSION}")
if(arg_CPE)
set(QT_SBOM_CPE "${arg_CPE}")
else()
set(QT_SBOM_CPE "")
endif()
string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
# Prevent collision with other generated SPDXID with -[0-9]+ suffix.
string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
if(arg_OUT_VAR_PROJECT_SPDX_ID)
set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
endif()
get_filename_component(doc_name "${arg_OUTPUT}" NAME_WLE)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
else()
set(cmake_configs "${CMAKE_BUILD_TYPE}")
endif()
_qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
set(cmake_version "Built by CMake ${CMAKE_VERSION}")
set(system_name_and_processor "${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})")
set(default_project_comment
"${cmake_version} with ${cmake_configs} configuration for ${system_name_and_processor}")
set(project_comment "${default_project_comment}")
if(arg_PROJECT_COMMENT)
string(APPEND project_comment "${arg_PROJECT_COMMENT}")
endif()
set(project_comment "${project_comment}")
set(content
"SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: ${doc_name}
DocumentNamespace: ${arg_NAMESPACE}
Creator: Organization: ${arg_SUPPLIER}
Creator: Tool: Qt Build System
CreatorComment: This SPDX document was created from CMake ${CMAKE_VERSION}, using the qt
build system from https://code.qt.io/cgit/qt/qtbase.git/tree/cmake/QtPublicSbomHelpers.cmake
Created: ${current_utc}\${QT_SBOM_EXTERNAL_DOC_REFS}
PackageName: ${CMAKE_CXX_COMPILER_ID}
SPDXID: SPDXRef-compiler
PackageVersion: ${CMAKE_CXX_COMPILER_VERSION}
PackageDownloadLocation: NOASSERTION
PackageLicenseConcluded: NOASSERTION
PackageLicenseDeclared: NOASSERTION
PackageCopyrightText: NOASSERTION
PackageSupplier: Organization: Anonymous
FilesAnalyzed: false
PackageSummary: The compiler as identified by CMake, running on ${CMAKE_HOST_SYSTEM_NAME} (${CMAKE_HOST_SYSTEM_PROCESSOR})
PrimaryPackagePurpose: APPLICATION
Relationship: SPDXRef-compiler BUILD_DEPENDENCY_OF ${project_spdx_id}
RelationshipComment: ${project_spdx_id} is built by compiler ${CMAKE_CXX_COMPILER_ID} version ${CMAKE_CXX_COMPILER_VERSION}
PackageName: ${arg_PROJECT}
SPDXID: ${project_spdx_id}
ExternalRef: SECURITY cpe23Type ${QT_SBOM_CPE}
ExternalRef: PACKAGE-MANAGER purl pkg:generic/${arg_SUPPLIER}/${arg_PROJECT}@${QT_SBOM_GIT_VERSION}
PackageVersion: ${QT_SBOM_GIT_VERSION}
PackageSupplier: Organization: ${arg_SUPPLIER}
PackageDownloadLocation: ${arg_DOWNLOAD_LOCATION}
PackageLicenseConcluded: ${arg_LICENSE}
PackageLicenseDeclared: ${arg_LICENSE}
PackageCopyrightText: ${arg_COPYRIGHT}
PackageHomePage: ${arg_SUPPLIER_URL}
PackageComment: ${project_comment}
PackageVerificationCode: \${QT_SBOM_VERIFICATION_CODE}
BuiltDate: ${current_utc}
Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
")
# Create the directory that will contain all sbom related files.
_qt_internal_get_current_project_sbom_dir(sbom_dir)
file(MAKE_DIRECTORY "${sbom_dir}")
set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
# Generate project document intro spdx file.
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
set(document_intro_file_name
"${sbom_dir}/SPDXRef-DOCUMENT-${repo_project_name_lowercase}.spdx.in")
file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
# This is the file that will be incrementally assembled by having content appended to it.
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
get_filename_component(output_file_name_without_ext "${arg_OUTPUT}" NAME_WLE)
get_filename_component(output_file_ext "${arg_OUTPUT}" LAST_EXT)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(multi_config_suffix "-$")
else()
set(multi_config_suffix "")
endif()
set(computed_sbom_file_name_without_ext "${output_file_name_without_ext}${multi_config_suffix}")
set(computed_sbom_file_name "${output_file_name_without_ext}${output_file_ext}")
# In a super build and in a no-prefix build, put all the build time sboms into the same dir in,
# in the qtbase build dir.
if(QT_BUILDING_QT AND (QT_SUPERBUILD OR (NOT QT_WILL_INSTALL)))
set(build_sbom_root_dir "${QT_BUILD_DIR}")
else()
set(build_sbom_root_dir "${sbom_dir}")
endif()
get_filename_component(output_relative_dir "${arg_OUTPUT_RELATIVE_PATH}" DIRECTORY)
set(build_sbom_dir "${build_sbom_root_dir}/${output_relative_dir}")
set(build_sbom_path "${build_sbom_dir}/${computed_sbom_file_name}")
set(build_sbom_path_without_ext
"${build_sbom_dir}/${computed_sbom_file_name_without_ext}")
set(install_sbom_path "${arg_OUTPUT}")
get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
# Create cmake file to append the document intro spdx to the staging file.
set(create_staging_file "${sbom_dir}/append_document_to_staging${multi_config_suffix}.cmake")
set(content "
cmake_minimum_required(VERSION 3.16)
message(STATUS \"Starting SBOM generation in build dir: ${staging_area_spdx_file}\")
set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
file(READ \"${document_intro_file_name}\" content)
# Override any previous file because we're starting from scratch.
file(WRITE \"${staging_area_spdx_file}\" \"\${content}\")
")
file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
set_property(GLOBAL PROPERTY _qt_sbom_project_supplier "${arg_SUPPLIER}")
set_property(GLOBAL PROPERTY _qt_sbom_project_supplier_url "${arg_SUPPLIER_URL}")
set_property(GLOBAL PROPERTY _qt_sbom_project_namespace "${arg_NAMESPACE}")
set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${project_spdx_id}")
set_property(GLOBAL PROPERTY _qt_sbom_build_output_path "${build_sbom_path}")
set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext
"${build_sbom_path_without_ext}")
set_property(GLOBAL PROPERTY _qt_sbom_build_output_dir "${build_sbom_dir}")
set_property(GLOBAL PROPERTY _qt_sbom_install_output_path "${install_sbom_path}")
set_property(GLOBAL PROPERTY _qt_sbom_install_output_path_without_ext
"${install_sbom_path_without_ext}")
set_property(GLOBAL PROPERTY _qt_sbom_install_output_dir "${install_sbom_dir}")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${create_staging_file}")
set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count 0)
set_property(GLOBAL PROPERTY _qt_sbom_relationship_counter 0)
endfunction()
# Signals the end of recording sbom information for a project.
# Creates an 'sbom' custom target to generate an incomplete sbom at build time (no checksums).
# Creates install rules to install a complete (with checksums) sbom.
function(_qt_internal_sbom_end_project_generate)
get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path)
get_property(sbom_build_output_path_without_ext GLOBAL PROPERTY
_qt_sbom_build_output_path_without_ext)
get_property(sbom_build_output_dir GLOBAL PROPERTY _qt_sbom_build_output_dir)
get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path)
get_property(sbom_install_output_path_without_ext GLOBAL PROPERTY
_qt_sbom_install_output_path_without_ext)
get_property(sbom_install_output_dir GLOBAL PROPERTY _qt_sbom_install_output_dir)
if(NOT sbom_build_output_path)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
_qt_internal_sbom_collect_cmake_include_files(includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_include_files _qt_sbom_cmake_end_include_files
)
# Before checksum includes are included after the verification codes have been collected
# and before their merged checksum(s) has been computed.
_qt_internal_sbom_collect_cmake_include_files(before_checksum_includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_before_checksum_include_files
)
# After checksum includes are included after the checksum has been computed and written to the
# QT_SBOM_VERIFICATION_CODE variable.
_qt_internal_sbom_collect_cmake_include_files(after_checksum_includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_after_checksum_include_files
)
# Post generation includes are included for both build and install time sboms, after
# sbom generation has finished.
_qt_internal_sbom_collect_cmake_include_files(post_generation_includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_post_generation_include_files
)
# Verification only makes sense on installation, where the checksums are present.
_qt_internal_sbom_collect_cmake_include_files(verify_includes
JOIN_WITH_NEWLINES
PROPERTIES _qt_sbom_cmake_verify_include_files
)
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(multi_config_suffix "-$")
else()
set(multi_config_suffix "")
endif()
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(content "
# QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
# This allows reusing the same cmake file for both build and install.
if(NOT DEFINED QT_SBOM_BUILD_TIME)
set(QT_SBOM_BUILD_TIME TRUE)
endif()
if(NOT QT_SBOM_OUTPUT_PATH)
set(QT_SBOM_OUTPUT_DIR \"${sbom_build_output_dir}\")
set(QT_SBOM_OUTPUT_PATH \"${sbom_build_output_path}\")
set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_build_output_path_without_ext}\")
file(MAKE_DIRECTORY \"${sbom_build_output_dir}\")
endif()
set(QT_SBOM_VERIFICATION_CODES \"\")
${includes}
if(QT_SBOM_BUILD_TIME)
message(STATUS \"Finalizing SBOM generation in build dir: \${QT_SBOM_OUTPUT_PATH}\")
configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
${post_generation_includes}
endif()
")
set(assemble_sbom "${sbom_dir}/assemble_sbom${multi_config_suffix}.cmake")
file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
if(NOT TARGET sbom)
add_custom_target(sbom)
endif()
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
_qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
# Create a build target to create a build-time sbom (no verification codes or sha1s).
set(repo_sbom_target "sbom_${repo_project_name_lowercase}")
set(comment "")
string(APPEND comment "Assembling build time SPDX document without checksums for "
"${repo_project_name_lowercase}. Just for testing.")
add_custom_target(${repo_sbom_target}
COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
COMMENT "${comment}"
VERBATIM
USES_TERMINAL # To avoid running two configs of the command in parallel
)
get_cmake_property(qt_repo_deps _qt_repo_deps_${real_qt_repo_project_name_lowercase})
if(qt_repo_deps)
foreach(repo_dep IN LISTS qt_repo_deps)
set(repo_dep_sbom "sbom_${repo_dep}")
if(TARGET "${repo_dep_sbom}")
add_dependencies(${repo_sbom_target} ${repo_dep_sbom})
endif()
endforeach()
endif()
add_dependencies(sbom ${repo_sbom_target})
# Add 'reuse lint' per-repo custom targets.
if(arg_LINT_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
if(NOT TARGET reuse_lint)
add_custom_target(reuse_lint)
endif()
set(comment "Running 'reuse lint' for '${repo_project_name_lowercase}'.")
add_custom_target(${repo_sbom_target}_reuse_lint
COMMAND "${CMAKE_COMMAND}" -P "${reuse_lint_script}"
COMMENT "${comment}"
VERBATIM
USES_TERMINAL # To avoid running multiple lints in parallel
)
add_dependencies(reuse_lint ${repo_sbom_target}_reuse_lint)
endif()
set(extra_code_begin "")
set(extra_code_inner_end "")
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(configs ${CMAKE_CONFIGURATION_TYPES})
set(install_markers_dir "${sbom_dir}")
set(install_marker_path "${install_markers_dir}/finished_install-$.cmake")
set(install_marker_code "
message(STATUS \"Writing install marker for config $: ${install_marker_path} \")
file(WRITE \"${install_marker_path}\" \"\")
")
install(CODE "${install_marker_code}" COMPONENT sbom)
if(QT_SUPERBUILD)
install(CODE "${install_marker_code}" COMPONENT "sbom_${repo_project_name_lowercase}"
EXCLUDE_FROM_ALL)
endif()
set(install_markers "")
foreach(config IN LISTS configs)
set(marker_path "${install_markers_dir}/finished_install-${config}.cmake")
list(APPEND install_markers "${marker_path}")
# Remove the markers on reconfiguration, just in case there are stale ones.
if(EXISTS "${marker_path}")
file(REMOVE "${marker_path}")
endif()
endforeach()
set(extra_code_begin "
set(QT_SBOM_INSTALL_MARKERS \"${install_markers}\")
foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
set(QT_SBOM_INSTALLED_ALL_CONFIGS FALSE)
endif()
endforeach()
")
set(extra_code_inner_end "
foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
message(STATUS
\"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
endforeach()
")
endif()
# Allow skipping checksum computation for testing purposes, while installing just the sbom
# documents, without requiring to build and install all the actual files.
if(QT_SBOM_FAKE_CHECKSUM)
string(APPEND extra_code_begin "
set(QT_SBOM_FAKE_CHECKSUM TRUE)")
endif()
set(assemble_sbom_install "
set(QT_SBOM_INSTALLED_ALL_CONFIGS TRUE)
${extra_code_begin}
if(QT_SBOM_INSTALLED_ALL_CONFIGS)
set(QT_SBOM_BUILD_TIME FALSE)
set(QT_SBOM_OUTPUT_DIR \"${sbom_install_output_dir}\")
set(QT_SBOM_OUTPUT_PATH \"${sbom_install_output_path}\")
set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_install_output_path_without_ext}\")
file(MAKE_DIRECTORY \"${sbom_install_output_dir}\")
include(\"${assemble_sbom}\")
${before_checksum_includes}
list(SORT QT_SBOM_VERIFICATION_CODES)
string(REPLACE \";\" \"\" QT_SBOM_VERIFICATION_CODES \"\${QT_SBOM_VERIFICATION_CODES}\")
file(WRITE \"${sbom_dir}/verification.txt\" \"\${QT_SBOM_VERIFICATION_CODES}\")
file(SHA1 \"${sbom_dir}/verification.txt\" QT_SBOM_VERIFICATION_CODE)
${after_checksum_includes}
message(STATUS \"Finalizing SBOM generation in install dir: \${QT_SBOM_OUTPUT_PATH}\")
configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
${post_generation_includes}
${verify_includes}
${extra_code_inner_end}
else()
message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
endif()
")
install(CODE "${assemble_sbom_install}" COMPONENT sbom)
if(QT_SUPERBUILD)
install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${repo_project_name_lowercase}"
EXCLUDE_FROM_ALL)
endif()
# Clean up properties, so that they are empty for possible next repo in a top-level build.
set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files "")
set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files "")
set_property(GLOBAL PROPERTY _qt_sbom_cmake_before_checksum_include_files "")
set_property(GLOBAL PROPERTY _qt_sbom_cmake_after_checksum_include_files "")
set_property(GLOBAL PROPERTY _qt_sbom_cmake_post_generation_include_files "")
set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files "")
endfunction()
# Gets a list of cmake include file paths, joins them as include() statements and returns the
# output.
function(_qt_internal_sbom_collect_cmake_include_files out_var)
set(opt_args
JOIN_WITH_NEWLINES
)
set(single_args "")
set(multi_args
PROPERTIES
)
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
if(NOT arg_PROPERTIES)
message(FATAL_ERROR "PROPERTIES is required")
endif()
set(includes "")
foreach(property IN LISTS arg_PROPERTIES)
get_cmake_property(include_files "${property}")
if(include_files)
foreach(include_file IN LISTS include_files)
list(APPEND includes "include(\"${include_file}\")")
endforeach()
endif()
endforeach()
if(arg_JOIN_WITH_NEWLINES)
list(JOIN includes "\n" includes)
endif()
set(${out_var} "${includes}" PARENT_SCOPE)
endfunction()
# Helper to add info about a file to the sbom.
# Targets are backed by multiple files in multi-config builds. To support multi-config,
# we generate a -$ file for each config, but we only include / install the one that is
# specified via the CONFIG option.
# For build time sboms, we skip checking file existence and sha1 computation, because the files
# are not installed yet.
function(_qt_internal_sbom_generate_add_file)
set(opt_args
OPTIONAL
)
set(single_args
FILENAME
FILETYPE
RELATIONSHIP
SPDXID
CONFIG
LICENSE
COPYRIGHT
INSTALL_PREFIX
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(FILENAME "")
_qt_internal_sbom_set_default_option_value_and_error_if_empty(FILETYPE "")
set(check_option "")
if(arg_SPDXID)
set(check_option "CHECK" "${arg_SPDXID}")
endif()
_qt_internal_sbom_get_and_check_spdx_id(
VARIABLE arg_SPDXID
${check_option}
HINTS "SPDXRef-${arg_FILENAME}"
)
_qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
_qt_internal_sbom_set_default_option_value(COPYRIGHT "NOASSERTION")
get_property(sbom_project_spdx_id GLOBAL PROPERTY _qt_sbom_project_spdx_id)
if(NOT sbom_project_spdx_id)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
if(NOT arg_RELATIONSHIP)
set(arg_RELATIONSHIP "${sbom_project_spdx_id} CONTAINS ${arg_SPDXID}")
else()
string(REPLACE
"@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
endif()
set(fields "")
if(arg_LICENSE)
set(fields "${fields}
LicenseConcluded: ${arg_LICENSE}"
)
else()
set(fields "${fields}
LicenseConcluded: NOASSERTION"
)
endif()
if(arg_COPYRIGHT)
set(fields "${fields}
FileCopyrightText: ${arg_COPYRIGHT}"
)
else()
set(fields "${fields}
FileCopyrightText: NOASSERTION"
)
endif()
set(file_suffix_to_generate "")
get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
if(is_multi_config)
set(file_suffix_to_generate "-$")
endif()
if(arg_CONFIG)
set(file_suffix_to_install "-${arg_CONFIG}")
else()
set(file_suffix_to_install "")
endif()
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
if(arg_INSTALL_PREFIX)
set(install_prefix "${arg_INSTALL_PREFIX}")
else()
# The variable is escaped, so it is evaluated during cmake install time, so that the value
# can be overridden with cmake --install . --prefix .
set(install_prefix "\${CMAKE_INSTALL_PREFIX}")
endif()
set(content "
if(NOT EXISTS \"\$ENV{DESTDIR}${install_prefix}/${arg_FILENAME}\"
AND NOT QT_SBOM_BUILD_TIME AND NOT QT_SBOM_FAKE_CHECKSUM)
if(NOT ${arg_OPTIONAL})
message(FATAL_ERROR \"Cannot find '${arg_FILENAME}' to compute its checksum. \"
\"Expected to find it at '\$ENV{DESTDIR}${install_prefix}/${arg_FILENAME}' \")
endif()
else()
if(NOT QT_SBOM_BUILD_TIME)
if(QT_SBOM_FAKE_CHECKSUM)
set(sha1 \"158942a783ee1095eafacaffd93de73edeadbeef\")
else()
file(SHA1 \"\$ENV{DESTDIR}${install_prefix}/${arg_FILENAME}\" sha1)
endif()
list(APPEND QT_SBOM_VERIFICATION_CODES \${sha1})
endif()
file(APPEND \"${staging_area_spdx_file}\"
\"
FileName: ./${arg_FILENAME}
SPDXID: ${arg_SPDXID}
FileType: ${arg_FILETYPE}
FileChecksum: SHA1: \${sha1}${fields}
LicenseInfoInFile: NOASSERTION
Relationship: ${arg_RELATIONSHIP}
\"
)
endif()
")
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(file_sbom "${sbom_dir}/${arg_SPDXID}${file_suffix_to_generate}.cmake")
file(GENERATE OUTPUT "${file_sbom}" CONTENT "${content}")
set(file_sbom_to_install "${sbom_dir}/${arg_SPDXID}${file_suffix_to_install}.cmake")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${file_sbom_to_install}")
endfunction()
# Helper to add a reference to an external SPDX document.
#
# EXTERNAL_DOCUMENT_SPDX_ID: The spdx id by which the external document should be referenced in
# the current project SPDX document. This semantically serves as a pointer to the external document
# URI.
# e.g. DocumentRef-qtbase.
#
# EXTERNAL_DOCUMENT_FILE_PATH: The relative file path of the external sbom document.
# e.g. "sbom/qtbase-6.9.0.spdx"
#
# Can contain generator expressions.
# The file path is searched for in the directories specified by the
# EXTERNAL_DOCUMENT_INSTALL_PREFIXES option during sbom generation, to compute the file checksum.
# The file path is NOT embedded into the current project spdx document.
# Only its spdx id and namespace is embedded. The namespace is extracted from the contents of the
# referenced file.
#
# EXTERNAL_DOCUMENT_INSTALL_PREFIXES: A list of directories where the external document file path
# is searched for. The first existing file is used. Additionally the following locations are
# searched:
# - QT6_INSTALL_PREFIX
# - QT_ADDITIONAL_PACKAGES_PREFIX_PATH
# - QT_ADDITIONAL_SBOM_DOCUMENT_PATHS
#
# EXTERNAL_PACKAGE_SPDX_ID: If set, and no RELATIONSHIP_STRING is provided, an automatic DEPENDS_ON
# relationship is added from the current project spdx id to the package identified by
# $EXTERNAL_DOCUMENT_SPDX_ID:$EXTERNAL_PACKAGE_SPDX_ID options.
# This is mostly a beginner convenience.
#
# RELATIONSHIP_STRING: If set, it is used as the relationship string to add to the current project
# relationships.
function(_qt_internal_sbom_generate_add_external_reference)
if(NOT QT_GENERATE_SBOM)
return()
endif()
set(opt_args "")
set(single_args
EXTERNAL_DOCUMENT_FILE_PATH
EXTERNAL_DOCUMENT_SPDX_ID
EXTERNAL_PACKAGE_SPDX_ID
RELATIONSHIP_STRING
)
set(multi_args
EXTERNAL_DOCUMENT_INSTALL_PREFIXES
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(EXTERNAL_DOCUMENT_FILE_PATH "")
if(NOT arg_EXTERNAL_DOCUMENT_SPDX_ID)
get_property(spdx_id_count GLOBAL PROPERTY _qt_sbom_spdx_id_count)
set(arg_EXTERNAL_DOCUMENT_SPDX_ID "DocumentRef-${spdx_id_count}")
math(EXPR spdx_id_count "${spdx_id_count} + 1")
set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count "${spdx_id_count}")
elseif(NOT "${arg_EXTERNAL_DOCUMENT_SPDX_ID}" MATCHES
"^DocumentRef-[a-zA-Z0-9]+[-a-zA-Z0-9]+$")
message(FATAL_ERROR "Invalid DocumentRef \"${arg_EXTERNAL_DOCUMENT_SPDX_ID}\"")
endif()
get_property(sbom_project_spdx_id GLOBAL PROPERTY _qt_sbom_project_spdx_id)
if(NOT sbom_project_spdx_id)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
if(arg_RELATIONSHIP_STRING STREQUAL "")
if(arg_EXTERNAL_PACKAGE_SPDX_ID)
set(external_package "${arg_EXTERNAL_DOCUMENT_SPDX_ID}:${arg_EXTERNAL_PACKAGE_SPDX_ID}")
set(arg_RELATIONSHIP_STRING
"${sbom_project_spdx_id} DEPENDS_ON ${external_package}")
endif()
else()
string(REPLACE
"@QT_SBOM_LAST_SPDXID@" "${arg_EXTERNAL_DOCUMENT_SPDX_ID}"
arg_RELATIONSHIP_STRING "${arg_RELATIONSHIP_STRING}")
endif()
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
set(install_prefixes "")
# Add the current sbom build dirs as install prefixes, so that we can use ninja 'sbom'
# in top-level builds. This is needed because the external references will point
# to sbom docs in different build dirs, not just one.
# We also need it in case we are converting a json document to a tag/value format in the
# current build dir of the project, and want it to be found.
get_cmake_property(build_sbom_dirs _qt_internal_sbom_dirs)
if(build_sbom_dirs)
foreach(build_sbom_dir IN LISTS build_sbom_dirs)
list(APPEND install_prefixes "${build_sbom_dir}")
endforeach()
endif()
# Always append the install time install prefix.
# The variable is escaped, so it is evaluated during cmake install time, so that the value
# can be overridden with cmake --install . --prefix .
list(APPEND install_prefixes "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}")
if(arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES)
list(APPEND install_prefixes ${arg_EXTERNAL_DOCUMENT_INSTALL_PREFIXES})
endif()
if(QT6_INSTALL_PREFIX)
list(APPEND install_prefixes ${QT6_INSTALL_PREFIX})
endif()
if(QT_ADDITIONAL_PACKAGES_PREFIX_PATH)
list(APPEND install_prefixes ${QT_ADDITIONAL_PACKAGES_PREFIX_PATH})
endif()
if(QT_ADDITIONAL_SBOM_DOCUMENT_PATHS)
list(APPEND install_prefixes ${QT_ADDITIONAL_SBOM_DOCUMENT_PATHS})
endif()
list(REMOVE_DUPLICATES install_prefixes)
set(relationship_content "")
if(arg_RELATIONSHIP_STRING)
set(relationship_content "
file(APPEND \"${staging_area_spdx_file}\"
\"Relationship: ${arg_RELATIONSHIP_STRING}\")
")
endif()
# File path may not exist yet, and it could be a generator expression.
set(content "
set(relative_file_name \"${arg_EXTERNAL_DOCUMENT_FILE_PATH}\")
set(document_dir_paths ${install_prefixes})
list(JOIN document_dir_paths \"\\n\" document_dir_paths_per_line)
foreach(document_dir_path IN LISTS document_dir_paths)
set(document_file_path \"\${document_dir_path}/\${relative_file_name}\")
if(EXISTS \"\${document_file_path}\")
break()
endif()
endforeach()
if(NOT EXISTS \"\${document_file_path}\")
message(FATAL_ERROR \"Could not find external SBOM document \${relative_file_name}\"
\" in any of the document dir paths: \${document_dir_paths_per_line} \"
)
endif()
file(SHA1 \"\${document_file_path}\" ext_sha1)
file(READ \"\${document_file_path}\" ext_content)
if(NOT \"\${ext_content}\" MATCHES \"[\\r\\n]DocumentNamespace:\")
message(FATAL_ERROR \"Missing DocumentNamespace in \${document_file_path}\")
endif()
string(REGEX REPLACE \"^.*[\\r\\n]DocumentNamespace:[ \\t]*([^#\\r\\n]*).*$\"
\"\\\\1\" ext_ns \"\${ext_content}\")
string(APPEND QT_SBOM_EXTERNAL_DOC_REFS \"
ExternalDocumentRef: ${arg_EXTERNAL_DOCUMENT_SPDX_ID} \${ext_ns} SHA1: \${ext_sha1}\")
${relationship_content}
")
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(ext_ref_sbom "${sbom_dir}/${arg_EXTERNAL_DOCUMENT_SPDX_ID}.cmake")
file(GENERATE OUTPUT "${ext_ref_sbom}" CONTENT "${content}")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files "${ext_ref_sbom}")
endfunction()
# Helper to add info about a package to the sbom. Usually a package is a mapping to a cmake target.
function(_qt_internal_sbom_generate_add_package)
set(opt_args
CONTAINS_FILES
)
set(single_args
PACKAGE
VERSION
LICENSE_DECLARED
LICENSE_CONCLUDED
COPYRIGHT
DOWNLOAD_LOCATION
RELATIONSHIP
SPDXID
SUPPLIER
PURPOSE
COMMENT
)
set(multi_args
EXTREF
CPE
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(PACKAGE "")
set(check_option "")
if(arg_SPDXID)
set(check_option "CHECK" "${arg_SPDXID}")
endif()
_qt_internal_sbom_get_and_check_spdx_id(
VARIABLE arg_SPDXID
${check_option}
HINTS "SPDXRef-${arg_PACKAGE}"
)
_qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
_qt_internal_sbom_set_default_option_value(VERSION "unknown")
_qt_internal_sbom_set_default_option_value(SUPPLIER "Person: Anonymous")
_qt_internal_sbom_set_default_option_value(LICENSE_DECLARED "NOASSERTION")
_qt_internal_sbom_set_default_option_value(LICENSE_CONCLUDED "NOASSERTION")
_qt_internal_sbom_set_default_option_value(COPYRIGHT "NOASSERTION")
_qt_internal_sbom_set_default_option_value(PURPOSE "OTHER")
set(fields "")
if(arg_LICENSE_CONCLUDED)
set(fields "${fields}
PackageLicenseConcluded: ${arg_LICENSE_CONCLUDED}"
)
else()
set(fields "${fields}
PackageLicenseConcluded: NOASSERTION"
)
endif()
if(arg_LICENSE_DECLARED)
set(fields "${fields}
PackageLicenseDeclared: ${arg_LICENSE_DECLARED}"
)
else()
set(fields "${fields}
PackageLicenseDeclared: NOASSERTION"
)
endif()
foreach(ext_ref IN LISTS arg_EXTREF)
set(fields "${fields}
ExternalRef: ${ext_ref}"
)
endforeach()
if(arg_CONTAINS_FILES)
set(fields "${fields}
FilesAnalyzed: true"
)
else()
set(fields "${fields}
FilesAnalyzed: false"
)
endif()
if(arg_COPYRIGHT)
set(fields "${fields}
PackageCopyrightText: ${arg_COPYRIGHT}"
)
else()
set(fields "${fields}
PackageCopyrightText: NOASSERTION"
)
endif()
if(arg_PURPOSE)
set(fields "${fields}
PrimaryPackagePurpose: ${arg_PURPOSE}"
)
else()
set(fields "${fields}
PrimaryPackagePurpose: OTHER"
)
endif()
if(arg_COMMENT)
set(fields "${fields}
PackageComment: ${arg_COMMENT}"
)
endif()
foreach(cpe IN LISTS arg_CPE)
set(fields "${fields}
ExternalRef: SECURITY cpe23Type ${cpe}"
)
endforeach()
get_property(sbom_project_spdx_id GLOBAL PROPERTY _qt_sbom_project_spdx_id)
if(NOT sbom_project_spdx_id)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
if(NOT arg_RELATIONSHIP)
set(arg_RELATIONSHIP "${sbom_project_spdx_id} CONTAINS ${arg_SPDXID}")
else()
string(REPLACE "@QT_SBOM_LAST_SPDXID@" "${arg_SPDXID}" arg_RELATIONSHIP "${arg_RELATIONSHIP}")
endif()
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
set(content "
file(APPEND \"${staging_area_spdx_file}\"
\"
PackageName: ${arg_PACKAGE}
SPDXID: ${arg_SPDXID}
PackageDownloadLocation: ${arg_DOWNLOAD_LOCATION}
PackageVersion: ${arg_VERSION}
PackageSupplier: ${arg_SUPPLIER}${fields}
Relationship: ${arg_RELATIONSHIP}
\"
)
")
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(package_sbom "${sbom_dir}/${arg_SPDXID}.cmake")
file(GENERATE OUTPUT "${package_sbom}" CONTENT "${content}")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${package_sbom}")
endfunction()
# Helper to add relationship entries to the current project SBOM document package.
#
# RELATIONSHIPS: A list of relationship strings to add to the current project relationships.
#
# Care must be taken to call the function right after project creation, before other targets are
# created, otherwise the relationship strings might be added to the wrong package.
# It doesn't seem to cause tooling to fail, but it's something to look out for.
function(_qt_internal_sbom_generate_add_project_relationship)
if(NOT QT_GENERATE_SBOM)
return()
endif()
set(opt_args "")
set(single_args "")
set(multi_args
RELATIONSHIPS
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(RELATIONSHIPS "")
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
get_property(counter GLOBAL PROPERTY _qt_sbom_relationship_counter)
set(current_counter "${counter}")
math(EXPR counter "${counter} + 1")
set_property(GLOBAL PROPERTY _qt_sbom_relationship_counter "${counter}")
set(relationships "${arg_RELATIONSHIPS}")
list(REMOVE_DUPLICATES relationships)
list(JOIN relationships "\nRelationship: " relationships)
set(content "
# Custom relationship index: ${current_counter}
file(APPEND \"${staging_area_spdx_file}\"
\"
Relationship: ${relationships}\")
")
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(ext_ref_sbom "${sbom_dir}/relationship_${counter}.cmake")
file(GENERATE OUTPUT "${ext_ref_sbom}" CONTENT "${content}")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${ext_ref_sbom}")
endfunction()
# Adds a cmake include file to the sbom generation process at a specific step.
# INCLUDE_PATH - path to the cmake file to include.
# STEP - one of
# BEGIN
# END
# POST_GENERATION
# VERIFY
#
# BEGIN includes are included after the spdx staging file is created.
#
# END includes are included after the all the BEGIN ones are included.
#
# BEFORE_CHECKSUM includes are included after the verification codes have been collected
# and before their merged checksum(s) has been computed. Only relevant for install time sboms.
#
# AFTER_CHECKSUM includes are included after the checksum has been computed and written to the
# QT_SBOM_VERIFICATION_CODE variable. Only relevant for install time sboms.
#
# POST_GENERATION includes are included for both build and install time sboms, after
# sbom generation has finished.
# Currently used for adding reuse lint and reuse source steps, before VERIFY include are run.
#
# VERIFY includes only make sense on installation, where the checksums are present, so they are
# only included during install time.
# Used for generating a json sbom from the tag value one, for running ntia compliance check, etc.
function(_qt_internal_sbom_add_cmake_include_step)
set(opt_args "")
set(single_args
STEP
INCLUDE_PATH
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
if(NOT arg_STEP)
message(FATAL_ERROR "STEP is required")
endif()
if(NOT arg_INCLUDE_PATH)
message(FATAL_ERROR "INCLUDE_PATH is required")
endif()
set(step "${arg_STEP}")
if(step STREQUAL "BEGIN")
set(property "_qt_sbom_cmake_include_files")
elseif(step STREQUAL "END")
set(property "_qt_sbom_cmake_end_include_files")
elseif(step STREQUAL "BEFORE_CHECKSUM")
set(property "_qt_sbom_cmake_before_checksum_include_files")
elseif(step STREQUAL "AFTER_CHECKSUM")
set(property "_qt_sbom_cmake_after_checksum_include_files")
elseif(step STREQUAL "POST_GENERATION")
set(property "_qt_sbom_cmake_post_generation_include_files")
elseif(step STREQUAL "VERIFY")
set(property "_qt_sbom_cmake_verify_include_files")
else()
message(FATAL_ERROR "Invalid SBOM cmake include step name: ${step}")
endif()
set_property(GLOBAL APPEND PROPERTY "${property}" "${arg_INCLUDE_PATH}")
endfunction()
# Helper to add a license text from a file or text into the sbom document.
function(_qt_internal_sbom_generate_add_license)
set(opt_args "")
set(single_args
LICENSE_ID
EXTRACTED_TEXT
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(LICENSE_ID "")
set(check_option "")
if(arg_SPDXID)
set(check_option "CHECK" "${arg_SPDXID}")
endif()
_qt_internal_sbom_get_and_check_spdx_id(
VARIABLE arg_SPDXID
${check_option}
HINTS "SPDXRef-${arg_LICENSE_ID}"
)
if(NOT arg_EXTRACTED_TEXT)
set(licenses_dir "${PROJECT_SOURCE_DIR}/LICENSES")
file(READ "${licenses_dir}/${arg_LICENSE_ID}.txt" arg_EXTRACTED_TEXT)
string(PREPEND arg_EXTRACTED_TEXT "")
string(APPEND arg_EXTRACTED_TEXT "")
endif()
_qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
set(content "
file(APPEND \"${staging_area_spdx_file}\"
\"
LicenseID: ${arg_LICENSE_ID}
ExtractedText: ${arg_EXTRACTED_TEXT}
\"
)
")
_qt_internal_get_current_project_sbom_dir(sbom_dir)
set(license_sbom "${sbom_dir}/${arg_SPDXID}.cmake")
file(GENERATE OUTPUT "${license_sbom}" CONTENT "${content}")
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files "${license_sbom}")
endfunction()
# Helper to retrieve a valid spdx id, given some hints.
# HINTS can be a list of values, one of which will be sanitized and used as the spdx id.
# CHECK is expected to be a valid spdx id.
function(_qt_internal_sbom_get_and_check_spdx_id)
set(opt_args "")
set(single_args
VARIABLE
CHECK
)
set(multi_args
HINTS
)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
_qt_internal_sbom_set_default_option_value_and_error_if_empty(VARIABLE "")
if(NOT arg_CHECK)
get_property(spdx_id_count GLOBAL PROPERTY _qt_sbom_spdx_id_count)
set(suffix "-${spdx_id_count}")
math(EXPR spdx_id_count "${spdx_id_count} + 1")
set_property(GLOBAL PROPERTY _qt_sbom_spdx_id_count "${spdx_id_count}")
foreach(hint IN LISTS arg_HINTS)
_qt_internal_sbom_get_sanitized_spdx_id(id "${hint}")
if(id)
set(id "${id}${suffix}")
break()
endif()
endforeach()
if(NOT id)
set(id "SPDXRef${suffix}")
endif()
else()
set(id "${arg_CHECK}")
endif()
if("${id}" MATCHES "^SPDXRef-[-]+$"
OR (NOT "${id}" MATCHES "^SPDXRef-[-a-zA-Z0-9]+$"))
message(FATAL_ERROR "Invalid SPDXID \"${id}\"")
endif()
set(${arg_VARIABLE} "${id}" PARENT_SCOPE)
endfunction()