The code was missing an escaped dollar sign. Pick-to: 6.8 6.9 Task-number: QTBUG-122899 Change-Id: I51bff0a128546085e9418682b540d92eacfdbbe4 Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
862 lines
31 KiB
CMake
862 lines
31 KiB
CMake
# 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()
|
|
|
|
# Always save the python interpreter path if it is found, even if the dependencies are not
|
|
# found. This improves the error message workflow.
|
|
if(python_found AND NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
|
|
set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${python_path}" CACHE INTERNAL
|
|
"Python interpeter used for SBOM generation.")
|
|
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.")
|
|
|
|
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 that checks a variable for truthiness, and sets the OUT_VAR_DEPS_FOUND and
|
|
# OUT_VAR_REASON_FAILURE_MESSAGE variables based on what was passed in.
|
|
function(_qt_internal_sbom_handle_sbom_op_missing_dependency)
|
|
set(opt_args "")
|
|
set(single_args
|
|
VAR_TO_CHECK
|
|
OUT_VAR_DEPS_FOUND
|
|
OUT_VAR_REASON_FAILURE_MESSAGE
|
|
)
|
|
set(multi_args
|
|
FAILURE_MESSAGE
|
|
)
|
|
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
|
_qt_internal_validate_all_args_are_parsed(arg)
|
|
|
|
if(NOT arg_VAR_TO_CHECK)
|
|
message(FATAL_ERROR "VAR_TO_CHECK is required")
|
|
endif()
|
|
|
|
set(reason "")
|
|
set(value "${${arg_VAR_TO_CHECK}}")
|
|
if(NOT value)
|
|
if(arg_FAILURE_MESSAGE)
|
|
list(JOIN arg_FAILURE_MESSAGE " " reason)
|
|
endif()
|
|
set(deps_found FALSE)
|
|
else()
|
|
set(deps_found TRUE)
|
|
endif()
|
|
|
|
if(arg_OUT_VAR_DEPS_FOUND)
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
endif()
|
|
if(arg_OUT_VAR_REASON_FAILURE_MESSAGE)
|
|
set(${arg_OUT_VAR_REASON_FAILURE_MESSAGE} "${reason}" PARENT_SCOPE)
|
|
endif()
|
|
endfunction()
|
|
|
|
# Helper to query whether the sbom python interpreter is available, and also the error message if it
|
|
# is not available.
|
|
function(_qt_internal_sbom_check_python_interpreter_available)
|
|
set(opt_args "")
|
|
set(single_args
|
|
OUT_VAR_DEPS_FOUND
|
|
OUT_VAR_REASON_FAILURE_MESSAGE
|
|
)
|
|
set(multi_args
|
|
FAILURE_MESSAGE_PREFIX
|
|
)
|
|
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
|
_qt_internal_validate_all_args_are_parsed(arg)
|
|
|
|
set(failure_message
|
|
"QT_INTERNAL_SBOM_PYTHON_EXECUTABLE is missing a valid path to a python interpreter")
|
|
|
|
if(arg_FAILURE_MESSAGE_PREFIX)
|
|
list(PREPEND failure_message ${arg_FAILURE_MESSAGE_PREFIX})
|
|
endif()
|
|
|
|
_qt_internal_sbom_handle_sbom_op_missing_dependency(
|
|
VAR_TO_CHECK QT_INTERNAL_SBOM_PYTHON_EXECUTABLE
|
|
FAILURE_MESSAGE ${failure_message}
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE reason
|
|
)
|
|
|
|
if(arg_OUT_VAR_DEPS_FOUND)
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
endif()
|
|
if(arg_OUT_VAR_REASON_FAILURE_MESSAGE)
|
|
set(${arg_OUT_VAR_REASON_FAILURE_MESSAGE} "${reason}" PARENT_SCOPE)
|
|
endif()
|
|
endfunction()
|
|
|
|
# Helper to assert that the python interpreter is available.
|
|
function(_qt_internal_sbom_assert_python_interpreter_available error_message_prefix)
|
|
_qt_internal_sbom_check_python_interpreter_available(
|
|
FAILURE_MESSAGE_PREFIX ${error_message_prefix}
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE reason
|
|
)
|
|
|
|
if(NOT deps_found)
|
|
message(FATAL_ERROR ${reason})
|
|
endif()
|
|
endfunction()
|
|
|
|
# Helper to query whether an sbom python dependency is available, and also the error message if it
|
|
# is not available.
|
|
function(_qt_internal_sbom_check_python_dependency_available)
|
|
set(opt_args "")
|
|
set(single_args
|
|
OUT_VAR_DEPS_FOUND
|
|
OUT_VAR_REASON_FAILURE_MESSAGE
|
|
VAR_TO_CHECK
|
|
)
|
|
set(multi_args
|
|
FAILURE_MESSAGE_PREFIX
|
|
FAILURE_MESSAGE_SUFFIX
|
|
)
|
|
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
|
_qt_internal_validate_all_args_are_parsed(arg)
|
|
|
|
set(failure_message
|
|
"Required Python dependencies not found: ")
|
|
|
|
if(arg_FAILURE_MESSAGE_PREFIX)
|
|
list(PREPEND failure_message ${arg_FAILURE_MESSAGE_PREFIX})
|
|
endif()
|
|
|
|
if(arg_FAILURE_MESSAGE_SUFFIX)
|
|
list(APPEND failure_message ${arg_FAILURE_MESSAGE_SUFFIX})
|
|
endif()
|
|
|
|
_qt_internal_sbom_handle_sbom_op_missing_dependency(
|
|
VAR_TO_CHECK ${arg_VAR_TO_CHECK}
|
|
FAILURE_MESSAGE ${failure_message}
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE reason
|
|
)
|
|
|
|
if(arg_OUT_VAR_DEPS_FOUND)
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
endif()
|
|
if(arg_OUT_VAR_REASON_FAILURE_MESSAGE)
|
|
set(${arg_OUT_VAR_REASON_FAILURE_MESSAGE} "${reason}" PARENT_SCOPE)
|
|
endif()
|
|
endfunction()
|
|
|
|
# Helper to assert that an sbom python dependency is available.
|
|
function(_qt_internal_sbom_assert_python_dependency_available key dep error_message_prefix)
|
|
_qt_internal_sbom_check_python_dependency_available(
|
|
VAR_TO_CHECK QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${key}
|
|
FAILURE_MESSAGE_PREFIX ${error_message_prefix}
|
|
FAILURE_MESSAGE_SUFFIX ${dep}
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE reason
|
|
)
|
|
|
|
if(NOT deps_found)
|
|
message(FATAL_ERROR ${reason})
|
|
endif()
|
|
endfunction()
|
|
|
|
# Helper to query whether the json sbom generation dependency is available, and also the error
|
|
# message if it is not available.
|
|
function(_qt_internal_sbom_check_generate_json_dependency_available)
|
|
set(opt_args "")
|
|
set(single_args
|
|
OUT_VAR_DEPS_FOUND
|
|
OUT_VAR_REASON_FAILURE_MESSAGE
|
|
)
|
|
set(multi_args "")
|
|
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
|
_qt_internal_validate_all_args_are_parsed(arg)
|
|
|
|
_qt_internal_sbom_check_python_dependency_available(
|
|
VAR_TO_CHECK QT_INTERNAL_SBOM_DEPS_FOUND_FOR_GENERATE_JSON
|
|
FAILURE_MESSAGE_SUFFIX "spdx_tools.spdx.clitools.pyspdxtools"
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE reason
|
|
)
|
|
|
|
if(arg_OUT_VAR_DEPS_FOUND)
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
endif()
|
|
if(arg_OUT_VAR_REASON_FAILURE_MESSAGE)
|
|
set(${arg_OUT_VAR_REASON_FAILURE_MESSAGE} "${reason}" 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)
|
|
set(error_message_prefix "Failed to generate an SBOM json file.")
|
|
_qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
|
|
_qt_internal_sbom_assert_python_dependency_available(GENERATE_JSON
|
|
"spdx_tools.spdx.clitools.pyspdxtools" ${error_message_prefix})
|
|
|
|
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 query whether the all required dependencies are available to generate a tag / value
|
|
# document from a json one.
|
|
function(_qt_internal_sbom_verify_deps_for_generate_tag_value_spdx_document)
|
|
set(opt_args "")
|
|
set(single_args
|
|
OUT_VAR_DEPS_FOUND
|
|
OUT_VAR_REASON_FAILURE_MESSAGE
|
|
)
|
|
set(multi_args "")
|
|
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
|
|
_qt_internal_validate_all_args_are_parsed(arg)
|
|
|
|
# First try to find dependencies, because they might not have been found yet.
|
|
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(
|
|
OP_KEY "GENERATE_JSON"
|
|
)
|
|
|
|
_qt_internal_sbom_check_python_interpreter_available(
|
|
OUT_VAR_DEPS_FOUND python_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE python_error
|
|
)
|
|
_qt_internal_sbom_check_generate_json_dependency_available(
|
|
OUT_VAR_DEPS_FOUND dep_found
|
|
OUT_VAR_REASON_FAILURE_MESSAGE dep_error
|
|
)
|
|
|
|
set(reasons "")
|
|
if(python_error)
|
|
string(APPEND reasons "${python_error}\n")
|
|
endif()
|
|
if(dep_error)
|
|
string(APPEND reasons "${dep_error}\n")
|
|
endif()
|
|
|
|
if(python_found AND dep_found)
|
|
set(deps_found "TRUE")
|
|
else()
|
|
set(deps_found "FALSE")
|
|
endif()
|
|
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
set(${arg_OUT_VAR_REASON_FAILURE_MESSAGE} "${reasons}" PARENT_SCOPE)
|
|
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.
|
|
#
|
|
# OPTIONAL - whether the operation should return early, if the required python dependencies are
|
|
# not found. OUT_VAR_DEPS_FOUND is still set in that case.
|
|
#
|
|
# OUT_VAR_DEPS_FOUND - output variable where to store whether the python dependencies for the
|
|
# operation were found, and thus the conversion will be attempted.
|
|
function(_qt_internal_sbom_generate_tag_value_spdx_document)
|
|
if(NOT QT_GENERATE_SBOM)
|
|
return()
|
|
endif()
|
|
|
|
set(opt_args
|
|
OPTIONAL
|
|
)
|
|
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
|
|
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)
|
|
|
|
# First try to find dependencies, because they might not have been found yet.
|
|
_qt_internal_sbom_find_and_handle_sbom_op_dependencies(
|
|
OP_KEY "GENERATE_JSON"
|
|
OUT_VAR_DEPS_FOUND deps_found
|
|
)
|
|
|
|
if(arg_OPTIONAL)
|
|
set(deps_are_required FALSE)
|
|
else()
|
|
set(deps_are_required TRUE)
|
|
endif()
|
|
|
|
# If the operation has to succeed, then the deps are required. Assert they are available.
|
|
if(deps_are_required)
|
|
set(error_message_prefix "Failed to generate a tag/value SBOM file from a json SBOM file.")
|
|
_qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
|
|
_qt_internal_sbom_assert_python_dependency_available(GENERATE_JSON
|
|
"spdx_tools.spdx.clitools.pyspdxtools" ${error_message_prefix})
|
|
|
|
# If the operation is optional, don't error out if the deps are not found, but silently return
|
|
# and mention that the deps are not found.
|
|
elseif(NOT deps_found)
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
return()
|
|
endif()
|
|
|
|
set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
|
|
|
|
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)
|
|
set(error_message_prefix "Failed to verify SBOM file syntax.")
|
|
_qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
|
|
_qt_internal_sbom_assert_python_dependency_available(VERIFY_SBOM
|
|
"spdx_tools.spdx.clitools.pyspdxtools" ${error_message_prefix})
|
|
|
|
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)
|
|
set(error_message_prefix "Failed to run NTIA checker on SBOM file.")
|
|
_qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
|
|
_qt_internal_sbom_assert_python_dependency_available(RUN_NTIA
|
|
"ntia_conformance_checker.main" ${error_message_prefix})
|
|
|
|
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")
|
|
file(TO_CMAKE_PATH "$ENV{QT_QA_LICENSE_TEST_DIR}/$ENV{QT_SOURCE_SBOM_TEST_SCRIPT}"
|
|
full_path_to_license_test)
|
|
set(verify_source_sbom "
|
|
if(res EQUAL 0)
|
|
message(STATUS \"Verifying source SBOM ${source_sbom_path} using qtqa tst_licenses.pl ${full_path_to_license_test}\")
|
|
if(NOT EXISTS \"${full_path_to_license_test}\")
|
|
message(FATAL_ERROR \"Source SBOM check has failed: The tst_licenses.pl script could not be found at ${full_path_to_license_test}\")
|
|
endif()
|
|
execute_process(
|
|
COMMAND perl \"\$ENV{QT_SOURCE_SBOM_TEST_SCRIPT}\" -sbomonly -sbom \"${source_sbom_path}\"
|
|
WORKING_DIRECTORY \"\$ENV{QT_QA_LICENSE_TEST_DIR}\"
|
|
RESULT_VARIABLE res
|
|
COMMAND_ECHO STDOUT
|
|
)
|
|
if(NOT res EQUAL 0)
|
|
message(FATAL_ERROR \"Source SBOM check has failed: \${res}\")
|
|
endif()
|
|
endif()
|
|
")
|
|
|
|
set(extra_reuse_args "")
|
|
|
|
get_property(project_supplier GLOBAL PROPERTY _qt_sbom_project_supplier)
|
|
if(project_supplier)
|
|
get_property(project_supplier_url GLOBAL PROPERTY _qt_sbom_project_supplier_url)
|
|
|
|
# Add the supplier url if available. Add it in parenthesis to stop reuse from adding its
|
|
# own empty parenthesis.
|
|
if(project_supplier_url)
|
|
set(project_supplier "${project_supplier} (${project_supplier_url})")
|
|
endif()
|
|
|
|
# Unfortunately there's no way to silence the addition of the 'Creator: Person' field,
|
|
# even though 'Creator: Organization' is supplied.
|
|
list(APPEND extra_reuse_args --creator-organization "${project_supplier}")
|
|
endif()
|
|
|
|
set(content "
|
|
message(STATUS \"Generating source SBOM using reuse tool: ${source_sbom_path}\")
|
|
set(extra_reuse_args \"${extra_reuse_args}\")
|
|
execute_process(
|
|
COMMAND
|
|
${QT_SBOM_PROGRAM_REUSE}
|
|
--root \"${PROJECT_SOURCE_DIR}\"
|
|
spdx
|
|
-o ${source_sbom_path}
|
|
\${extra_reuse_args}
|
|
RESULT_VARIABLE res
|
|
)
|
|
${handle_error}
|
|
if(\"\$ENV{VERIFY_SOURCE_SBOM}\")
|
|
${verify_source_sbom}
|
|
endif()
|
|
")
|
|
|
|
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()
|