CMake: Split SBOM implementation into separate files
The SBOM implementation got somewhat large. Split the code into several new QtPublicSbomFooHelpers.cmake files, to make it more manageable. No code or behavior was changed. Pick-to: 6.8 6.9 Task-number: QTBUG-122899 Change-Id: Ia0ca1792eec21d12c4bb4cabe63279e1f5c07e3d Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
parent
d612dcd249
commit
27d2b54b5d
@ -293,8 +293,17 @@ function(qt_internal_get_qt_build_public_helpers out_var)
|
||||
QtPublicGitHelpers
|
||||
QtPublicPluginHelpers
|
||||
QtPublicPluginHelpers_v2
|
||||
QtPublicSbomAttributionHelpers
|
||||
QtPublicSbomCpeHelpers
|
||||
QtPublicSbomDepHelpers
|
||||
QtPublicSbomFileHelpers
|
||||
QtPublicSbomGenerationHelpers
|
||||
QtPublicSbomHelpers
|
||||
QtPublicSbomLicenseHelpers
|
||||
QtPublicSbomOpsHelpers
|
||||
QtPublicSbomPurlHelpers
|
||||
QtPublicSbomPythonHelpers
|
||||
QtPublicSbomSystemDepHelpers
|
||||
QtPublicTargetHelpers
|
||||
QtPublicTestHelpers
|
||||
QtPublicToolHelpers
|
||||
|
516
cmake/QtPublicSbomAttributionHelpers.cmake
Normal file
516
cmake/QtPublicSbomAttributionHelpers.cmake
Normal file
@ -0,0 +1,516 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Handles attribution information for a target.
|
||||
#
|
||||
# If CREATE_SBOM_FOR_EACH_ATTRIBUTION is set, a separate sbom target is created for each parsed
|
||||
# attribution entry, and the new targets are added as dependencies to the parent target.
|
||||
#
|
||||
# If CREATE_SBOM_FOR_EACH_ATTRIBUTION is not set, the information read from the first attribution
|
||||
# entry is added directly to the parent target, aka the the values are propagated to the outer
|
||||
# function scope to be read.. The rest of the attribution entries are created as separate targets
|
||||
# and added as dependencies, as if the option was passed.
|
||||
#
|
||||
# Handles multiple attribution files and entries within a file.
|
||||
# Attribution files can be specified either via directories and direct file paths.
|
||||
# If ATTRIBUTION_ENTRY_INDEX is set, only that specific attribution entry will be processed
|
||||
# from the given attribution file.
|
||||
function(_qt_internal_sbom_handle_qt_attribution_files out_prefix_outer)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_VERSION LESS_EQUAL "3.19")
|
||||
message(DEBUG "CMake version is too low, can't parse attribution.json file.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args
|
||||
CREATE_SBOM_FOR_EACH_ATTRIBUTION
|
||||
)
|
||||
set(single_args
|
||||
PARENT_TARGET
|
||||
)
|
||||
set(multi_args "")
|
||||
|
||||
_qt_internal_get_sbom_specific_options(sbom_opt_args sbom_single_args sbom_multi_args)
|
||||
list(APPEND opt_args ${sbom_opt_args})
|
||||
list(APPEND single_args ${sbom_single_args})
|
||||
list(APPEND multi_args ${sbom_multi_args})
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(attribution_files "")
|
||||
set(attribution_file_count 0)
|
||||
|
||||
foreach(attribution_file_path IN LISTS arg_ATTRIBUTION_FILE_PATHS)
|
||||
get_filename_component(real_path "${attribution_file_path}" REALPATH)
|
||||
list(APPEND attribution_files "${real_path}")
|
||||
math(EXPR attribution_file_count "${attribution_file_count} + 1")
|
||||
endforeach()
|
||||
|
||||
foreach(attribution_file_dir_path IN LISTS arg_ATTRIBUTION_FILE_DIR_PATHS)
|
||||
get_filename_component(real_path
|
||||
"${attribution_file_dir_path}/qt_attribution.json" REALPATH)
|
||||
list(APPEND attribution_files "${real_path}")
|
||||
math(EXPR attribution_file_count "${attribution_file_count} + 1")
|
||||
endforeach()
|
||||
|
||||
# If CREATE_SBOM_FOR_EACH_ATTRIBUTION is set, that means the parent target was a qt entity,
|
||||
# and not a 3rd party library.
|
||||
# In which case we don't want to proagate options like CPE to the child attribution targets,
|
||||
# because the CPE is meant for the parent target.
|
||||
set(propagate_sbom_options_to_new_attribution_targets TRUE)
|
||||
if(arg_CREATE_SBOM_FOR_EACH_ATTRIBUTION)
|
||||
set(propagate_sbom_options_to_new_attribution_targets FALSE)
|
||||
if(NOT arg_PARENT_TARGET)
|
||||
message(FATAL_ERROR "PARENT_TARGET must be set")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_ATTRIBUTION_ENTRY_INDEX AND attribution_file_count GREATER 1)
|
||||
message(FATAL_ERROR
|
||||
"ATTRIBUTION_ENTRY_INDEX should only be set if a single attribution "
|
||||
"file is specified."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(file_index 0)
|
||||
set(first_attribution_processed FALSE)
|
||||
foreach(attribution_file_path IN LISTS attribution_files)
|
||||
# Collect all processed attribution files to later create a configure-time dependency on
|
||||
# them so that the SBOM is regenerated (and CMake is re-ran) if they are modified.
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_internal_project_attribution_files
|
||||
"${attribution_file_path}")
|
||||
|
||||
# Set a unique out_prefix that will not overlap when multiple entries are processed.
|
||||
set(out_prefix_file "${out_prefix_outer}_${file_index}")
|
||||
|
||||
# Get the number of entries in the attribution file.
|
||||
_qt_internal_sbom_read_qt_attribution(${out_prefix_file}
|
||||
GET_ATTRIBUTION_ENTRY_COUNT
|
||||
OUT_VAR_VALUE attribution_entry_count
|
||||
FILE_PATH "${attribution_file_path}"
|
||||
)
|
||||
|
||||
# If a specific entry was specified, we will only process it from the file.
|
||||
if(NOT "${arg_ATTRIBUTION_ENTRY_INDEX}" STREQUAL "")
|
||||
set(entry_index ${arg_ATTRIBUTION_ENTRY_INDEX})
|
||||
else()
|
||||
set(entry_index 0)
|
||||
endif()
|
||||
|
||||
# Go through each entry in the attribution file.
|
||||
while("${entry_index}" LESS "${${out_prefix_file}_attribution_entry_count}")
|
||||
# If this is the first entry to be processed, or if CREATE_SBOM_FOR_EACH_ATTRIBUTION
|
||||
# is not set, we read the attribution file entry directly, and propagate the values
|
||||
# to the parent scope.
|
||||
if(NOT first_attribution_processed AND NOT arg_CREATE_SBOM_FOR_EACH_ATTRIBUTION)
|
||||
# Set a prefix without indices, so that the parent scope add_sbom call can
|
||||
# refer to the values directly with the outer prefix, without any index infix.
|
||||
set(out_prefix "${out_prefix_outer}")
|
||||
|
||||
_qt_internal_sbom_read_qt_attribution(${out_prefix}
|
||||
GET_DEFAULT_KEYS
|
||||
ENTRY_INDEX "${entry_index}"
|
||||
OUT_VAR_ASSIGNED_VARIABLE_NAMES variable_names
|
||||
FILE_PATH "${attribution_file_path}"
|
||||
)
|
||||
|
||||
# Propagate the values to the outer scope.
|
||||
foreach(variable_name IN LISTS variable_names)
|
||||
set(${out_prefix}_${variable_name} "${${out_prefix}_${variable_name}}"
|
||||
PARENT_SCOPE)
|
||||
endforeach()
|
||||
|
||||
get_filename_component(relative_attribution_file_path
|
||||
"${attribution_file_path}" REALPATH)
|
||||
|
||||
set(${out_prefix}_chosen_attribution_file_path "${relative_attribution_file_path}"
|
||||
PARENT_SCOPE)
|
||||
set(${out_prefix}_chosen_attribution_entry_index "${entry_index}"
|
||||
PARENT_SCOPE)
|
||||
|
||||
set(first_attribution_processed TRUE)
|
||||
if(NOT "${arg_ATTRIBUTION_ENTRY_INDEX}" STREQUAL "")
|
||||
# We had a specific index to process, so break right after processing it.
|
||||
break()
|
||||
endif()
|
||||
else()
|
||||
# We are processing the second or later entry, or CREATE_SBOM_FOR_EACH_ATTRIBUTION
|
||||
# was set. Instead of directly reading all the keys from the attribution file,
|
||||
# we get the Id, and create a new sbom target for the entry.
|
||||
# That will recursively call this function with a specific attribution file path
|
||||
# and index, to process the specific entry.
|
||||
|
||||
set(out_prefix "${out_prefix_outer}_${file_index}_${entry_index}")
|
||||
|
||||
# Get the attribution id.
|
||||
_qt_internal_sbom_read_qt_attribution(${out_prefix}
|
||||
GET_KEY
|
||||
KEY Id
|
||||
OUT_VAR_VALUE attribution_id
|
||||
ENTRY_INDEX "${entry_index}"
|
||||
FILE_PATH "${attribution_file_path}"
|
||||
)
|
||||
|
||||
# If no Id was retrieved, just add a numeric one, to make the sbom target
|
||||
# unique.
|
||||
set(attribution_target "${arg_PARENT_TARGET}_Attribution_")
|
||||
if(NOT ${out_prefix}_attribution_id)
|
||||
string(APPEND attribution_target "${file_index}_${entry_index}")
|
||||
else()
|
||||
string(APPEND attribution_target "${${out_prefix}_attribution_id}")
|
||||
endif()
|
||||
|
||||
set(sbom_args "")
|
||||
|
||||
if(propagate_sbom_options_to_new_attribution_targets)
|
||||
# Filter out the attributtion options, they will be passed mnaually
|
||||
# depending on which file and index is currently being processed.
|
||||
_qt_internal_get_sbom_specific_options(
|
||||
sbom_opt_args sbom_single_args sbom_multi_args)
|
||||
list(REMOVE_ITEM sbom_opt_args NO_CURRENT_DIR_ATTRIBUTION)
|
||||
list(REMOVE_ITEM sbom_single_args ATTRIBUTION_ENTRY_INDEX)
|
||||
list(REMOVE_ITEM sbom_multi_args
|
||||
ATTRIBUTION_FILE_PATHS
|
||||
ATTRIBUTION_FILE_DIR_PATHS
|
||||
)
|
||||
|
||||
# Also filter out the FRIENDLY_PACKAGE_NAME option, otherwise we'd try to
|
||||
# file(GENERATE) multiple times with the same file name, but different content.
|
||||
list(REMOVE_ITEM sbom_single_args FRIENDLY_PACKAGE_NAME)
|
||||
|
||||
_qt_internal_forward_function_args(
|
||||
FORWARD_APPEND
|
||||
FORWARD_PREFIX arg
|
||||
FORWARD_OUT_VAR sbom_args
|
||||
FORWARD_OPTIONS
|
||||
${sbom_opt_args}
|
||||
FORWARD_SINGLE
|
||||
${sbom_single_args}
|
||||
FORWARD_MULTI
|
||||
${sbom_multi_args}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create another sbom target with the id as a hint for the target name,
|
||||
# the attribution file passed, and make the new target a dependency of the
|
||||
# parent one.
|
||||
_qt_internal_add_sbom("${attribution_target}"
|
||||
IMMEDIATE_FINALIZATION
|
||||
TYPE QT_THIRD_PARTY_SOURCES
|
||||
ATTRIBUTION_FILE_PATHS "${attribution_file_path}"
|
||||
ATTRIBUTION_ENTRY_INDEX "${entry_index}"
|
||||
NO_CURRENT_DIR_ATTRIBUTION
|
||||
${sbom_args}
|
||||
)
|
||||
|
||||
_qt_internal_extend_sbom_dependencies(${arg_PARENT_TARGET}
|
||||
SBOM_DEPENDENCIES ${attribution_target}
|
||||
)
|
||||
endif()
|
||||
|
||||
math(EXPR entry_index "${entry_index} + 1")
|
||||
endwhile()
|
||||
|
||||
math(EXPR file_index "${file_index} + 1")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Helper to parse a qt_attribution.json file and do various operations:
|
||||
# - GET_DEFAULT_KEYS extracts the license id, copyrights, version, etc.
|
||||
# - GET_KEY extracts a single given json key's value, as specified with KEY and saved into
|
||||
# OUT_VAR_VALUE
|
||||
# - GET_ATTRIBUTION_ENTRY_COUNT returns the number of entries in the json file, set in
|
||||
# OUT_VAR_VALUE
|
||||
#
|
||||
# ENTRY_INDEX can be used to specify the array index to select a specific entry in the json file.
|
||||
#
|
||||
# Any retrieved value is set in the outer scope.
|
||||
# The variables are prefixed with ${out_prefix}.
|
||||
# OUT_VAR_ASSIGNED_VARIABLE_NAMES contains the list of variables set in the parent scope, the
|
||||
# variables names in this list are not prefixed with ${out_prefix}.
|
||||
#
|
||||
# Requires cmake 3.19 for json parsing.
|
||||
function(_qt_internal_sbom_read_qt_attribution out_prefix)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(CMAKE_VERSION LESS_EQUAL "3.19")
|
||||
message(DEBUG "CMake version is too low, can't parse attribution.json file.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args
|
||||
GET_DEFAULT_KEYS
|
||||
GET_KEY
|
||||
GET_ATTRIBUTION_ENTRY_COUNT
|
||||
)
|
||||
set(single_args
|
||||
FILE_PATH
|
||||
KEY
|
||||
ENTRY_INDEX
|
||||
OUT_VAR_VALUE
|
||||
OUT_VAR_ASSIGNED_VARIABLE_NAMES
|
||||
)
|
||||
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(file_path "${arg_FILE_PATH}")
|
||||
|
||||
if(NOT file_path)
|
||||
message(FATAL_ERROR "qt attribution file path not given")
|
||||
endif()
|
||||
|
||||
file(READ "${file_path}" contents)
|
||||
if(NOT contents)
|
||||
message(FATAL_ERROR "qt attribution file is empty: ${file_path}")
|
||||
endif()
|
||||
|
||||
if(NOT arg_GET_DEFAULT_KEYS AND NOT arg_GET_KEY AND NOT arg_GET_ATTRIBUTION_ENTRY_COUNT)
|
||||
message(FATAL_ERROR
|
||||
"No valid operation specified to _qt_internal_sbom_read_qt_attribution call.")
|
||||
endif()
|
||||
|
||||
if(arg_GET_KEY)
|
||||
if(NOT arg_KEY)
|
||||
message(FATAL_ERROR "KEY must be set")
|
||||
endif()
|
||||
if(NOT arg_OUT_VAR_VALUE)
|
||||
message(FATAL_ERROR "OUT_VAR_VALUE must be set")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
get_filename_component(attribution_file_dir "${file_path}" DIRECTORY)
|
||||
|
||||
# Parse the json file.
|
||||
# The first element might be an array, or an object. We need to detect which one.
|
||||
# Do that by trying to query index 0 of the potential root array.
|
||||
# If the index is found, that means the root is an array, and elem_error is set to NOTFOUND,
|
||||
# because there was no error.
|
||||
# Otherwise elem_error will be something like 'member '0' not found', and we can assume the
|
||||
# root is an object.
|
||||
string(JSON first_elem_type ERROR_VARIABLE elem_error TYPE "${contents}" 0)
|
||||
if(elem_error STREQUAL "NOTFOUND")
|
||||
# Root is an array. The attribution file might contain multiple entries.
|
||||
# Pick the first one if no specific index was specified, otherwise use the given index.
|
||||
if(NOT "${arg_ENTRY_INDEX}" STREQUAL "")
|
||||
set(indices "${arg_ENTRY_INDEX}")
|
||||
else()
|
||||
set(indices "0")
|
||||
endif()
|
||||
set(is_array TRUE)
|
||||
else()
|
||||
# Root is an object, not an array, which means the file has a single entry.
|
||||
set(indices "")
|
||||
set(is_array FALSE)
|
||||
endif()
|
||||
|
||||
set(variable_names "")
|
||||
|
||||
if(arg_GET_KEY)
|
||||
_qt_internal_sbom_get_attribution_key(${arg_KEY} ${arg_OUT_VAR_VALUE} ${out_prefix})
|
||||
endif()
|
||||
|
||||
if(arg_GET_ATTRIBUTION_ENTRY_COUNT)
|
||||
if(NOT arg_OUT_VAR_VALUE)
|
||||
message(FATAL_ERROR "OUT_VAR_VALUE must be set")
|
||||
endif()
|
||||
|
||||
if(is_array)
|
||||
string(JSON attribution_entry_count ERROR_VARIABLE elem_error LENGTH "${contents}")
|
||||
# There was an error getting the length of the array, so we assume it's empty.
|
||||
if(NOT elem_error STREQUAL "NOTFOUND")
|
||||
set(attribution_entry_count 0)
|
||||
endif()
|
||||
else()
|
||||
set(attribution_entry_count 1)
|
||||
endif()
|
||||
|
||||
set(${out_prefix}_${arg_OUT_VAR_VALUE} "${attribution_entry_count}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(arg_GET_DEFAULT_KEYS)
|
||||
# Some calls are currently commented out, to save on json parsing time because we don't have
|
||||
# a usage for them yet.
|
||||
# _qt_internal_sbom_get_attribution_key(License license)
|
||||
_qt_internal_sbom_get_attribution_key(LicenseId license_id "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(Version version "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(Homepage homepage "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(Name attribution_name "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(Description description "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(QtUsage qt_usage "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(DownloadLocation download_location "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(Copyright copyrights "${out_prefix}" IS_MULTI_VALUE)
|
||||
_qt_internal_sbom_get_attribution_key(CopyrightFile copyright_file "${out_prefix}")
|
||||
_qt_internal_sbom_get_attribution_key(PURL purls "${out_prefix}" IS_MULTI_VALUE)
|
||||
_qt_internal_sbom_get_attribution_key(CPE cpes "${out_prefix}" IS_MULTI_VALUE)
|
||||
|
||||
# Some attribution files contain a copyright file that contains the actual list of
|
||||
# copyrights. Read it and use it.
|
||||
set(copyright_file_path "${attribution_file_dir}/${copyright_file}")
|
||||
get_filename_component(copyright_file_path "${copyright_file_path}" REALPATH)
|
||||
if(NOT copyrights AND copyright_file AND EXISTS "${copyright_file_path}")
|
||||
file(READ "${copyright_file_path}" copyright_contents)
|
||||
if(copyright_contents)
|
||||
set(copyright_contents "${copyright_contents}")
|
||||
set(copyrights "${copyright_contents}")
|
||||
set(${out_prefix}_copyrights "${copyright_contents}" PARENT_SCOPE)
|
||||
list(APPEND variable_names "copyrights")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_ASSIGNED_VARIABLE_NAMES)
|
||||
set(${arg_OUT_VAR_ASSIGNED_VARIABLE_NAMES} "${variable_names}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Extracts a string or an array of strings from a json index path, depending on the extracted value
|
||||
# type.
|
||||
#
|
||||
# Given the 'contents' of the whole json document and the EXTRACTED_VALUE of a json key specified
|
||||
# by the INDICES path, it tries to determine whether the value is an array, in which case the array
|
||||
# is converted to a cmake list and assigned to ${out_var} in the parent scope.
|
||||
# Otherwise the function assumes the EXTRACTED_VALUE was not an array, and just assigns the value
|
||||
# of EXTRACTED_VALUE to ${out_var}
|
||||
function(_qt_internal_sbom_handle_attribution_json_array contents)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
EXTRACTED_VALUE
|
||||
OUT_VAR
|
||||
)
|
||||
set(multi_args
|
||||
INDICES
|
||||
)
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
# Write the original value to the parent scope, in case it was not an array.
|
||||
set(${arg_OUT_VAR} "${arg_EXTRACTED_VALUE}" PARENT_SCOPE)
|
||||
|
||||
if(NOT arg_EXTRACTED_VALUE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(JSON element_type TYPE "${contents}" ${arg_INDICES})
|
||||
|
||||
if(NOT element_type STREQUAL "ARRAY")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(json_array "${arg_EXTRACTED_VALUE}")
|
||||
string(JSON array_len LENGTH "${json_array}")
|
||||
|
||||
set(value_list "")
|
||||
|
||||
math(EXPR array_len "${array_len} - 1")
|
||||
foreach(index RANGE 0 "${array_len}")
|
||||
string(JSON value GET "${json_array}" ${index})
|
||||
if(value)
|
||||
list(APPEND value_list "${value}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(value_list)
|
||||
set(${arg_OUT_VAR} "${value_list}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Escapes various characters in json content, so that the generate cmake code to append the content
|
||||
# to the spdx document is syntactically valid.
|
||||
function(_qt_internal_sbom_escape_json_content content out_var)
|
||||
# Escape backslashes
|
||||
string(REPLACE "\\" "\\\\" escaped_content "${content}")
|
||||
|
||||
# Escape quotes
|
||||
string(REPLACE "\"" "\\\"" escaped_content "${escaped_content}")
|
||||
|
||||
set(${out_var} "${escaped_content}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# This macro reads a json key from a qt_attribution.json file, and assigns the escaped value to
|
||||
# out_var.
|
||||
# Also appends the name of the out_var to the parent scope 'variable_names' var.
|
||||
#
|
||||
# Expects 'contents' and 'indices' to already be set in the calling scope.
|
||||
#
|
||||
# If IS_MULTI_VALUE is set, handles the key as if it contained an array of
|
||||
# values, by converting the array of json values to a cmake list.
|
||||
macro(_qt_internal_sbom_get_attribution_key json_key out_var out_prefix)
|
||||
cmake_parse_arguments(arg "IS_MULTI_VALUE" "" "" ${ARGN})
|
||||
|
||||
string(JSON "${out_var}" ERROR_VARIABLE get_error GET "${contents}" ${indices} "${json_key}")
|
||||
if(NOT "${${out_var}}" STREQUAL "" AND NOT get_error)
|
||||
set(extracted_value "${${out_var}}")
|
||||
|
||||
if(arg_IS_MULTI_VALUE)
|
||||
_qt_internal_sbom_handle_attribution_json_array("${contents}"
|
||||
EXTRACTED_VALUE "${extracted_value}"
|
||||
INDICES ${indices} ${json_key}
|
||||
OUT_VAR value_list
|
||||
)
|
||||
if(value_list)
|
||||
set(extracted_value "${value_list}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_escape_json_content("${extracted_value}" escaped_content)
|
||||
|
||||
set(${out_prefix}_${out_var} "${escaped_content}" PARENT_SCOPE)
|
||||
list(APPEND variable_names "${out_var}")
|
||||
|
||||
unset(extracted_value)
|
||||
unset(escaped_content)
|
||||
unset(value_list)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Replaces placeholders in CPE and PURL strings read from qt_attribution.json files.
|
||||
#
|
||||
# VALUES - list of CPE or PURL strings
|
||||
# OUT_VAR - variable to store the replaced values
|
||||
# VERSION - version to replace in the placeholders
|
||||
|
||||
# Known placeholders:
|
||||
# $<VERSION> - Replaces occurrences of the placeholder with the value passed to the VERSION option.
|
||||
# $<VERSION_DASHED> - Replaces occurrences of the placeholder with the value passed to the VERSION
|
||||
# option, but with dots replaced by dashes.
|
||||
function(_qt_internal_sbom_replace_qa_placeholders)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
OUT_VAR
|
||||
VERSION
|
||||
)
|
||||
set(multi_args
|
||||
VALUES
|
||||
)
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
if(NOT arg_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
set(result "")
|
||||
|
||||
if(arg_VERSION)
|
||||
string(REPLACE "." "-" dashed_version "${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
foreach(value IN LISTS arg_VALUES)
|
||||
if(arg_VERSION)
|
||||
string(REPLACE "$<VERSION>" "${arg_VERSION}" value "${value}")
|
||||
string(REPLACE "$<VERSION_DASHED>" "${dashed_version}" value "${value}")
|
||||
endif()
|
||||
|
||||
list(APPEND result "${value}")
|
||||
endforeach()
|
||||
|
||||
set(${arg_OUT_VAR} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
90
cmake/QtPublicSbomCpeHelpers.cmake
Normal file
90
cmake/QtPublicSbomCpeHelpers.cmake
Normal file
@ -0,0 +1,90 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Computes a security CPE for a given set of attributes.
|
||||
#
|
||||
# When a part is not specified, a wildcard is added.
|
||||
#
|
||||
# References:
|
||||
# https://spdx.github.io/spdx-spec/v2.3/external-repository-identifiers/#f22-cpe23type
|
||||
# https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf
|
||||
# https://nvd.nist.gov/products/cpe
|
||||
#
|
||||
# Each attribute means:
|
||||
# 1. part
|
||||
# 2. vendor
|
||||
# 3. product
|
||||
# 4. version
|
||||
# 5. update
|
||||
# 6. edition
|
||||
# 7. language
|
||||
# 8. sw_edition
|
||||
# 9. target_sw
|
||||
# 10. target_hw
|
||||
# 11. other
|
||||
function(_qt_internal_sbom_compute_security_cpe out_cpe)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
PART
|
||||
VENDOR
|
||||
PRODUCT
|
||||
VERSION
|
||||
UPDATE
|
||||
EDITION
|
||||
)
|
||||
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(cpe_template "cpe:2.3:PART:VENDOR:PRODUCT:VERSION:UPDATE:EDITION:*:*:*:*:*")
|
||||
|
||||
set(cpe "${cpe_template}")
|
||||
foreach(attribute_name IN LISTS single_args)
|
||||
if(arg_${attribute_name})
|
||||
set(${attribute_name}_value "${arg_${attribute_name}}")
|
||||
else()
|
||||
if(attribute_name STREQUAL "PART")
|
||||
set(${attribute_name}_value "a")
|
||||
else()
|
||||
set(${attribute_name}_value "*")
|
||||
endif()
|
||||
endif()
|
||||
string(REPLACE "${attribute_name}" "${${attribute_name}_value}" cpe "${cpe}")
|
||||
endforeach()
|
||||
|
||||
set(${out_cpe} "${cpe}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Computes the default security CPE for the Qt framework.
|
||||
function(_qt_internal_sbom_get_cpe_qt out_var)
|
||||
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
|
||||
_qt_internal_sbom_compute_security_cpe(repo_cpe
|
||||
VENDOR "qt"
|
||||
PRODUCT "${repo_project_name_lowercase}"
|
||||
VERSION "${QT_REPO_MODULE_VERSION}"
|
||||
)
|
||||
set(${out_var} "${repo_cpe}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Computes the default security CPE for a given qt repository.
|
||||
function(_qt_internal_sbom_get_cpe_qt_repo out_var)
|
||||
_qt_internal_sbom_compute_security_cpe(qt_cpe
|
||||
VENDOR "qt"
|
||||
PRODUCT "qt"
|
||||
VERSION "${QT_REPO_MODULE_VERSION}"
|
||||
)
|
||||
set(${out_var} "${qt_cpe}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Computes the list of security CPEs for Qt, including both the repo-specific one and generic one.
|
||||
function(_qt_internal_sbom_compute_security_cpe_for_qt out_cpe_list)
|
||||
set(cpe_list "")
|
||||
|
||||
_qt_internal_sbom_get_cpe_qt(repo_cpe)
|
||||
list(APPEND cpe_list "${repo_cpe}")
|
||||
|
||||
_qt_internal_sbom_get_cpe_qt_repo(qt_cpe)
|
||||
list(APPEND cpe_list "${qt_cpe}")
|
||||
|
||||
set(${out_cpe_list} "${cpe_list}" PARENT_SCOPE)
|
||||
endfunction()
|
327
cmake/QtPublicSbomDepHelpers.cmake
Normal file
327
cmake/QtPublicSbomDepHelpers.cmake
Normal file
@ -0,0 +1,327 @@
|
||||
# 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()
|
1083
cmake/QtPublicSbomFileHelpers.cmake
Normal file
1083
cmake/QtPublicSbomFileHelpers.cmake
Normal file
File diff suppressed because it is too large
Load Diff
@ -256,94 +256,6 @@ Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
|
||||
set_property(GLOBAL PROPERTY _qt_sbom_relationship_counter 0)
|
||||
endfunction()
|
||||
|
||||
# Handles the look up of Python, Python spdx dependencies and other various post-installation steps
|
||||
# like NTIA validation, auditing, json generation, etc.
|
||||
function(_qt_internal_sbom_setup_project_ops_generation)
|
||||
set(opt_args
|
||||
GENERATE_JSON
|
||||
GENERATE_JSON_REQUIRED
|
||||
GENERATE_SOURCE_SBOM
|
||||
VERIFY_SBOM
|
||||
VERIFY_SBOM_REQUIRED
|
||||
VERIFY_NTIA_COMPLIANT
|
||||
LINT_SOURCE_SBOM
|
||||
LINT_SOURCE_SBOM_NO_ERROR
|
||||
SHOW_TABLE
|
||||
AUDIT
|
||||
AUDIT_NO_ERROR
|
||||
)
|
||||
set(single_args "")
|
||||
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(arg_GENERATE_JSON AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(op_args
|
||||
OP_KEY "GENERATE_JSON"
|
||||
OUT_VAR_DEPS_FOUND deps_found
|
||||
)
|
||||
if(arg_GENERATE_JSON_REQUIRED)
|
||||
list(APPEND op_args REQUIRED)
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
|
||||
if(deps_found)
|
||||
_qt_internal_sbom_generate_json()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_VERIFY_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(op_args
|
||||
OP_KEY "VERIFY_SBOM"
|
||||
OUT_VAR_DEPS_FOUND deps_found
|
||||
)
|
||||
if(arg_VERIFY_SBOM_REQUIRED)
|
||||
list(APPEND op_args REQUIRED)
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
|
||||
if(deps_found)
|
||||
_qt_internal_sbom_verify_valid()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_VERIFY_NTIA_COMPLIANT AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(REQUIRED OP_KEY "RUN_NTIA")
|
||||
_qt_internal_sbom_verify_ntia_compliant()
|
||||
endif()
|
||||
|
||||
if(arg_SHOW_TABLE AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME sbom2doc REQUIRED)
|
||||
_qt_internal_sbom_show_table()
|
||||
endif()
|
||||
|
||||
if(arg_AUDIT AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(audit_no_error_option "")
|
||||
if(arg_AUDIT_NO_ERROR)
|
||||
set(audit_no_error_option NO_ERROR)
|
||||
endif()
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME sbomaudit REQUIRED)
|
||||
_qt_internal_sbom_audit(${audit_no_error_option})
|
||||
endif()
|
||||
|
||||
if(arg_GENERATE_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME reuse REQUIRED)
|
||||
_qt_internal_sbom_generate_reuse_source_sbom()
|
||||
endif()
|
||||
|
||||
if(arg_LINT_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(lint_no_error_option "")
|
||||
if(arg_LINT_SOURCE_SBOM_NO_ERROR)
|
||||
set(lint_no_error_option NO_ERROR)
|
||||
endif()
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME reuse REQUIRED)
|
||||
_qt_internal_sbom_run_reuse_lint(
|
||||
${lint_no_error_option}
|
||||
BUILD_TIME_SCRIPT_PATH_OUT_VAR reuse_lint_script
|
||||
)
|
||||
endif()
|
||||
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.
|
||||
@ -1255,738 +1167,4 @@ function(_qt_internal_sbom_get_and_check_spdx_id)
|
||||
set(${arg_VARIABLE} "${id}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Helper to find a python interpreter and a specific python dependency, e.g. to be able to generate
|
||||
# a SPDX JSON SBOM, or run post-installation steps like NTIA verification.
|
||||
# The exact dependency should be specified as the OP_KEY.
|
||||
#
|
||||
# Caches the found python executable in a separate cache var QT_INTERNAL_SBOM_PYTHON_EXECUTABLE, to
|
||||
# avoid conflicts with any other found python interpreter.
|
||||
function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
|
||||
set(opt_args
|
||||
REQUIRED
|
||||
)
|
||||
set(single_args
|
||||
OP_KEY
|
||||
OUT_VAR_DEPS_FOUND
|
||||
)
|
||||
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_OP_KEY)
|
||||
message(FATAL_ERROR "OP_KEY is required")
|
||||
endif()
|
||||
|
||||
set(supported_ops "GENERATE_JSON" "VERIFY_SBOM" "RUN_NTIA")
|
||||
|
||||
if(arg_OP_KEY STREQUAL "GENERATE_JSON" OR arg_OP_KEY STREQUAL "VERIFY_SBOM")
|
||||
set(import_statement "import spdx_tools.spdx.clitools.pyspdxtools")
|
||||
elseif(arg_OP_KEY STREQUAL "RUN_NTIA")
|
||||
set(import_statement "import ntia_conformance_checker.main")
|
||||
else()
|
||||
message(FATAL_ERROR "OP_KEY must be one of ${supported_ops}")
|
||||
endif()
|
||||
|
||||
# Return early if we found the dependencies.
|
||||
if(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY})
|
||||
if(arg_OUT_VAR_DEPS_FOUND)
|
||||
set(${arg_OUT_VAR_DEPS_FOUND} TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# NTIA-compliance checker requires Python 3.9 or later, so we use it as the minimum for all
|
||||
# SBOM OPs.
|
||||
set(required_version "3.9")
|
||||
|
||||
set(python_common_args
|
||||
VERSION "${required_version}"
|
||||
)
|
||||
|
||||
set(everything_found FALSE)
|
||||
|
||||
# On macOS FindPython prefers looking in the system framework location, but that usually would
|
||||
# not have the required dependencies. So we first look in it, and then fallback to any other
|
||||
# non-framework python found.
|
||||
if(CMAKE_HOST_APPLE)
|
||||
set(extra_python_args SEARCH_IN_FRAMEWORKS QUIET)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
|
||||
endif()
|
||||
|
||||
if(NOT everything_found)
|
||||
set(extra_python_args QUIET)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
|
||||
endif()
|
||||
|
||||
if(NOT everything_found)
|
||||
if(arg_REQUIRED)
|
||||
set(message_type "FATAL_ERROR")
|
||||
else()
|
||||
set(message_type "DEBUG")
|
||||
endif()
|
||||
|
||||
if(NOT python_found)
|
||||
# Look for python one more time, this time without QUIET, to show an error why it
|
||||
# wasn't found.
|
||||
if(arg_REQUIRED)
|
||||
_qt_internal_sbom_find_python_helper(${python_common_args}
|
||||
OUT_VAR_PYTHON_PATH unused_python
|
||||
OUT_VAR_PYTHON_FOUND unused_found
|
||||
)
|
||||
endif()
|
||||
message(${message_type} "Python ${required_version} for running SBOM ops not found.")
|
||||
elseif(NOT dep_found)
|
||||
message(${message_type} "Python dependency for running SBOM op ${arg_OP_KEY} "
|
||||
"not found:\n Python: ${python_path} \n Output: \n${dep_find_output}")
|
||||
endif()
|
||||
else()
|
||||
message(DEBUG "Using Python ${python_path} for running SBOM ops.")
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${python_path}" CACHE INTERNAL
|
||||
"Python interpeter used for SBOM generation.")
|
||||
endif()
|
||||
|
||||
set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
|
||||
"All dependencies found to run SBOM OP ${arg_OP_KEY}")
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_DEPS_FOUND)
|
||||
set(${arg_OUT_VAR_DEPS_FOUND} "${QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY}}"
|
||||
PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper macro to find python and a given dependency. Expects the caller to set all of the vars.
|
||||
# Meant to reduce the line noise due to the repeated calls.
|
||||
macro(_qt_internal_sbom_find_python_and_dependency_helper_lambda)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper(
|
||||
PYTHON_ARGS
|
||||
${extra_python_args}
|
||||
${python_common_args}
|
||||
DEPENDENCY_ARGS
|
||||
DEPENDENCY_IMPORT_STATEMENT "${import_statement}"
|
||||
OUT_VAR_PYTHON_PATH python_path
|
||||
OUT_VAR_PYTHON_FOUND python_found
|
||||
OUT_VAR_DEP_FOUND dep_found
|
||||
OUT_VAR_PYTHON_AND_DEP_FOUND everything_found
|
||||
OUT_VAR_DEP_FIND_OUTPUT dep_find_output
|
||||
)
|
||||
endmacro()
|
||||
|
||||
# Tries to find python and a given dependency based on the args passed to PYTHON_ARGS and
|
||||
# DEPENDENCY_ARGS which are forwarded to the respective finding functions.
|
||||
# Returns the path to the python interpreter, whether it was found, whether the dependency was
|
||||
# found, whether both were found, and the reason why the dependency might not be found.
|
||||
function(_qt_internal_sbom_find_python_and_dependency_helper)
|
||||
set(opt_args)
|
||||
set(single_args
|
||||
OUT_VAR_PYTHON_PATH
|
||||
OUT_VAR_PYTHON_FOUND
|
||||
OUT_VAR_DEP_FOUND
|
||||
OUT_VAR_PYTHON_AND_DEP_FOUND
|
||||
OUT_VAR_DEP_FIND_OUTPUT
|
||||
)
|
||||
set(multi_args
|
||||
PYTHON_ARGS
|
||||
DEPENDENCY_ARGS
|
||||
)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(everything_found_inner FALSE)
|
||||
set(deps_find_output_inner "")
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_PATH)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_PATH var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_DEP_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_DEP_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_AND_DEP_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_AND_DEP_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_DEP_FIND_OUTPUT)
|
||||
message(FATAL_ERROR "OUT_VAR_DEP_FIND_OUTPUT var is required")
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_python_helper(
|
||||
${arg_PYTHON_ARGS}
|
||||
OUT_VAR_PYTHON_PATH python_path_inner
|
||||
OUT_VAR_PYTHON_FOUND python_found_inner
|
||||
)
|
||||
|
||||
if(python_found_inner AND python_path_inner)
|
||||
_qt_internal_sbom_find_python_dependency_helper(
|
||||
${arg_DEPENDENCY_ARGS}
|
||||
PYTHON_PATH "${python_path_inner}"
|
||||
OUT_VAR_FOUND dep_found_inner
|
||||
OUT_VAR_OUTPUT dep_find_output_inner
|
||||
)
|
||||
|
||||
if(dep_found_inner)
|
||||
set(everything_found_inner TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${arg_OUT_VAR_PYTHON_PATH} "${python_path_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_FOUND} "${python_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_DEP_FOUND} "${dep_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_AND_DEP_FOUND} "${everything_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_DEP_FIND_OUTPUT} "${dep_find_output_inner}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Tries to find the python intrepreter, given the QT_SBOM_PYTHON_INTERP path hint, as well as
|
||||
# other options.
|
||||
# Ignores any previously found python.
|
||||
# Returns the python interpreter path and whether it was successfully found.
|
||||
#
|
||||
# This is intentionally a function, and not a macro, to prevent overriding the Python3_EXECUTABLE
|
||||
# non-cache variable in a global scope in case if a different python is found and used for a
|
||||
# different purpose (e.g. qtwebengine or qtinterfaceframework).
|
||||
# The reason to use a different python is that an already found python might not be the version we
|
||||
# need, or might lack the dependencies we need.
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/issues/21797#note_901621 claims that finding multiple
|
||||
# python versions in separate directory scopes is possible, and I claim a function scope is as
|
||||
# good as a directory scope.
|
||||
function(_qt_internal_sbom_find_python_helper)
|
||||
set(opt_args
|
||||
SEARCH_IN_FRAMEWORKS
|
||||
QUIET
|
||||
)
|
||||
set(single_args
|
||||
VERSION
|
||||
OUT_VAR_PYTHON_PATH
|
||||
OUT_VAR_PYTHON_FOUND
|
||||
)
|
||||
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_OUT_VAR_PYTHON_PATH)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_PATH var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_FOUND var is required")
|
||||
endif()
|
||||
|
||||
# Allow disabling looking for a python interpreter shipped as part of a macOS system framework.
|
||||
if(NOT arg_SEARCH_IN_FRAMEWORKS)
|
||||
set(Python3_FIND_FRAMEWORK NEVER)
|
||||
endif()
|
||||
|
||||
set(required_version "")
|
||||
if(arg_VERSION)
|
||||
set(required_version "${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
set(find_quiet "")
|
||||
if(arg_QUIET)
|
||||
set(find_quiet "QUIET")
|
||||
endif()
|
||||
|
||||
# Locally reset any executable that was possibly already found.
|
||||
# We do this to ensure we always re-do the lookup/
|
||||
# This needs to be set to an empty string, to override any cache variable
|
||||
set(Python3_EXECUTABLE "")
|
||||
|
||||
# This needs to be unset, because the Python module checks whether the variable is defined, not
|
||||
# whether it is empty.
|
||||
unset(_Python3_EXECUTABLE)
|
||||
|
||||
if(QT_SBOM_PYTHON_INTERP)
|
||||
set(Python3_ROOT_DIR ${QT_SBOM_PYTHON_INTERP})
|
||||
endif()
|
||||
|
||||
find_package(Python3 ${required_version} ${find_quiet} COMPONENTS Interpreter)
|
||||
|
||||
set(${arg_OUT_VAR_PYTHON_PATH} "${Python3_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_FOUND} "${Python3_Interpreter_FOUND}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Helper that takes an python import statement to run using the given python interpreter path,
|
||||
# to confirm that the given python dependency can be found.
|
||||
# Returns whether the dependency was found and the output of running the import, for error handling.
|
||||
function(_qt_internal_sbom_find_python_dependency_helper)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
DEPENDENCY_IMPORT_STATEMENT
|
||||
PYTHON_PATH
|
||||
OUT_VAR_FOUND
|
||||
OUT_VAR_OUTPUT
|
||||
)
|
||||
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_PYTHON_PATH)
|
||||
message(FATAL_ERROR "Python interpreter path not given.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_DEPENDENCY_IMPORT_STATEMENT)
|
||||
message(FATAL_ERROR "Python depdendency import statement not given.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_FOUND)
|
||||
message(FATAL_ERROR "Out var found variable not given.")
|
||||
endif()
|
||||
|
||||
set(python_path "${arg_PYTHON_PATH}")
|
||||
execute_process(
|
||||
COMMAND
|
||||
${python_path} -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
|
||||
RESULT_VARIABLE res
|
||||
OUTPUT_VARIABLE output
|
||||
ERROR_VARIABLE output
|
||||
)
|
||||
|
||||
if("${res}" STREQUAL "0")
|
||||
set(found TRUE)
|
||||
set(output "${output}")
|
||||
else()
|
||||
set(found FALSE)
|
||||
string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} not found. "
|
||||
"Error:\n${output}")
|
||||
endif()
|
||||
|
||||
set(${arg_OUT_VAR_FOUND} "${found}" PARENT_SCOPE)
|
||||
if(arg_OUT_VAR_OUTPUT)
|
||||
set(${arg_OUT_VAR_OUTPUT} "${output}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper to find a python installed CLI utility.
|
||||
# Expected to be in PATH.
|
||||
function(_qt_internal_sbom_find_python_dependency_program)
|
||||
set(opt_args
|
||||
REQUIRED
|
||||
)
|
||||
set(single_args
|
||||
NAME
|
||||
)
|
||||
set(multi_args "")
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(program_name "${arg_NAME}")
|
||||
string(TOUPPER "${program_name}" upper_name)
|
||||
set(cache_var "QT_SBOM_PROGRAM_${upper_name}")
|
||||
|
||||
set(hints "")
|
||||
|
||||
# The path to python installed apps is different on Windows compared to UNIX, so we use
|
||||
# a different path than where the python interpreter might be located.
|
||||
if(QT_SBOM_PYTHON_APPS_PATH)
|
||||
list(APPEND hints ${QT_SBOM_PYTHON_APPS_PATH})
|
||||
endif()
|
||||
|
||||
find_program(${cache_var}
|
||||
NAMES ${program_name}
|
||||
HINTS ${hints}
|
||||
)
|
||||
|
||||
if(NOT ${cache_var})
|
||||
if(arg_REQUIRED)
|
||||
set(message_type "FATAL_ERROR")
|
||||
set(prefix "Required ")
|
||||
else()
|
||||
set(message_type "STATUS")
|
||||
set(prefix "Optional ")
|
||||
endif()
|
||||
message(${message_type} "${prefix}SBOM python program '${program_name}' not found.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper to generate a SPDX JSON file from a tag/value format file.
|
||||
# This also implies some additional validity checks, useful to ensure a proper sbom file.
|
||||
function(_qt_internal_sbom_generate_json)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for generating SBOM json file.")
|
||||
endif()
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_GENERATE_JSON)
|
||||
message(FATAL_ERROR "Python dependencies not found for generating SBOM json file.")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Generating JSON: \${QT_SBOM_OUTPUT_PATH}.json\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"\${QT_SBOM_OUTPUT_PATH}\" -o \"\${QT_SBOM_OUTPUT_PATH}.json\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM conversion to JSON failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/convert_to_json.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to generate a tag/value SPDX file from a SPDX JSON format file.
|
||||
#
|
||||
# Will be used by WebEngine to convert the Chromium JSON file to a tag/value SPDX file.
|
||||
#
|
||||
# This conversion needs to happen before the document is referenced in the SBOM generation process,
|
||||
# so that the file already exists when it is parsed for its unique id and namespace.
|
||||
# It also needs to happen before verification codes are computed for the current document
|
||||
# that will depend on the target one, to ensure the the file exists and its checksum can be
|
||||
# computed.
|
||||
#
|
||||
# OPERATION_ID - a unique id for the operation, used to generate a unique cmake file name for
|
||||
# the SBOM generation process.
|
||||
#
|
||||
# INPUT_JSON_PATH - the absolute path to the input JSON file.
|
||||
#
|
||||
# OUTPUT_FILE_PATH - the absolute path where to create the output tag/value SPDX file.
|
||||
# Note that if the output file path is set, it is up to the caller to also copy / install the file
|
||||
# into the build and install directories where the build system expects to find all external
|
||||
# document references.
|
||||
#
|
||||
# OUTPUT_FILE_NAME - when OUTPUT_FILE_PATH is not specified, the output directory is automatically
|
||||
# set to the SBOM output directory. In this case OUTPUT_FILE_NAME can be used to override the
|
||||
# outout file name. If not specified, it will be derived from the input file name.
|
||||
#
|
||||
# OUT_VAR_OUTPUT_FILE_NAME - output variable where to store the output file.
|
||||
#
|
||||
# OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH - output variable where to store the output file path.
|
||||
# Note that the path will contain an unresolved '${QT_SBOM_OUTPUT_DIR}' which only has a value at
|
||||
# install time. So the path can't be used sensibly during configure time.
|
||||
function(_qt_internal_sbom_generate_tag_value_spdx_document)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
OPERATION_ID
|
||||
INPUT_JSON_FILE_PATH
|
||||
OUTPUT_FILE_PATH
|
||||
OUTPUT_FILE_NAME
|
||||
OUT_VAR_OUTPUT_FILE_NAME
|
||||
OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH
|
||||
)
|
||||
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 QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for generating tag/value file from JSON.")
|
||||
endif()
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_GENERATE_JSON)
|
||||
message(FATAL_ERROR
|
||||
"Python dependencies not found for generating tag/value file from JSON.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OPERATION_ID)
|
||||
message(FATAL_ERROR "OPERATION_ID is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_INPUT_JSON_FILE_PATH)
|
||||
message(FATAL_ERROR "INPUT_JSON_FILE_PATH is required")
|
||||
endif()
|
||||
|
||||
if(arg_OUTPUT_FILE_PATH)
|
||||
set(output_path "${arg_OUTPUT_FILE_PATH}")
|
||||
else()
|
||||
if(arg_OUTPUT_FILE_NAME)
|
||||
set(output_name "${arg_OUTPUT_FILE_NAME}")
|
||||
else()
|
||||
# Use the input file name without the last extension (without .json) as the output name.
|
||||
get_filename_component(output_name "${arg_INPUT_JSON_FILE_PATH}" NAME_WLE)
|
||||
endif()
|
||||
set(output_path "\${QT_SBOM_OUTPUT_DIR}/${output_name}")
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_OUTPUT_FILE_NAME)
|
||||
get_filename_component(output_name_resolved "${output_path}" NAME)
|
||||
set(${arg_OUT_VAR_OUTPUT_FILE_NAME} "${output_name_resolved}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH)
|
||||
set(${arg_OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH} "${output_path}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS
|
||||
\"Generating tag/value SPDX document: ${output_path} from \"
|
||||
\"${arg_INPUT_JSON_FILE_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"${arg_INPUT_JSON_FILE_PATH}\" -o \"${output_path}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM conversion to tag/value failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(convert_sbom "${sbom_dir}/convert_to_tag_value_${arg_OPERATION_ID}.cmake")
|
||||
file(GENERATE OUTPUT "${convert_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files
|
||||
"${convert_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to verify the generated sbom is valid.
|
||||
function(_qt_internal_sbom_verify_valid)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for verifying SBOM file.")
|
||||
endif()
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_VERIFY_SBOM)
|
||||
message(FATAL_ERROR "Python dependencies not found for verifying SBOM file")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Verifying: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM verification failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/verify_valid.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to verify the generated sbom is NTIA compliant.
|
||||
function(_qt_internal_sbom_verify_ntia_compliant)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for verifying SBOM file.")
|
||||
endif()
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_RUN_NTIA)
|
||||
message(FATAL_ERROR "Python dependencies not found for running the SBOM NTIA checker.")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Checking for NTIA compliance: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m ntia_conformance_checker.main
|
||||
--file \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM NTIA verification failed: \{res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/verify_ntia.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to show the main sbom document info in the form of a CLI table.
|
||||
function(_qt_internal_sbom_show_table)
|
||||
set(extra_code_begin "")
|
||||
if(DEFINED ENV{COIN_UNIQUE_JOB_ID})
|
||||
# The output of the process dynamically adjusts the width of the shown table based on the
|
||||
# console width. In the CI, the width is very short for some reason, and thus the output
|
||||
# is truncated in the CI log. Explicitly set a bigger width to avoid this.
|
||||
set(extra_code_begin "
|
||||
set(backup_env_columns \$ENV{COLUMNS})
|
||||
set(ENV{COLUMNS} 150)
|
||||
")
|
||||
set(extra_code_end "
|
||||
set(ENV{COLUMNS} \${backup_env_columns})
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Showing main SBOM document info: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
|
||||
${extra_code_begin}
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_SBOM2DOC} -i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${extra_code_end}
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Showing SBOM document failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/show_table.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to audit the generated sbom.
|
||||
function(_qt_internal_sbom_audit)
|
||||
set(opt_args NO_ERROR)
|
||||
set(single_args "")
|
||||
set(multi_args "")
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM Audit failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Auditing SBOM: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_SBOMAUDIT} -i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
--disable-license-check --cpecheck --offline
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/audit.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Returns path to project's potential root source reuse.toml file.
|
||||
function(_qt_internal_sbom_get_project_reuse_toml_path out_var)
|
||||
set(reuse_toml_path "${PROJECT_SOURCE_DIR}/REUSE.toml")
|
||||
set(${out_var} "${reuse_toml_path}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Helper to generate and install a source SBOM using reuse.
|
||||
function(_qt_internal_sbom_generate_reuse_source_sbom)
|
||||
set(opt_args NO_ERROR)
|
||||
set(single_args "")
|
||||
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_get_current_project_sbom_dir(sbom_dir)
|
||||
set(file_op "${sbom_dir}/generate_reuse_source_sbom.cmake")
|
||||
|
||||
_qt_internal_sbom_get_project_reuse_toml_path(reuse_toml_path)
|
||||
if(NOT EXISTS "${reuse_toml_path}" AND NOT QT_FORCE_SOURCE_SBOM_GENERATION)
|
||||
set(skip_message
|
||||
"Skipping source SBOM generation: No reuse.toml file found at '${reuse_toml_path}'.")
|
||||
message(STATUS "${skip_message}")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"${skip_message}\")
|
||||
")
|
||||
|
||||
file(GENERATE OUTPUT "${file_op}" CONTENT "${content}")
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_post_generation_include_files
|
||||
"${file_op}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Source SBOM generation using reuse tool failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(source_sbom_path "\${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.source.spdx")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Generating source SBOM using reuse tool: ${source_sbom_path}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_REUSE} --root \"${PROJECT_SOURCE_DIR}\" spdx
|
||||
-o ${source_sbom_path}
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
file(GENERATE OUTPUT "${file_op}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_post_generation_include_files "${file_op}")
|
||||
endfunction()
|
||||
|
||||
# Helper to run 'reuse lint' on the project source dir.
|
||||
function(_qt_internal_sbom_run_reuse_lint)
|
||||
set(opt_args
|
||||
NO_ERROR
|
||||
)
|
||||
set(single_args
|
||||
BUILD_TIME_SCRIPT_PATH_OUT_VAR
|
||||
)
|
||||
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 no reuse.toml file exists, it means the repo is likely not reuse compliant yet,
|
||||
# so we shouldn't error out during installation when running the lint.
|
||||
_qt_internal_sbom_get_project_reuse_toml_path(reuse_toml_path)
|
||||
if(NOT EXISTS "${reuse_toml_path}" AND NOT QT_FORCE_REUSE_LINT_ERROR)
|
||||
set(arg_NO_ERROR TRUE)
|
||||
endif()
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Running 'reuse lint' failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Running 'reuse lint' in '${PROJECT_SOURCE_DIR}'.\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_REUSE} --root \"${PROJECT_SOURCE_DIR}\" lint
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(file_op_build "${sbom_dir}/run_reuse_lint_build.cmake")
|
||||
file(GENERATE OUTPUT "${file_op_build}" CONTENT "${content}")
|
||||
|
||||
# Allow skipping running 'reuse lint' during installation. But still allow running it during
|
||||
# build time. This is a fail safe opt-out in case some repo needs it.
|
||||
if(QT_FORCE_SKIP_REUSE_LINT_ON_INSTALL)
|
||||
set(skip_message "Skipping running 'reuse lint' in '${PROJECT_SOURCE_DIR}'.")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"${skip_message}\")
|
||||
")
|
||||
set(file_op_install "${sbom_dir}/run_reuse_lint_install.cmake")
|
||||
file(GENERATE OUTPUT "${file_op_install}" CONTENT "${content}")
|
||||
else()
|
||||
# Just reuse the already generated script for installation as well.
|
||||
set(file_op_install "${file_op_build}")
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${file_op_install}")
|
||||
|
||||
if(arg_BUILD_TIME_SCRIPT_PATH_OUT_VAR)
|
||||
set(${arg_BUILD_TIME_SCRIPT_PATH_OUT_VAR} "${file_op_build}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
File diff suppressed because it is too large
Load Diff
108
cmake/QtPublicSbomLicenseHelpers.cmake
Normal file
108
cmake/QtPublicSbomLicenseHelpers.cmake
Normal file
@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Adds a license id and its text to the sbom.
|
||||
function(_qt_internal_sbom_add_license)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args
|
||||
NO_LICENSE_REF_PREFIX
|
||||
)
|
||||
set(single_args
|
||||
LICENSE_ID
|
||||
LICENSE_PATH
|
||||
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)
|
||||
|
||||
if(NOT arg_LICENSE_ID)
|
||||
message(FATAL_ERROR "LICENSE_ID must be set")
|
||||
endif()
|
||||
|
||||
if(NOT arg_TEXT AND NOT arg_LICENSE_PATH)
|
||||
message(FATAL_ERROR "Either TEXT or LICENSE_PATH must be set")
|
||||
endif()
|
||||
|
||||
# Sanitize the content a bit.
|
||||
if(arg_TEXT)
|
||||
set(text "${arg_TEXT}")
|
||||
string(REPLACE ";" "$<SEMICOLON>" text "${text}")
|
||||
string(REPLACE "\"" "\\\"" text "${text}")
|
||||
else()
|
||||
file(READ "${arg_LICENSE_PATH}" text)
|
||||
string(REPLACE ";" "$<SEMICOLON>" text "${text}")
|
||||
string(REPLACE "\"" "\\\"" text "${text}")
|
||||
endif()
|
||||
|
||||
set(license_id "${arg_LICENSE_ID}")
|
||||
if(NOT arg_NO_LICENSE_REF_PREFIX)
|
||||
set(license_id "LicenseRef-${license_id}")
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_generate_add_license(
|
||||
LICENSE_ID "${license_id}"
|
||||
EXTRACTED_TEXT "<text>${text}</text>"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
# Get a qt spdx license expression given the id.
|
||||
function(_qt_internal_sbom_get_spdx_license_expression id out_var)
|
||||
set(license "")
|
||||
|
||||
# The default for modules / plugins
|
||||
if(id STREQUAL "QT_DEFAULT" OR id STREQUAL "QT_COMMERCIAL_OR_LGPL3")
|
||||
set(license "LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only")
|
||||
|
||||
# For commercial only entities
|
||||
elseif(id STREQUAL "QT_COMMERCIAL")
|
||||
set(license "LicenseRef-Qt-Commercial")
|
||||
|
||||
# For GPL3 only modules
|
||||
elseif(id STREQUAL "QT_COMMERCIAL_OR_GPL3")
|
||||
set(license "LicenseRef-Qt-Commercial OR GPL-3.0-only")
|
||||
|
||||
# For tools and apps
|
||||
elseif(id STREQUAL "QT_COMMERCIAL_OR_GPL3_WITH_EXCEPTION")
|
||||
set(license "LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0")
|
||||
|
||||
# For things like the qtmain library
|
||||
elseif(id STREQUAL "QT_COMMERCIAL_OR_BSD3")
|
||||
set(license "LicenseRef-Qt-Commercial OR BSD-3-Clause")
|
||||
|
||||
# For documentation
|
||||
elseif(id STREQUAL "QT_COMMERCIAL_OR_GFDL1_3")
|
||||
set(license "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only")
|
||||
|
||||
# For examples and the like
|
||||
elseif(id STREQUAL "BSD3")
|
||||
set(license "BSD-3-Clause")
|
||||
|
||||
endif()
|
||||
|
||||
if(NOT license)
|
||||
message(FATAL_ERROR "No SPDX license expression found for id: ${id}")
|
||||
endif()
|
||||
|
||||
set(${out_var} "${license}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Joins two license IDs with the given ${op}, avoiding parenthesis when possible.
|
||||
function(_qt_internal_sbom_join_two_license_ids_with_op left_id op right_id out_var)
|
||||
if(NOT left_id)
|
||||
set(${out_var} "${right_id}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT right_id)
|
||||
set(${out_var} "${left_id}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(value "(${left_id}) ${op} (${right_id})")
|
||||
set(${out_var} "${value}" PARENT_SCOPE)
|
||||
endfunction()
|
579
cmake/QtPublicSbomOpsHelpers.cmake
Normal file
579
cmake/QtPublicSbomOpsHelpers.cmake
Normal file
@ -0,0 +1,579 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# Copyright (C) 2023-2024 Jochem Rutgers
|
||||
# SPDX-License-Identifier: MIT AND BSD-3-Clause
|
||||
|
||||
# Handles the look up of Python, Python spdx dependencies and other various post-installation steps
|
||||
# like NTIA validation, auditing, json generation, etc.
|
||||
function(_qt_internal_sbom_setup_project_ops_generation)
|
||||
set(opt_args
|
||||
GENERATE_JSON
|
||||
GENERATE_JSON_REQUIRED
|
||||
GENERATE_SOURCE_SBOM
|
||||
VERIFY_SBOM
|
||||
VERIFY_SBOM_REQUIRED
|
||||
VERIFY_NTIA_COMPLIANT
|
||||
LINT_SOURCE_SBOM
|
||||
LINT_SOURCE_SBOM_NO_ERROR
|
||||
SHOW_TABLE
|
||||
AUDIT
|
||||
AUDIT_NO_ERROR
|
||||
)
|
||||
set(single_args "")
|
||||
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(arg_GENERATE_JSON AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(op_args
|
||||
OP_KEY "GENERATE_JSON"
|
||||
OUT_VAR_DEPS_FOUND deps_found
|
||||
)
|
||||
if(arg_GENERATE_JSON_REQUIRED)
|
||||
list(APPEND op_args REQUIRED)
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
|
||||
if(deps_found)
|
||||
_qt_internal_sbom_generate_json()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_VERIFY_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(op_args
|
||||
OP_KEY "VERIFY_SBOM"
|
||||
OUT_VAR_DEPS_FOUND deps_found
|
||||
)
|
||||
if(arg_VERIFY_SBOM_REQUIRED)
|
||||
list(APPEND op_args REQUIRED)
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
|
||||
if(deps_found)
|
||||
_qt_internal_sbom_verify_valid()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(arg_VERIFY_NTIA_COMPLIANT AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(REQUIRED OP_KEY "RUN_NTIA")
|
||||
_qt_internal_sbom_verify_ntia_compliant()
|
||||
endif()
|
||||
|
||||
if(arg_SHOW_TABLE AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME sbom2doc REQUIRED)
|
||||
_qt_internal_sbom_show_table()
|
||||
endif()
|
||||
|
||||
if(arg_AUDIT AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(audit_no_error_option "")
|
||||
if(arg_AUDIT_NO_ERROR)
|
||||
set(audit_no_error_option NO_ERROR)
|
||||
endif()
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME sbomaudit REQUIRED)
|
||||
_qt_internal_sbom_audit(${audit_no_error_option})
|
||||
endif()
|
||||
|
||||
if(arg_GENERATE_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME reuse REQUIRED)
|
||||
_qt_internal_sbom_generate_reuse_source_sbom()
|
||||
endif()
|
||||
|
||||
if(arg_LINT_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
|
||||
set(lint_no_error_option "")
|
||||
if(arg_LINT_SOURCE_SBOM_NO_ERROR)
|
||||
set(lint_no_error_option NO_ERROR)
|
||||
endif()
|
||||
_qt_internal_sbom_find_python_dependency_program(NAME reuse REQUIRED)
|
||||
_qt_internal_sbom_run_reuse_lint(
|
||||
${lint_no_error_option}
|
||||
BUILD_TIME_SCRIPT_PATH_OUT_VAR reuse_lint_script
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper to find a python interpreter and a specific python dependency, e.g. to be able to generate
|
||||
# a SPDX JSON SBOM, or run post-installation steps like NTIA verification.
|
||||
# The exact dependency should be specified as the OP_KEY.
|
||||
#
|
||||
# Caches the found python executable in a separate cache var QT_INTERNAL_SBOM_PYTHON_EXECUTABLE, to
|
||||
# avoid conflicts with any other found python interpreter.
|
||||
function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
|
||||
set(opt_args
|
||||
REQUIRED
|
||||
)
|
||||
set(single_args
|
||||
OP_KEY
|
||||
OUT_VAR_DEPS_FOUND
|
||||
)
|
||||
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_OP_KEY)
|
||||
message(FATAL_ERROR "OP_KEY is required")
|
||||
endif()
|
||||
|
||||
set(supported_ops "GENERATE_JSON" "VERIFY_SBOM" "RUN_NTIA")
|
||||
|
||||
if(arg_OP_KEY STREQUAL "GENERATE_JSON" OR arg_OP_KEY STREQUAL "VERIFY_SBOM")
|
||||
set(import_statement "import spdx_tools.spdx.clitools.pyspdxtools")
|
||||
elseif(arg_OP_KEY STREQUAL "RUN_NTIA")
|
||||
set(import_statement "import ntia_conformance_checker.main")
|
||||
else()
|
||||
message(FATAL_ERROR "OP_KEY must be one of ${supported_ops}")
|
||||
endif()
|
||||
|
||||
# Return early if we found the dependencies.
|
||||
if(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY})
|
||||
if(arg_OUT_VAR_DEPS_FOUND)
|
||||
set(${arg_OUT_VAR_DEPS_FOUND} TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# NTIA-compliance checker requires Python 3.9 or later, so we use it as the minimum for all
|
||||
# SBOM OPs.
|
||||
set(required_version "3.9")
|
||||
|
||||
set(python_common_args
|
||||
VERSION "${required_version}"
|
||||
)
|
||||
|
||||
set(everything_found FALSE)
|
||||
|
||||
# On macOS FindPython prefers looking in the system framework location, but that usually would
|
||||
# not have the required dependencies. So we first look in it, and then fallback to any other
|
||||
# non-framework python found.
|
||||
if(CMAKE_HOST_APPLE)
|
||||
set(extra_python_args SEARCH_IN_FRAMEWORKS QUIET)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
|
||||
endif()
|
||||
|
||||
if(NOT everything_found)
|
||||
set(extra_python_args QUIET)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
|
||||
endif()
|
||||
|
||||
if(NOT everything_found)
|
||||
if(arg_REQUIRED)
|
||||
set(message_type "FATAL_ERROR")
|
||||
else()
|
||||
set(message_type "DEBUG")
|
||||
endif()
|
||||
|
||||
if(NOT python_found)
|
||||
# Look for python one more time, this time without QUIET, to show an error why it
|
||||
# wasn't found.
|
||||
if(arg_REQUIRED)
|
||||
_qt_internal_sbom_find_python_helper(${python_common_args}
|
||||
OUT_VAR_PYTHON_PATH unused_python
|
||||
OUT_VAR_PYTHON_FOUND unused_found
|
||||
)
|
||||
endif()
|
||||
message(${message_type} "Python ${required_version} for running SBOM ops not found.")
|
||||
elseif(NOT dep_found)
|
||||
message(${message_type} "Python dependency for running SBOM op ${arg_OP_KEY} "
|
||||
"not found:\n Python: ${python_path} \n Output: \n${dep_find_output}")
|
||||
endif()
|
||||
else()
|
||||
message(DEBUG "Using Python ${python_path} for running SBOM ops.")
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${python_path}" CACHE INTERNAL
|
||||
"Python interpeter used for SBOM generation.")
|
||||
endif()
|
||||
|
||||
set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
|
||||
"All dependencies found to run SBOM OP ${arg_OP_KEY}")
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_DEPS_FOUND)
|
||||
set(${arg_OUT_VAR_DEPS_FOUND} "${QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY}}"
|
||||
PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper to generate a SPDX JSON file from a tag/value format file.
|
||||
# This also implies some additional validity checks, useful to ensure a proper sbom file.
|
||||
function(_qt_internal_sbom_generate_json)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for generating SBOM json file.")
|
||||
endif()
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_GENERATE_JSON)
|
||||
message(FATAL_ERROR "Python dependencies not found for generating SBOM json file.")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Generating JSON: \${QT_SBOM_OUTPUT_PATH}.json\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"\${QT_SBOM_OUTPUT_PATH}\" -o \"\${QT_SBOM_OUTPUT_PATH}.json\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM conversion to JSON failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/convert_to_json.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to generate a tag/value SPDX file from a SPDX JSON format file.
|
||||
#
|
||||
# Will be used by WebEngine to convert the Chromium JSON file to a tag/value SPDX file.
|
||||
#
|
||||
# This conversion needs to happen before the document is referenced in the SBOM generation process,
|
||||
# so that the file already exists when it is parsed for its unique id and namespace.
|
||||
# It also needs to happen before verification codes are computed for the current document
|
||||
# that will depend on the target one, to ensure the the file exists and its checksum can be
|
||||
# computed.
|
||||
#
|
||||
# OPERATION_ID - a unique id for the operation, used to generate a unique cmake file name for
|
||||
# the SBOM generation process.
|
||||
#
|
||||
# INPUT_JSON_PATH - the absolute path to the input JSON file.
|
||||
#
|
||||
# OUTPUT_FILE_PATH - the absolute path where to create the output tag/value SPDX file.
|
||||
# Note that if the output file path is set, it is up to the caller to also copy / install the file
|
||||
# into the build and install directories where the build system expects to find all external
|
||||
# document references.
|
||||
#
|
||||
# OUTPUT_FILE_NAME - when OUTPUT_FILE_PATH is not specified, the output directory is automatically
|
||||
# set to the SBOM output directory. In this case OUTPUT_FILE_NAME can be used to override the
|
||||
# outout file name. If not specified, it will be derived from the input file name.
|
||||
#
|
||||
# OUT_VAR_OUTPUT_FILE_NAME - output variable where to store the output file.
|
||||
#
|
||||
# OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH - output variable where to store the output file path.
|
||||
# Note that the path will contain an unresolved '${QT_SBOM_OUTPUT_DIR}' which only has a value at
|
||||
# install time. So the path can't be used sensibly during configure time.
|
||||
function(_qt_internal_sbom_generate_tag_value_spdx_document)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
OPERATION_ID
|
||||
INPUT_JSON_FILE_PATH
|
||||
OUTPUT_FILE_PATH
|
||||
OUTPUT_FILE_NAME
|
||||
OUT_VAR_OUTPUT_FILE_NAME
|
||||
OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH
|
||||
)
|
||||
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 QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for generating tag/value file from JSON.")
|
||||
endif()
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_GENERATE_JSON)
|
||||
message(FATAL_ERROR
|
||||
"Python dependencies not found for generating tag/value file from JSON.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OPERATION_ID)
|
||||
message(FATAL_ERROR "OPERATION_ID is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_INPUT_JSON_FILE_PATH)
|
||||
message(FATAL_ERROR "INPUT_JSON_FILE_PATH is required")
|
||||
endif()
|
||||
|
||||
if(arg_OUTPUT_FILE_PATH)
|
||||
set(output_path "${arg_OUTPUT_FILE_PATH}")
|
||||
else()
|
||||
if(arg_OUTPUT_FILE_NAME)
|
||||
set(output_name "${arg_OUTPUT_FILE_NAME}")
|
||||
else()
|
||||
# Use the input file name without the last extension (without .json) as the output name.
|
||||
get_filename_component(output_name "${arg_INPUT_JSON_FILE_PATH}" NAME_WLE)
|
||||
endif()
|
||||
set(output_path "\${QT_SBOM_OUTPUT_DIR}/${output_name}")
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_OUTPUT_FILE_NAME)
|
||||
get_filename_component(output_name_resolved "${output_path}" NAME)
|
||||
set(${arg_OUT_VAR_OUTPUT_FILE_NAME} "${output_name_resolved}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
if(arg_OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH)
|
||||
set(${arg_OUT_VAR_OUTPUT_ABSOLUTE_FILE_PATH} "${output_path}" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS
|
||||
\"Generating tag/value SPDX document: ${output_path} from \"
|
||||
\"${arg_INPUT_JSON_FILE_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"${arg_INPUT_JSON_FILE_PATH}\" -o \"${output_path}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM conversion to tag/value failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(convert_sbom "${sbom_dir}/convert_to_tag_value_${arg_OPERATION_ID}.cmake")
|
||||
file(GENERATE OUTPUT "${convert_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files
|
||||
"${convert_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to verify the generated sbom is valid.
|
||||
function(_qt_internal_sbom_verify_valid)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for verifying SBOM file.")
|
||||
endif()
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_VERIFY_SBOM)
|
||||
message(FATAL_ERROR "Python dependencies not found for verifying SBOM file")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Verifying: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m spdx_tools.spdx.clitools.pyspdxtools
|
||||
-i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM verification failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/verify_valid.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to verify the generated sbom is NTIA compliant.
|
||||
function(_qt_internal_sbom_verify_ntia_compliant)
|
||||
if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
||||
message(FATAL_ERROR "Python interpreter not found for verifying SBOM file.")
|
||||
endif()
|
||||
|
||||
if(NOT QT_INTERNAL_SBOM_DEPS_FOUND_FOR_RUN_NTIA)
|
||||
message(FATAL_ERROR "Python dependencies not found for running the SBOM NTIA checker.")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Checking for NTIA compliance: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE} -m ntia_conformance_checker.main
|
||||
--file \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM NTIA verification failed: \{res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/verify_ntia.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to show the main sbom document info in the form of a CLI table.
|
||||
function(_qt_internal_sbom_show_table)
|
||||
set(extra_code_begin "")
|
||||
if(DEFINED ENV{COIN_UNIQUE_JOB_ID})
|
||||
# The output of the process dynamically adjusts the width of the shown table based on the
|
||||
# console width. In the CI, the width is very short for some reason, and thus the output
|
||||
# is truncated in the CI log. Explicitly set a bigger width to avoid this.
|
||||
set(extra_code_begin "
|
||||
set(backup_env_columns \$ENV{COLUMNS})
|
||||
set(ENV{COLUMNS} 150)
|
||||
")
|
||||
set(extra_code_end "
|
||||
set(ENV{COLUMNS} \${backup_env_columns})
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Showing main SBOM document info: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
|
||||
${extra_code_begin}
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_SBOM2DOC} -i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${extra_code_end}
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Showing SBOM document failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/show_table.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Helper to audit the generated sbom.
|
||||
function(_qt_internal_sbom_audit)
|
||||
set(opt_args NO_ERROR)
|
||||
set(single_args "")
|
||||
set(multi_args "")
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"SBOM Audit failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Auditing SBOM: \${QT_SBOM_OUTPUT_PATH}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_SBOMAUDIT} -i \"\${QT_SBOM_OUTPUT_PATH}\"
|
||||
--disable-license-check --cpecheck --offline
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(verify_sbom "${sbom_dir}/audit.cmake")
|
||||
file(GENERATE OUTPUT "${verify_sbom}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
|
||||
endfunction()
|
||||
|
||||
# Returns path to project's potential root source reuse.toml file.
|
||||
function(_qt_internal_sbom_get_project_reuse_toml_path out_var)
|
||||
set(reuse_toml_path "${PROJECT_SOURCE_DIR}/REUSE.toml")
|
||||
set(${out_var} "${reuse_toml_path}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Helper to generate and install a source SBOM using reuse.
|
||||
function(_qt_internal_sbom_generate_reuse_source_sbom)
|
||||
set(opt_args NO_ERROR)
|
||||
set(single_args "")
|
||||
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_get_current_project_sbom_dir(sbom_dir)
|
||||
set(file_op "${sbom_dir}/generate_reuse_source_sbom.cmake")
|
||||
|
||||
_qt_internal_sbom_get_project_reuse_toml_path(reuse_toml_path)
|
||||
if(NOT EXISTS "${reuse_toml_path}" AND NOT QT_FORCE_SOURCE_SBOM_GENERATION)
|
||||
set(skip_message
|
||||
"Skipping source SBOM generation: No reuse.toml file found at '${reuse_toml_path}'.")
|
||||
message(STATUS "${skip_message}")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"${skip_message}\")
|
||||
")
|
||||
|
||||
file(GENERATE OUTPUT "${file_op}" CONTENT "${content}")
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_post_generation_include_files
|
||||
"${file_op}")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Source SBOM generation using reuse tool failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(source_sbom_path "\${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.source.spdx")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Generating source SBOM using reuse tool: ${source_sbom_path}\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_REUSE} --root \"${PROJECT_SOURCE_DIR}\" spdx
|
||||
-o ${source_sbom_path}
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
file(GENERATE OUTPUT "${file_op}" CONTENT "${content}")
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_post_generation_include_files "${file_op}")
|
||||
endfunction()
|
||||
|
||||
# Helper to run 'reuse lint' on the project source dir.
|
||||
function(_qt_internal_sbom_run_reuse_lint)
|
||||
set(opt_args
|
||||
NO_ERROR
|
||||
)
|
||||
set(single_args
|
||||
BUILD_TIME_SCRIPT_PATH_OUT_VAR
|
||||
)
|
||||
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 no reuse.toml file exists, it means the repo is likely not reuse compliant yet,
|
||||
# so we shouldn't error out during installation when running the lint.
|
||||
_qt_internal_sbom_get_project_reuse_toml_path(reuse_toml_path)
|
||||
if(NOT EXISTS "${reuse_toml_path}" AND NOT QT_FORCE_REUSE_LINT_ERROR)
|
||||
set(arg_NO_ERROR TRUE)
|
||||
endif()
|
||||
|
||||
set(handle_error "")
|
||||
if(NOT arg_NO_ERROR)
|
||||
set(handle_error "
|
||||
if(NOT res EQUAL 0)
|
||||
message(FATAL_ERROR \"Running 'reuse lint' failed: \${res}\")
|
||||
endif()
|
||||
")
|
||||
endif()
|
||||
|
||||
set(content "
|
||||
message(STATUS \"Running 'reuse lint' in '${PROJECT_SOURCE_DIR}'.\")
|
||||
execute_process(
|
||||
COMMAND ${QT_SBOM_PROGRAM_REUSE} --root \"${PROJECT_SOURCE_DIR}\" lint
|
||||
RESULT_VARIABLE res
|
||||
)
|
||||
${handle_error}
|
||||
")
|
||||
|
||||
_qt_internal_get_current_project_sbom_dir(sbom_dir)
|
||||
set(file_op_build "${sbom_dir}/run_reuse_lint_build.cmake")
|
||||
file(GENERATE OUTPUT "${file_op_build}" CONTENT "${content}")
|
||||
|
||||
# Allow skipping running 'reuse lint' during installation. But still allow running it during
|
||||
# build time. This is a fail safe opt-out in case some repo needs it.
|
||||
if(QT_FORCE_SKIP_REUSE_LINT_ON_INSTALL)
|
||||
set(skip_message "Skipping running 'reuse lint' in '${PROJECT_SOURCE_DIR}'.")
|
||||
|
||||
set(content "
|
||||
message(STATUS \"${skip_message}\")
|
||||
")
|
||||
set(file_op_install "${sbom_dir}/run_reuse_lint_install.cmake")
|
||||
file(GENERATE OUTPUT "${file_op_install}" CONTENT "${content}")
|
||||
else()
|
||||
# Just reuse the already generated script for installation as well.
|
||||
set(file_op_install "${file_op_build}")
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${file_op_install}")
|
||||
|
||||
if(arg_BUILD_TIME_SCRIPT_PATH_OUT_VAR)
|
||||
set(${arg_BUILD_TIME_SCRIPT_PATH_OUT_VAR} "${file_op_build}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
444
cmake/QtPublicSbomPurlHelpers.cmake
Normal file
444
cmake/QtPublicSbomPurlHelpers.cmake
Normal file
@ -0,0 +1,444 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Parse purl arguments for a specific purl variant, e.g. for parsing all values of arg_PURL_QT_ARGS.
|
||||
# arguments_var_name is the variable name that contains the args.
|
||||
macro(_qt_internal_sbom_parse_purl_variant_options prefix arguments_var_name)
|
||||
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
|
||||
|
||||
cmake_parse_arguments(arg "${purl_opt_args}" "${purl_single_args}" "${purl_multi_args}"
|
||||
${${arguments_var_name}})
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
endmacro()
|
||||
|
||||
# Returns a vcs url where for purls where qt entities of the current repo are hosted.
|
||||
function(_qt_internal_sbom_get_qt_entity_vcs_url target)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
REPO_NAME
|
||||
VERSION
|
||||
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)
|
||||
|
||||
if(NOT arg_REPO_NAME)
|
||||
message(FATAL_ERROR "REPO_NAME must be set")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
set(version_part "")
|
||||
if(arg_VERSION)
|
||||
set(version_part "@${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
set(vcs_url "https://code.qt.io/qt/${arg_REPO_NAME}.git${version_part}")
|
||||
set(${arg_OUT_VAR} "${vcs_url}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Returns a relative path to the source where the target was created, to be embedded into a
|
||||
# mirror purl as a subpath.
|
||||
function(_qt_internal_sbom_get_qt_entity_repo_source_dir target)
|
||||
set(opt_args "")
|
||||
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)
|
||||
|
||||
if(NOT arg_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
get_target_property(repo_source_dir "${target}" SOURCE_DIR)
|
||||
|
||||
# Get the path relative to the PROJECT_SOURCE_DIR
|
||||
file(RELATIVE_PATH relative_repo_source_dir "${PROJECT_SOURCE_DIR}" "${repo_source_dir}")
|
||||
|
||||
set(sub_path "${relative_repo_source_dir}")
|
||||
set(${arg_OUT_VAR} "${sub_path}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Handles purl arguments specified to functions like qt_internal_add_sbom.
|
||||
# Currently accepts arguments for 3 variants of purls, each of which will generate a separate purl.
|
||||
# If no arguments are specified, for qt entity types, default values will be chosen.
|
||||
#
|
||||
# Purl variants:
|
||||
# - PURL_QT_ARGS
|
||||
# args to override Qt's generic purl for Qt modules or patched 3rd party libs
|
||||
# defaults to something like pkg:generic/TheQtCompany/${repo_name}-${target}@SHA1
|
||||
# - PURL_MIRROR_ARGS
|
||||
# args to override Qt's mirror purl, which is hosted on github
|
||||
# defaults to something like pkg:github/qt/${repo_name}@SHA1
|
||||
# - PURL_3RDPARTY_UPSTREAM_ARGS
|
||||
# args to specify a purl pointing to an upstream repo, usually to github or another forge
|
||||
# no defaults, but could look like: pkg:github/harfbuzz/harfbuzz@v8.5.0
|
||||
# Example values for harfbuzz:
|
||||
# PURL_3RDPARTY_UPSTREAM_ARGS
|
||||
# PURL_TYPE "github"
|
||||
# PURL_NAMESPACE "harfbuzz"
|
||||
# PURL_NAME "harfbuzz"
|
||||
# PURL_VERSION "v8.5.0" # tag
|
||||
function(_qt_internal_sbom_handle_purl_values target)
|
||||
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
|
||||
list(APPEND single_args OUT_VAR)
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
if(NOT arg_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
# List of purl variants to process.
|
||||
set(purl_variants "")
|
||||
|
||||
set(third_party_types
|
||||
QT_THIRD_PARTY_MODULE
|
||||
QT_THIRD_PARTY_SOURCES
|
||||
)
|
||||
|
||||
if(arg_IS_QT_ENTITY_TYPE)
|
||||
# Qt entities have two purls by default, a QT generic one and a MIRROR hosted on github.
|
||||
list(APPEND purl_variants MIRROR QT)
|
||||
elseif(arg_TYPE IN_LIST third_party_types)
|
||||
# Third party libraries vendored in Qt also have at least two purls, like regular Qt
|
||||
# libraries, but might also have an upstream one.
|
||||
|
||||
# The order in which the purls are generated matters for tools that consume the SBOM. Some
|
||||
# tools can only handle one PURL per package, so the first one should be the important one.
|
||||
# For now, I deem that the upstream one if present. Otherwise the github mirror.
|
||||
if(arg_PURL_3RDPARTY_UPSTREAM_ARGS)
|
||||
list(APPEND purl_variants 3RDPARTY_UPSTREAM)
|
||||
endif()
|
||||
|
||||
list(APPEND purl_variants MIRROR QT)
|
||||
else()
|
||||
# If handling another entity type, handle based on whether any of the purl arguments are
|
||||
# set.
|
||||
set(known_purl_variants QT MIRROR 3RDPARTY_UPSTREAM)
|
||||
foreach(known_purl_variant IN LISTS known_purl_variants)
|
||||
if(arg_PURL_${known_purl_variant}_ARGS)
|
||||
list(APPEND purl_variants ${known_purl_variant})
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(arg_IS_QT_ENTITY_TYPE
|
||||
OR arg_TYPE STREQUAL "QT_THIRD_PARTY_MODULE"
|
||||
OR arg_TYPE STREQUAL "QT_THIRD_PARTY_SOURCES"
|
||||
)
|
||||
set(is_qt_purl_entity_type TRUE)
|
||||
else()
|
||||
set(is_qt_purl_entity_type FALSE)
|
||||
endif()
|
||||
|
||||
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
|
||||
|
||||
set(project_package_options "")
|
||||
|
||||
foreach(purl_variant IN LISTS purl_variants)
|
||||
# Clear previous values.
|
||||
foreach(option_name IN LISTS purl_opt_args purl_single_args purl_multi_args)
|
||||
unset(arg_${option_name})
|
||||
endforeach()
|
||||
|
||||
_qt_internal_sbom_parse_purl_variant_options(arg arg_PURL_${purl_variant}_ARGS)
|
||||
|
||||
# Check if custom purl args were specified.
|
||||
set(purl_args_available FALSE)
|
||||
if(arg_PURL_${purl_variant}_ARGS)
|
||||
set(purl_args_available TRUE)
|
||||
endif()
|
||||
|
||||
# We want to create a purl either if it's one of Qt's entities and one of it's default
|
||||
# purl types, or if custom args were specified.
|
||||
set(consider_purl_processing FALSE)
|
||||
if((purl_args_available OR is_qt_purl_entity_type) AND NOT arg_NO_PURL)
|
||||
set(consider_purl_processing TRUE)
|
||||
endif()
|
||||
|
||||
if(consider_purl_processing)
|
||||
set(purl_args "")
|
||||
|
||||
# Override the purl version with the package version.
|
||||
if(arg_PURL_USE_PACKAGE_VERSION AND arg_VERSION)
|
||||
set(arg_PURL_VERSION "${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
# Append a vcs_url to the qualifiers if specified.
|
||||
if(arg_PURL_VCS_URL)
|
||||
list(APPEND arg_PURL_QUALIFIERS "vcs_url=${arg_PURL_VCS_URL}")
|
||||
endif()
|
||||
|
||||
_qt_internal_forward_function_args(
|
||||
FORWARD_APPEND
|
||||
FORWARD_PREFIX arg
|
||||
FORWARD_OUT_VAR purl_args
|
||||
FORWARD_OPTIONS
|
||||
${purl_opt_args}
|
||||
FORWARD_SINGLE
|
||||
${purl_single_args}
|
||||
FORWARD_MULTI
|
||||
${purl_multi_args}
|
||||
)
|
||||
|
||||
# Qt entity types get special treatment purl.
|
||||
if(is_qt_purl_entity_type AND NOT arg_NO_DEFAULT_QT_PURL AND
|
||||
(purl_variant STREQUAL "QT" OR purl_variant STREQUAL "MIRROR"))
|
||||
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
|
||||
|
||||
# Add a vcs_url to the generic QT variant.
|
||||
if(purl_variant STREQUAL "QT")
|
||||
set(entity_vcs_url_version_option "")
|
||||
# Can be empty.
|
||||
if(QT_SBOM_GIT_HASH_SHORT)
|
||||
set(entity_vcs_url_version_option VERSION "${QT_SBOM_GIT_HASH_SHORT}")
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_get_qt_entity_vcs_url(${target}
|
||||
REPO_NAME "${repo_project_name_lowercase}"
|
||||
${entity_vcs_url_version_option}
|
||||
OUT_VAR vcs_url)
|
||||
list(APPEND purl_args PURL_QUALIFIERS "vcs_url=${vcs_url}")
|
||||
endif()
|
||||
|
||||
# Add the subdirectory path where the target was created as a custom qualifier.
|
||||
_qt_internal_sbom_get_qt_entity_repo_source_dir(${target} OUT_VAR sub_path)
|
||||
if(sub_path)
|
||||
list(APPEND purl_args PURL_SUBPATH "${sub_path}")
|
||||
endif()
|
||||
|
||||
# Add the target name as a custom qualifer.
|
||||
list(APPEND purl_args PURL_QUALIFIERS "library_name=${target}")
|
||||
|
||||
# Can be empty.
|
||||
if(QT_SBOM_GIT_HASH_SHORT)
|
||||
list(APPEND purl_args VERSION "${QT_SBOM_GIT_HASH_SHORT}")
|
||||
endif()
|
||||
|
||||
# Get purl args the Qt entity type, taking into account defaults.
|
||||
_qt_internal_sbom_get_qt_entity_purl_args(${target}
|
||||
NAME "${repo_project_name_lowercase}-${target}"
|
||||
REPO_NAME "${repo_project_name_lowercase}"
|
||||
SUPPLIER "${arg_SUPPLIER}"
|
||||
PURL_VARIANT "${purl_variant}"
|
||||
${purl_args}
|
||||
OUT_VAR purl_args
|
||||
)
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_assemble_purl(${target}
|
||||
${purl_args}
|
||||
OUT_VAR package_manager_external_ref
|
||||
)
|
||||
list(APPEND project_package_options ${package_manager_external_ref})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(direct_values
|
||||
PURL_QT_VALUES
|
||||
PURL_MIRROR_VALUES
|
||||
PURL_3RDPARTY_UPSTREAM_VALUES
|
||||
)
|
||||
|
||||
foreach(direct_value IN LISTS direct_values)
|
||||
if(arg_${direct_value})
|
||||
set(direct_values_per_type "")
|
||||
foreach(direct_value IN LISTS arg_${direct_value})
|
||||
_qt_internal_sbom_get_purl_value_extref(
|
||||
VALUE "${direct_value}" OUT_VAR package_manager_external_ref)
|
||||
|
||||
list(APPEND direct_values_per_type ${package_manager_external_ref})
|
||||
endforeach()
|
||||
# The order in which the purls are generated, matters for tools that consume the SBOM.
|
||||
# Some tools can only handle one PURL per package, so the first one should be the
|
||||
# important one.
|
||||
# For now, I deem that the directly specified ones (probably via a qt_attribution.json
|
||||
# file) are the more important ones. So we prepend them.
|
||||
list(PREPEND project_package_options ${direct_values_per_type})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(${arg_OUT_VAR} "${project_package_options}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Gets a list of arguments to pass to _qt_internal_sbom_assemble_purl when handling a Qt entity
|
||||
# type. The purl for Qt entity types have Qt-specific defaults, but can be overridden per purl
|
||||
# component.
|
||||
# The arguments are saved in OUT_VAR.
|
||||
function(_qt_internal_sbom_get_qt_entity_purl_args target)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
NAME
|
||||
REPO_NAME
|
||||
SUPPLIER
|
||||
VERSION
|
||||
PURL_VARIANT
|
||||
OUT_VAR
|
||||
)
|
||||
set(multi_args "")
|
||||
|
||||
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
|
||||
list(APPEND opt_args ${purl_opt_args})
|
||||
list(APPEND single_args ${purl_single_args})
|
||||
list(APPEND multi_args ${purl_multi_args})
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(supported_purl_variants QT MIRROR)
|
||||
if(NOT arg_PURL_VARIANT IN_LIST supported_purl_variants)
|
||||
message(FATAL_ERROR "PURL_VARIANT unknown: ${arg_PURL_VARIANT}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_VARIANT STREQUAL "QT")
|
||||
set(purl_type "generic")
|
||||
set(purl_namespace "${arg_SUPPLIER}")
|
||||
set(purl_name "${arg_NAME}")
|
||||
set(purl_version "${arg_VERSION}")
|
||||
elseif(arg_PURL_VARIANT STREQUAL "MIRROR")
|
||||
set(purl_type "github")
|
||||
set(purl_namespace "qt")
|
||||
set(purl_name "${arg_REPO_NAME}")
|
||||
set(purl_version "${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_TYPE)
|
||||
set(purl_type "${arg_PURL_TYPE}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_NAMESPACE)
|
||||
set(purl_namespace "${arg_PURL_NAMESPACE}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_NAME)
|
||||
set(purl_name "${arg_PURL_NAME}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_VERSION)
|
||||
set(purl_version "${arg_PURL_VERSION}")
|
||||
endif()
|
||||
|
||||
set(purl_version_option "")
|
||||
if(purl_version)
|
||||
set(purl_version_option PURL_VERSION "${purl_version}")
|
||||
endif()
|
||||
|
||||
set(purl_args
|
||||
PURL_TYPE "${purl_type}"
|
||||
PURL_NAMESPACE "${purl_namespace}"
|
||||
PURL_NAME "${purl_name}"
|
||||
${purl_version_option}
|
||||
)
|
||||
|
||||
if(arg_PURL_QUALIFIERS)
|
||||
list(APPEND purl_args PURL_QUALIFIERS "${arg_PURL_QUALIFIERS}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_SUBPATH)
|
||||
list(APPEND purl_args PURL_SUBPATH "${arg_PURL_SUBPATH}")
|
||||
endif()
|
||||
|
||||
set(${arg_OUT_VAR} "${purl_args}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Assembles an external reference purl identifier.
|
||||
# PURL_TYPE and PURL_NAME are required.
|
||||
# Stores the result in the OUT_VAR.
|
||||
# Accepted options:
|
||||
# PURL_TYPE
|
||||
# PURL_NAME
|
||||
# PURL_NAMESPACE
|
||||
# PURL_VERSION
|
||||
# PURL_SUBPATH
|
||||
# PURL_QUALIFIERS
|
||||
function(_qt_internal_sbom_assemble_purl target)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
OUT_VAR
|
||||
)
|
||||
set(multi_args "")
|
||||
|
||||
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
|
||||
list(APPEND opt_args ${purl_opt_args})
|
||||
list(APPEND single_args ${purl_single_args})
|
||||
list(APPEND multi_args ${purl_multi_args})
|
||||
|
||||
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(purl_scheme "pkg")
|
||||
|
||||
if(NOT arg_PURL_TYPE)
|
||||
message(FATAL_ERROR "PURL_TYPE must be set")
|
||||
endif()
|
||||
|
||||
if(NOT arg_PURL_NAME)
|
||||
message(FATAL_ERROR "PURL_NAME must be set")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
# https://github.com/package-url/purl-spec
|
||||
# Spec is 'scheme:type/namespace/name@version?qualifiers#subpath'
|
||||
set(purl "${purl_scheme}:${arg_PURL_TYPE}")
|
||||
|
||||
if(arg_PURL_NAMESPACE)
|
||||
string(APPEND purl "/${arg_PURL_NAMESPACE}")
|
||||
endif()
|
||||
|
||||
string(APPEND purl "/${arg_PURL_NAME}")
|
||||
|
||||
if(arg_PURL_VERSION)
|
||||
string(APPEND purl "@${arg_PURL_VERSION}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_QUALIFIERS)
|
||||
# TODO: Note that the qualifiers are expected to be URL encoded, which this implementation
|
||||
# is not doing at the moment.
|
||||
list(JOIN arg_PURL_QUALIFIERS "&" qualifiers)
|
||||
string(APPEND purl "?${qualifiers}")
|
||||
endif()
|
||||
|
||||
if(arg_PURL_SUBPATH)
|
||||
string(APPEND purl "#${arg_PURL_SUBPATH}")
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR result)
|
||||
|
||||
set(${arg_OUT_VAR} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Takes a PURL VALUE and returns an SBOM purl external reference in OUT_VAR.
|
||||
function(_qt_internal_sbom_get_purl_value_extref)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
OUT_VAR
|
||||
VALUE
|
||||
)
|
||||
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_OUT_VAR)
|
||||
message(FATAL_ERROR "OUT_VAR must be set")
|
||||
endif()
|
||||
|
||||
if(NOT arg_VALUE)
|
||||
message(FATAL_ERROR "VALUE must be set")
|
||||
endif()
|
||||
|
||||
# SPDX SBOM External reference type.
|
||||
set(ext_ref_prefix "PACKAGE-MANAGER purl")
|
||||
set(external_ref "${ext_ref_prefix} ${arg_VALUE}")
|
||||
set(result "EXTREF" "${external_ref}")
|
||||
set(${arg_OUT_VAR} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
250
cmake/QtPublicSbomPythonHelpers.cmake
Normal file
250
cmake/QtPublicSbomPythonHelpers.cmake
Normal file
@ -0,0 +1,250 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Helper macro to find python and a given dependency. Expects the caller to set all of the vars.
|
||||
# Meant to reduce the line noise due to the repeated calls.
|
||||
macro(_qt_internal_sbom_find_python_and_dependency_helper_lambda)
|
||||
_qt_internal_sbom_find_python_and_dependency_helper(
|
||||
PYTHON_ARGS
|
||||
${extra_python_args}
|
||||
${python_common_args}
|
||||
DEPENDENCY_ARGS
|
||||
DEPENDENCY_IMPORT_STATEMENT "${import_statement}"
|
||||
OUT_VAR_PYTHON_PATH python_path
|
||||
OUT_VAR_PYTHON_FOUND python_found
|
||||
OUT_VAR_DEP_FOUND dep_found
|
||||
OUT_VAR_PYTHON_AND_DEP_FOUND everything_found
|
||||
OUT_VAR_DEP_FIND_OUTPUT dep_find_output
|
||||
)
|
||||
endmacro()
|
||||
|
||||
# Tries to find python and a given dependency based on the args passed to PYTHON_ARGS and
|
||||
# DEPENDENCY_ARGS which are forwarded to the respective finding functions.
|
||||
# Returns the path to the python interpreter, whether it was found, whether the dependency was
|
||||
# found, whether both were found, and the reason why the dependency might not be found.
|
||||
function(_qt_internal_sbom_find_python_and_dependency_helper)
|
||||
set(opt_args)
|
||||
set(single_args
|
||||
OUT_VAR_PYTHON_PATH
|
||||
OUT_VAR_PYTHON_FOUND
|
||||
OUT_VAR_DEP_FOUND
|
||||
OUT_VAR_PYTHON_AND_DEP_FOUND
|
||||
OUT_VAR_DEP_FIND_OUTPUT
|
||||
)
|
||||
set(multi_args
|
||||
PYTHON_ARGS
|
||||
DEPENDENCY_ARGS
|
||||
)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(everything_found_inner FALSE)
|
||||
set(deps_find_output_inner "")
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_PATH)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_PATH var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_DEP_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_DEP_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_AND_DEP_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_AND_DEP_FOUND var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_DEP_FIND_OUTPUT)
|
||||
message(FATAL_ERROR "OUT_VAR_DEP_FIND_OUTPUT var is required")
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_find_python_helper(
|
||||
${arg_PYTHON_ARGS}
|
||||
OUT_VAR_PYTHON_PATH python_path_inner
|
||||
OUT_VAR_PYTHON_FOUND python_found_inner
|
||||
)
|
||||
|
||||
if(python_found_inner AND python_path_inner)
|
||||
_qt_internal_sbom_find_python_dependency_helper(
|
||||
${arg_DEPENDENCY_ARGS}
|
||||
PYTHON_PATH "${python_path_inner}"
|
||||
OUT_VAR_FOUND dep_found_inner
|
||||
OUT_VAR_OUTPUT dep_find_output_inner
|
||||
)
|
||||
|
||||
if(dep_found_inner)
|
||||
set(everything_found_inner TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${arg_OUT_VAR_PYTHON_PATH} "${python_path_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_FOUND} "${python_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_DEP_FOUND} "${dep_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_AND_DEP_FOUND} "${everything_found_inner}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_DEP_FIND_OUTPUT} "${dep_find_output_inner}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Tries to find the python intrepreter, given the QT_SBOM_PYTHON_INTERP path hint, as well as
|
||||
# other options.
|
||||
# Ignores any previously found python.
|
||||
# Returns the python interpreter path and whether it was successfully found.
|
||||
#
|
||||
# This is intentionally a function, and not a macro, to prevent overriding the Python3_EXECUTABLE
|
||||
# non-cache variable in a global scope in case if a different python is found and used for a
|
||||
# different purpose (e.g. qtwebengine or qtinterfaceframework).
|
||||
# The reason to use a different python is that an already found python might not be the version we
|
||||
# need, or might lack the dependencies we need.
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/issues/21797#note_901621 claims that finding multiple
|
||||
# python versions in separate directory scopes is possible, and I claim a function scope is as
|
||||
# good as a directory scope.
|
||||
function(_qt_internal_sbom_find_python_helper)
|
||||
set(opt_args
|
||||
SEARCH_IN_FRAMEWORKS
|
||||
QUIET
|
||||
)
|
||||
set(single_args
|
||||
VERSION
|
||||
OUT_VAR_PYTHON_PATH
|
||||
OUT_VAR_PYTHON_FOUND
|
||||
)
|
||||
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_OUT_VAR_PYTHON_PATH)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_PATH var is required")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_PYTHON_FOUND)
|
||||
message(FATAL_ERROR "OUT_VAR_PYTHON_FOUND var is required")
|
||||
endif()
|
||||
|
||||
# Allow disabling looking for a python interpreter shipped as part of a macOS system framework.
|
||||
if(NOT arg_SEARCH_IN_FRAMEWORKS)
|
||||
set(Python3_FIND_FRAMEWORK NEVER)
|
||||
endif()
|
||||
|
||||
set(required_version "")
|
||||
if(arg_VERSION)
|
||||
set(required_version "${arg_VERSION}")
|
||||
endif()
|
||||
|
||||
set(find_quiet "")
|
||||
if(arg_QUIET)
|
||||
set(find_quiet "QUIET")
|
||||
endif()
|
||||
|
||||
# Locally reset any executable that was possibly already found.
|
||||
# We do this to ensure we always re-do the lookup/
|
||||
# This needs to be set to an empty string, to override any cache variable
|
||||
set(Python3_EXECUTABLE "")
|
||||
|
||||
# This needs to be unset, because the Python module checks whether the variable is defined, not
|
||||
# whether it is empty.
|
||||
unset(_Python3_EXECUTABLE)
|
||||
|
||||
if(QT_SBOM_PYTHON_INTERP)
|
||||
set(Python3_ROOT_DIR ${QT_SBOM_PYTHON_INTERP})
|
||||
endif()
|
||||
|
||||
find_package(Python3 ${required_version} ${find_quiet} COMPONENTS Interpreter)
|
||||
|
||||
set(${arg_OUT_VAR_PYTHON_PATH} "${Python3_EXECUTABLE}" PARENT_SCOPE)
|
||||
set(${arg_OUT_VAR_PYTHON_FOUND} "${Python3_Interpreter_FOUND}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Helper that takes an python import statement to run using the given python interpreter path,
|
||||
# to confirm that the given python dependency can be found.
|
||||
# Returns whether the dependency was found and the output of running the import, for error handling.
|
||||
function(_qt_internal_sbom_find_python_dependency_helper)
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
DEPENDENCY_IMPORT_STATEMENT
|
||||
PYTHON_PATH
|
||||
OUT_VAR_FOUND
|
||||
OUT_VAR_OUTPUT
|
||||
)
|
||||
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_PYTHON_PATH)
|
||||
message(FATAL_ERROR "Python interpreter path not given.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_DEPENDENCY_IMPORT_STATEMENT)
|
||||
message(FATAL_ERROR "Python depdendency import statement not given.")
|
||||
endif()
|
||||
|
||||
if(NOT arg_OUT_VAR_FOUND)
|
||||
message(FATAL_ERROR "Out var found variable not given.")
|
||||
endif()
|
||||
|
||||
set(python_path "${arg_PYTHON_PATH}")
|
||||
execute_process(
|
||||
COMMAND
|
||||
${python_path} -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
|
||||
RESULT_VARIABLE res
|
||||
OUTPUT_VARIABLE output
|
||||
ERROR_VARIABLE output
|
||||
)
|
||||
|
||||
if("${res}" STREQUAL "0")
|
||||
set(found TRUE)
|
||||
set(output "${output}")
|
||||
else()
|
||||
set(found FALSE)
|
||||
string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} not found. "
|
||||
"Error:\n${output}")
|
||||
endif()
|
||||
|
||||
set(${arg_OUT_VAR_FOUND} "${found}" PARENT_SCOPE)
|
||||
if(arg_OUT_VAR_OUTPUT)
|
||||
set(${arg_OUT_VAR_OUTPUT} "${output}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper to find a python installed CLI utility.
|
||||
# Expected to be in PATH.
|
||||
function(_qt_internal_sbom_find_python_dependency_program)
|
||||
set(opt_args
|
||||
REQUIRED
|
||||
)
|
||||
set(single_args
|
||||
NAME
|
||||
)
|
||||
set(multi_args "")
|
||||
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
||||
_qt_internal_validate_all_args_are_parsed(arg)
|
||||
|
||||
set(program_name "${arg_NAME}")
|
||||
string(TOUPPER "${program_name}" upper_name)
|
||||
set(cache_var "QT_SBOM_PROGRAM_${upper_name}")
|
||||
|
||||
set(hints "")
|
||||
|
||||
# The path to python installed apps is different on Windows compared to UNIX, so we use
|
||||
# a different path than where the python interpreter might be located.
|
||||
if(QT_SBOM_PYTHON_APPS_PATH)
|
||||
list(APPEND hints ${QT_SBOM_PYTHON_APPS_PATH})
|
||||
endif()
|
||||
|
||||
find_program(${cache_var}
|
||||
NAMES ${program_name}
|
||||
HINTS ${hints}
|
||||
)
|
||||
|
||||
if(NOT ${cache_var})
|
||||
if(arg_REQUIRED)
|
||||
set(message_type "FATAL_ERROR")
|
||||
set(prefix "Required ")
|
||||
else()
|
||||
set(message_type "STATUS")
|
||||
set(prefix "Optional ")
|
||||
endif()
|
||||
message(${message_type} "${prefix}SBOM python program '${program_name}' not found.")
|
||||
endif()
|
||||
endfunction()
|
162
cmake/QtPublicSbomSystemDepHelpers.cmake
Normal file
162
cmake/QtPublicSbomSystemDepHelpers.cmake
Normal file
@ -0,0 +1,162 @@
|
||||
# Copyright (C) 2024 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Records information about a system library target, usually due to a qt_find_package call.
|
||||
# This information is later used to generate packages for the system libraries, but only after
|
||||
# confirming that the library was used (linked) into any of the Qt targets.
|
||||
function(_qt_internal_sbom_record_system_library_usage target)
|
||||
if(NOT QT_GENERATE_SBOM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(opt_args "")
|
||||
set(single_args
|
||||
TYPE
|
||||
PACKAGE_VERSION
|
||||
FRIENDLY_PACKAGE_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 arg_TYPE)
|
||||
message(FATAL_ERROR "TYPE must be set")
|
||||
endif()
|
||||
|
||||
# A package might be looked up more than once, make sure to record it once.
|
||||
get_property(already_recorded GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_target_${target})
|
||||
|
||||
if(already_recorded)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_target_${target} TRUE)
|
||||
|
||||
# Defer spdx id creation until _qt_internal_sbom_begin_project is called, so we know the
|
||||
# project name. The project name is used in the package infix generation of the system library,
|
||||
# but _qt_internal_sbom_record_system_library_usage might be called before sbom generation
|
||||
# has started, e.g. during _qt_internal_find_third_party_dependencies.
|
||||
set(spdx_options
|
||||
${target}
|
||||
TYPE "${arg_TYPE}"
|
||||
PACKAGE_NAME "${arg_FRIENDLY_PACKAGE_NAME}"
|
||||
)
|
||||
|
||||
get_cmake_property(sbom_repo_begin_called _qt_internal_sbom_repo_begin_called)
|
||||
if(sbom_repo_begin_called AND TARGET "${target}")
|
||||
_qt_internal_sbom_record_system_library_spdx_id(${target} ${spdx_options})
|
||||
else()
|
||||
set_property(GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_spdx_options_${target} "${spdx_options}")
|
||||
endif()
|
||||
|
||||
# Defer sbom info creation until we detect usage of the system library (whether the library is
|
||||
# linked into any other target).
|
||||
set_property(GLOBAL APPEND PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_targets "${target}")
|
||||
set_property(GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_options_${target} "${ARGN}")
|
||||
endfunction()
|
||||
|
||||
# Helper to record spdx ids of all system library targets that were found so far.
|
||||
function(_qt_internal_sbom_record_system_library_spdx_ids)
|
||||
get_property(recorded_targets GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets)
|
||||
|
||||
if(NOT recorded_targets)
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(target IN LISTS recorded_targets)
|
||||
get_property(args GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_spdx_options_${target})
|
||||
|
||||
# qt_find_package PROVIDED_TARGETS might refer to non-existent targets in certain cases,
|
||||
# like zstd::libzstd_shared for qt_find_package(WrapZSTD), because we are not sure what
|
||||
# kind of zstd build was done. Make sure to check if the target exists before recording it.
|
||||
if(TARGET "${target}")
|
||||
set(target_unaliased "${target}")
|
||||
get_target_property(aliased_target "${target}" ALIASED_TARGET)
|
||||
if(aliased_target)
|
||||
set(target_unaliased ${aliased_target})
|
||||
endif()
|
||||
|
||||
_qt_internal_sbom_record_system_library_spdx_id(${target_unaliased} ${args})
|
||||
else()
|
||||
message(DEBUG
|
||||
"Skipping recording system library for SBOM because target does not exist: "
|
||||
" ${target}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Helper to record the spdx id of a system library target.
|
||||
function(_qt_internal_sbom_record_system_library_spdx_id target)
|
||||
# Save the spdx id before the sbom info is added, so we can refer to it in relationships.
|
||||
_qt_internal_sbom_record_target_spdx_id(${ARGN} OUT_VAR package_spdx_id)
|
||||
|
||||
if(NOT package_spdx_id)
|
||||
message(FATAL_ERROR "Could not generate spdx id for system library target: ${target}")
|
||||
endif()
|
||||
|
||||
set_property(GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_package_${target} "${package_spdx_id}")
|
||||
endfunction()
|
||||
|
||||
# Goes through the list of consumed system libraries (those that were linked in) and creates
|
||||
# sbom packages for them.
|
||||
# Uses information from recorded system libraries (calls to qt_find_package).
|
||||
function(_qt_internal_sbom_add_recorded_system_libraries)
|
||||
get_property(recorded_targets GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets)
|
||||
get_property(consumed_targets GLOBAL PROPERTY _qt_internal_sbom_consumed_system_library_targets)
|
||||
|
||||
set(unconsumed_targets "${recorded_targets}")
|
||||
set(generated_package_names "")
|
||||
|
||||
foreach(target IN LISTS consumed_targets)
|
||||
# Some system targets like qtspeech SpeechDispatcher::SpeechDispatcher might be aliased,
|
||||
# and we can't set properties on them, so unalias the target name.
|
||||
set(target_original "${target}")
|
||||
get_target_property(aliased_target "${target}" ALIASED_TARGET)
|
||||
if(aliased_target)
|
||||
set(target ${aliased_target})
|
||||
endif()
|
||||
|
||||
get_property(args GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_options_${target})
|
||||
get_property(package_name GLOBAL PROPERTY
|
||||
_qt_internal_sbom_recorded_system_library_package_${target})
|
||||
|
||||
set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_target_${target} "")
|
||||
set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_options_${target} "")
|
||||
set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_package_${target} "")
|
||||
|
||||
# Guard against generating a package multiple times. Can happen when multiple targets belong
|
||||
# to the same package.
|
||||
if(sbom_generated_${package_name})
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# Automatic system library sbom recording happens at project root source dir scope, which
|
||||
# means it might accidentally pick up a qt_attribution.json file from the project root,
|
||||
# that is not intended to be use for system libraries.
|
||||
# For now, explicitly disable using the root attribution file.
|
||||
list(APPEND args NO_CURRENT_DIR_ATTRIBUTION)
|
||||
|
||||
list(APPEND generated_package_names "${package_name}")
|
||||
set(sbom_generated_${package_name} TRUE)
|
||||
|
||||
_qt_internal_extend_sbom(${target} ${args})
|
||||
_qt_internal_finalize_sbom(${target})
|
||||
|
||||
list(REMOVE_ITEM unconsumed_targets "${target_original}")
|
||||
endforeach()
|
||||
|
||||
message(DEBUG "System libraries that were recorded, but not consumed: ${unconsumed_targets}")
|
||||
message(DEBUG "Generated SBOMs for the following system packages: ${generated_package_names}")
|
||||
|
||||
# Clean up, before configuring next repo project.
|
||||
set_property(GLOBAL PROPERTY _qt_internal_sbom_consumed_system_library_targets "")
|
||||
set_property(GLOBAL PROPERTY _qt_internal_sbom_recorded_system_library_targets "")
|
||||
endfunction()
|
@ -32,6 +32,11 @@
|
||||
"file type" : "build system",
|
||||
"spdx" : ["MIT AND BSD-3-Clause"]
|
||||
},
|
||||
"cmake/QtPublicSbomOpsHelpers.cmake" : {
|
||||
"comment" : "MIT licensed copied parts",
|
||||
"file type" : "build system",
|
||||
"spdx" : ["MIT AND BSD-3-Clause"]
|
||||
},
|
||||
"tests/auto/cmake/test_plugin_shared_static_flavor\\.cmake" : {
|
||||
"comment" : "Exception. This is a test file.",
|
||||
"file type" : "test",
|
||||
|
Loading…
x
Reference in New Issue
Block a user