From ba88d6819e3714b8871da87f1036103586a3cbc6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:33:44 -0500 Subject: [PATCH 1/8] ENH: Add SPDX 2.3 SBOM generation at configure time Implement build-time generation of a Software Bill of Materials (SBOM) in SPDX 2.3 JSON format. The SBOM documents ITK and all enabled third-party dependencies with their names, licenses, and dependency relationships. - Add CMake/ITKSBOMGeneration.cmake with SBOM generation logic - Add ITK_GENERATE_SBOM option (default ON) to CMakeLists.txt - Generate sbom.spdx.json in the build directory at configure time - Install sbom.spdx.json alongside LICENSE, NOTICE, and README.md - Support FFTW as optional GPL dependency in SBOM - Provide itk_sbom_register_package() for remote modules to extend Co-authored-by: dzenanz <1792121+dzenanz@users.noreply.github.com> --- CMake/ITKSBOMGeneration.cmake | 552 ++++++++++++++++++++++++++++++++++ CMakeLists.txt | 23 ++ 2 files changed, 575 insertions(+) create mode 100644 CMake/ITKSBOMGeneration.cmake diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake new file mode 100644 index 00000000000..b33583728b0 --- /dev/null +++ b/CMake/ITKSBOMGeneration.cmake @@ -0,0 +1,552 @@ +#[=============================================================================[ + ITKSBOMGeneration.cmake - Generate SPDX Software Bill of Materials (SBOM) + + This module generates an SPDX 2.3 JSON SBOM document describing ITK and + its enabled third-party dependencies at build configuration time. The SBOM + includes component names, versions, licenses, and dependency relationships. + + Usage: + option(ITK_GENERATE_SBOM "Generate SPDX SBOM at configure time" ON) + include(ITKSBOMGeneration) + + The generated file is written to: + ${CMAKE_BINARY_DIR}/sbom.spdx.json +#]=============================================================================] + +if(NOT ITK_GENERATE_SBOM) + return() +endif() + +#----------------------------------------------------------------------------- +# Allow remote modules to register SBOM package metadata. +# +# Usage in a remote module's CMakeLists.txt: +# itk_sbom_register_package( +# NAME "MyRemoteModule" +# VERSION "1.0.0" +# SPDX_LICENSE "Apache-2.0" +# DOWNLOAD_LOCATION "https://github.com/example/MyRemoteModule" +# SUPPLIER "Organization: Example" +# COPYRIGHT "Copyright Example Inc." +# ) +# +define_property( + GLOBAL + PROPERTY ITK_SBOM_EXTRA_PACKAGES + BRIEF_DOCS + "Additional SBOM package entries registered by remote modules." + FULL_DOCS + "A list of JSON-formatted package entries for the SBOM." +) + +function(itk_sbom_register_package) + set(_options "") + set( + _one_value + NAME + VERSION + SPDX_LICENSE + DOWNLOAD_LOCATION + SUPPLIER + COPYRIGHT + ) + set(_multi_value "") + cmake_parse_arguments( + _pkg + "${_options}" + "${_one_value}" + "${_multi_value}" + ${ARGN} + ) + + if(NOT _pkg_NAME) + message(FATAL_ERROR "itk_sbom_register_package: NAME is required.") + endif() + if(NOT _pkg_SPDX_LICENSE) + set(_pkg_SPDX_LICENSE "NOASSERTION") + endif() + if(NOT _pkg_VERSION) + set(_pkg_VERSION "NOASSERTION") + endif() + if(NOT _pkg_DOWNLOAD_LOCATION) + set(_pkg_DOWNLOAD_LOCATION "NOASSERTION") + endif() + if(NOT _pkg_SUPPLIER) + set(_pkg_SUPPLIER "NOASSERTION") + endif() + if(NOT _pkg_COPYRIGHT) + set(_pkg_COPYRIGHT "NOASSERTION") + endif() + + # Sanitize the name for use as SPDX ID (only alphanumeric and -) + string(REGEX REPLACE "[^A-Za-z0-9-]" "-" _spdx_id "${_pkg_NAME}") + + set(_entry "") + string(APPEND _entry " {\n") + string(APPEND _entry " \"SPDXID\": \"SPDXRef-${_spdx_id}\",\n") + string(APPEND _entry " \"name\": \"${_pkg_NAME}\",\n") + string(APPEND _entry " \"versionInfo\": \"${_pkg_VERSION}\",\n") + string( + APPEND + _entry + " \"downloadLocation\": \"${_pkg_DOWNLOAD_LOCATION}\",\n" + ) + string(APPEND _entry " \"supplier\": \"${_pkg_SUPPLIER}\",\n") + string( + APPEND + _entry + " \"licenseConcluded\": \"${_pkg_SPDX_LICENSE}\",\n" + ) + string(APPEND _entry " \"licenseDeclared\": \"${_pkg_SPDX_LICENSE}\",\n") + string(APPEND _entry " \"copyrightText\": \"${_pkg_COPYRIGHT}\",\n") + string(APPEND _entry " \"filesAnalyzed\": false\n") + string(APPEND _entry " }") + + set_property( + GLOBAL + APPEND + PROPERTY + ITK_SBOM_EXTRA_PACKAGES + "${_entry}" + ) + + # Also store the SPDX ID for relationship generation + set_property( + GLOBAL + APPEND + PROPERTY + ITK_SBOM_EXTRA_SPDX_IDS + "SPDXRef-${_spdx_id}" + ) +endfunction() + +#----------------------------------------------------------------------------- +# Internal: Escape a string for use in a JSON value. +# Handles backslashes, double quotes, and newlines. +# +function(_itk_sbom_json_escape input_string output_var) + set(_str "${input_string}") + # Escape backslashes first + string(REPLACE "\\" "\\\\" _str "${_str}") + # Escape double quotes + string(REPLACE "\"" "\\\"" _str "${_str}") + # Escape newlines (CMake uses \n for semicolons and actual newlines) + string(REPLACE "\n" "\\n" _str "${_str}") + # Escape tabs + string(REPLACE "\t" "\\t" _str "${_str}") + set(${output_var} "${_str}" PARENT_SCOPE) +endfunction() + +#----------------------------------------------------------------------------- +# Internal: Define SBOM metadata for a known ThirdParty module. +# Sets variables in the caller's scope: +# _spdx_license, _download_location, _supplier, _copyright, _version +# +function( + _itk_sbom_get_thirdparty_metadata + module_name + out_license + out_download + out_supplier + out_copyright + out_version +) + # Defaults + set(_license "NOASSERTION") + set(_download "NOASSERTION") + set(_supplier "NOASSERTION") + set(_copyright "NOASSERTION") + set(_version "NOASSERTION") + + if("${module_name}" STREQUAL "ITKDCMTK") + set(_license "BSD-3-Clause") + set(_download "https://dicom.offis.de/dcmtk") + set(_supplier "Organization: OFFIS e.V.") + set(_copyright "Copyright OFFIS e.V.") + set(_version "3.6.9") + elseif("${module_name}" STREQUAL "ITKDICOMParser") + set(_license "BSD-3-Clause") + set(_download "https://github.com/InsightSoftwareConsortium/ITK") + set(_supplier "Organization: Kitware Inc.") + set(_copyright "Copyright Kitware Inc.") + elseif("${module_name}" STREQUAL "ITKDoubleConversion") + set(_license "BSD-3-Clause") + set(_download "https://github.com/google/double-conversion") + set(_supplier "Organization: Google Inc.") + set(_copyright "Copyright Google Inc.") + set(_version "3.1.6") + elseif("${module_name}" STREQUAL "ITKEigen3") + set(_license "MPL-2.0") + set(_download "https://eigen.tuxfamily.org") + set(_supplier "Organization: Eigen") + set(_copyright "Copyright Eigen contributors") + # Try to detect Eigen version from CMake variable + if(DEFINED EIGEN3_VERSION_STRING) + set(_version "${EIGEN3_VERSION_STRING}") + elseif(DEFINED Eigen3_VERSION) + set(_version "${Eigen3_VERSION}") + endif() + elseif("${module_name}" STREQUAL "ITKExpat") + set(_license "MIT") + set(_download "https://libexpat.github.io") + set(_supplier "Organization: Expat development team") + set(_copyright "Copyright Expat development team") + elseif("${module_name}" STREQUAL "ITKGDCM") + set(_license "BSD-3-Clause") + set(_download "https://gdcm.sourceforge.net") + set(_supplier "Organization: GDCM contributors") + set(_copyright "Copyright GDCM contributors") + set(_version "3.2.2") + elseif("${module_name}" STREQUAL "ITKGIFTI") + set(_license "LicenseRef-NITRC-Public-Domain") + set(_download "https://www.nitrc.org/projects/gifti") + set(_supplier "Organization: NITRC") + set(_copyright "NOASSERTION") + elseif("${module_name}" STREQUAL "ITKGoogleTest") + set(_license "BSD-3-Clause") + set(_download "https://github.com/google/googletest") + set(_supplier "Organization: Google Inc.") + set(_copyright "Copyright Google Inc.") + set(_version "1.17.0") + elseif("${module_name}" STREQUAL "ITKHDF5") + set(_license "BSD-3-Clause") + set(_download "https://www.hdfgroup.org/solutions/hdf5") + set(_supplier "Organization: The HDF Group") + set(_copyright "Copyright The HDF Group") + # Try to detect HDF5 version from CMake variable + if(DEFINED HDF5_VERSION) + set(_version "${HDF5_VERSION}") + endif() + elseif("${module_name}" STREQUAL "ITKJPEG") + set(_license "IJG AND BSD-3-Clause AND Zlib") + set(_download "https://libjpeg-turbo.org") + set(_supplier "Organization: libjpeg-turbo") + set(_copyright "Copyright libjpeg-turbo contributors") + elseif("${module_name}" STREQUAL "ITKKWSys") + set(_license "BSD-3-Clause") + set(_download "https://gitlab.kitware.com/utils/kwsys") + set(_supplier "Organization: Kitware Inc.") + set(_copyright "Copyright Kitware Inc.") + elseif("${module_name}" STREQUAL "ITKMINC") + set(_license "LGPL-2.1-only") + set(_download "https://github.com/BIC-MNI/libminc") + set(_supplier "Organization: McConnell Brain Imaging Centre") + set(_copyright "Copyright McConnell Brain Imaging Centre") + elseif("${module_name}" STREQUAL "ITKMetaIO") + set(_license "Apache-2.0") + set(_download "https://github.com/Kitware/MetaIO") + set(_supplier "Organization: Kitware Inc.") + set(_copyright "Copyright Kitware Inc.") + elseif("${module_name}" STREQUAL "ITKNIFTI") + set(_license "LicenseRef-NIFTI-Public-Domain") + set(_download "https://nifti.nimh.nih.gov") + set(_supplier "Organization: NITRC") + set(_copyright "NOASSERTION") + elseif("${module_name}" STREQUAL "ITKNetlib") + set(_license "LicenseRef-Netlib-SLATEC") + set(_download "https://www.netlib.org/slatec") + set(_supplier "Organization: Netlib") + set(_copyright "NOASSERTION") + elseif("${module_name}" STREQUAL "ITKNrrdIO") + set(_license "LGPL-2.1-only") + set(_download "https://teem.sourceforge.net/nrrd") + set(_supplier "Organization: Teem") + set(_copyright "Copyright Teem contributors") + elseif("${module_name}" STREQUAL "ITKOpenJPEG") + set(_license "BSD-2-Clause") + set(_download "https://www.openjpeg.org") + set(_supplier "Organization: OpenJPEG contributors") + set(_copyright "Copyright OpenJPEG contributors") + set(_version "2.5.4") + elseif("${module_name}" STREQUAL "ITKPNG") + set(_license "Libpng-2.0") + set(_download "https://www.libpng.org/pub/png/libpng.html") + set(_supplier "Organization: libpng contributors") + set(_copyright "Copyright libpng contributors") + # Try to detect PNG version from CMake variable + if(DEFINED PNG_VERSION_STRING) + set(_version "${PNG_VERSION_STRING}") + endif() + elseif("${module_name}" STREQUAL "ITKTBB") + set(_license "Apache-2.0") + set(_download "https://github.com/oneapi-src/oneTBB") + set(_supplier "Organization: Intel Corporation") + set(_copyright "Copyright Intel Corporation") + # Try to detect TBB version from CMake variable + if(DEFINED TBB_VERSION) + set(_version "${TBB_VERSION}") + endif() + elseif("${module_name}" STREQUAL "ITKTIFF") + set(_license "libtiff") + set(_download "https://libtiff.maptools.org") + set(_supplier "Organization: libtiff contributors") + set(_copyright "Copyright libtiff contributors") + # Try to detect TIFF version from CMake variable + if(DEFINED TIFF_VERSION_STRING) + set(_version "${TIFF_VERSION_STRING}") + endif() + elseif("${module_name}" STREQUAL "ITKVNL") + set(_license "BSD-3-Clause") + set(_download "https://vxl.github.io") + set(_supplier "Organization: VXL contributors") + set(_copyright "Copyright VXL contributors") + elseif("${module_name}" STREQUAL "ITKZLIB") + set(_license "Zlib") + set(_download "https://github.com/zlib-ng/zlib-ng") + set(_supplier "Organization: zlib-ng contributors") + set(_copyright "Copyright zlib-ng contributors") + # Try to detect zlib version from CMake variable + if(DEFINED ZLIB_VERSION_STRING) + set(_version "${ZLIB_VERSION_STRING}") + endif() + elseif("${module_name}" STREQUAL "ITKLIBLBFGS") + set(_license "MIT") + set(_download "https://github.com/chokkan/liblbfgs") + set(_supplier "Organization: Naoaki Okazaki") + set(_copyright "Copyright Naoaki Okazaki") + endif() + + set(${out_license} "${_license}" PARENT_SCOPE) + set(${out_download} "${_download}" PARENT_SCOPE) + set(${out_supplier} "${_supplier}" PARENT_SCOPE) + set(${out_copyright} "${_copyright}" PARENT_SCOPE) + set(${out_version} "${_version}" PARENT_SCOPE) +endfunction() + +#----------------------------------------------------------------------------- +# Main function: Generate the SPDX 2.3 SBOM JSON document. +# +function(itk_generate_sbom) + string(TIMESTAMP _sbom_timestamp "%Y-%m-%dT%H:%M:%SZ" UTC) + string(TIMESTAMP _sbom_uid "%Y%m%d%H%M%S" UTC) + + set( + _sbom_namespace + "https://spdx.org/spdxdocs/ITK-${ITK_VERSION}-${_sbom_uid}" + ) + + # --- Begin JSON document --- + set(_json "") + string(APPEND _json "{\n") + string(APPEND _json " \"spdxVersion\": \"SPDX-2.3\",\n") + string(APPEND _json " \"dataLicense\": \"CC0-1.0\",\n") + string(APPEND _json " \"SPDXID\": \"SPDXRef-DOCUMENT\",\n") + string(APPEND _json " \"name\": \"ITK-${ITK_VERSION}-SBOM\",\n") + string(APPEND _json " \"documentNamespace\": \"${_sbom_namespace}\",\n") + + # --- creationInfo --- + string(APPEND _json " \"creationInfo\": {\n") + string(APPEND _json " \"created\": \"${_sbom_timestamp}\",\n") + string(APPEND _json " \"creators\": [\n") + string(APPEND _json " \"Tool: CMake-${CMAKE_VERSION}\",\n") + string(APPEND _json " \"Organization: NumFOCUS\"\n") + string(APPEND _json " ],\n") + string(APPEND _json " \"licenseListVersion\": \"3.22\"\n") + string(APPEND _json " },\n") + + # --- packages array --- + string(APPEND _json " \"packages\": [\n") + + # ITK main package + string(APPEND _json " {\n") + string(APPEND _json " \"SPDXID\": \"SPDXRef-ITK\",\n") + string(APPEND _json " \"name\": \"ITK\",\n") + string(APPEND _json " \"versionInfo\": \"${ITK_VERSION}\",\n") + string( + APPEND + _json + " \"downloadLocation\": \"https://github.com/InsightSoftwareConsortium/ITK\",\n" + ) + string(APPEND _json " \"supplier\": \"Organization: NumFOCUS\",\n") + string(APPEND _json " \"licenseConcluded\": \"Apache-2.0\",\n") + string(APPEND _json " \"licenseDeclared\": \"Apache-2.0\",\n") + string( + APPEND + _json + " \"copyrightText\": \"Copyright 1999-2019 Insight Software Consortium, Copyright 2020-present NumFOCUS\",\n" + ) + string(APPEND _json " \"filesAnalyzed\": false\n") + string(APPEND _json " }") + + # Collect ThirdParty modules from enabled modules list + set(_thirdparty_spdx_ids "") + foreach(_mod ${ITK_MODULES_ENABLED}) + if(${_mod}_IS_TEST) + continue() + endif() + + # Check if this is a ThirdParty module by examining its source directory + if(NOT DEFINED ${_mod}_SOURCE_DIR) + continue() + endif() + string(FIND "${${_mod}_SOURCE_DIR}" "Modules/ThirdParty" _tp_pos) + if(_tp_pos EQUAL -1) + continue() + endif() + + # Get metadata for this ThirdParty module + _itk_sbom_get_thirdparty_metadata("${_mod}" + _pkg_license _pkg_download _pkg_supplier _pkg_copyright _pkg_version + ) + + # Get description from module declaration and escape for JSON + set(_pkg_description "${ITK_MODULE_${_mod}_DESCRIPTION}") + if(_pkg_description) + _itk_sbom_json_escape("${_pkg_description}" _pkg_description) + endif() + + # Sanitize module name for SPDX ID + string(REGEX REPLACE "[^A-Za-z0-9-]" "-" _spdx_id "${_mod}") + list(APPEND _thirdparty_spdx_ids "SPDXRef-${_spdx_id}") + + string(APPEND _json ",\n") + string(APPEND _json " {\n") + string(APPEND _json " \"SPDXID\": \"SPDXRef-${_spdx_id}\",\n") + string(APPEND _json " \"name\": \"${_mod}\",\n") + string(APPEND _json " \"versionInfo\": \"${_pkg_version}\",\n") + string(APPEND _json " \"downloadLocation\": \"${_pkg_download}\",\n") + string(APPEND _json " \"supplier\": \"${_pkg_supplier}\",\n") + string(APPEND _json " \"licenseConcluded\": \"${_pkg_license}\",\n") + string(APPEND _json " \"licenseDeclared\": \"${_pkg_license}\",\n") + string(APPEND _json " \"copyrightText\": \"${_pkg_copyright}\",\n") + if(_pkg_description) + string(APPEND _json " \"description\": \"${_pkg_description}\",\n") + endif() + string(APPEND _json " \"filesAnalyzed\": false\n") + string(APPEND _json " }") + endforeach() + + # FFTW (not an ITK module, but an optional external dependency) + if(ITK_USE_FFTWD OR ITK_USE_FFTWF) + set(_fftw_license "GPL-2.0-or-later") + set(_fftw_version "NOASSERTION") + if(DEFINED _fftw_target_version) + set(_fftw_version "${_fftw_target_version}") + elseif(DEFINED FFTW_VERSION) + set(_fftw_version "${FFTW_VERSION}") + endif() + + string(APPEND _json ",\n") + string(APPEND _json " {\n") + string(APPEND _json " \"SPDXID\": \"SPDXRef-FFTW\",\n") + string(APPEND _json " \"name\": \"FFTW\",\n") + string(APPEND _json " \"versionInfo\": \"${_fftw_version}\",\n") + string( + APPEND + _json + " \"downloadLocation\": \"https://www.fftw.org\",\n" + ) + string(APPEND _json " \"supplier\": \"Organization: MIT\",\n") + string(APPEND _json " \"licenseConcluded\": \"${_fftw_license}\",\n") + string(APPEND _json " \"licenseDeclared\": \"${_fftw_license}\",\n") + string( + APPEND + _json + " \"copyrightText\": \"Copyright Matteo Frigo and Massachusetts Institute of Technology\",\n" + ) + string( + APPEND + _json + " \"description\": \"Fastest Fourier Transform in the West\",\n" + ) + string(APPEND _json " \"filesAnalyzed\": false\n") + string(APPEND _json " }") + list(APPEND _thirdparty_spdx_ids "SPDXRef-FFTW") + endif() + + # Append extra packages registered by remote modules + get_property(_extra_packages GLOBAL PROPERTY ITK_SBOM_EXTRA_PACKAGES) + foreach(_extra_pkg ${_extra_packages}) + string(APPEND _json ",\n${_extra_pkg}") + endforeach() + + string(APPEND _json "\n ],\n") + + # --- relationships array --- + string(APPEND _json " \"relationships\": [\n") + + # DOCUMENT describes ITK + string(APPEND _json " {\n") + string(APPEND _json " \"spdxElementId\": \"SPDXRef-DOCUMENT\",\n") + string(APPEND _json " \"relationshipType\": \"DESCRIBES\",\n") + string(APPEND _json " \"relatedSpdxElement\": \"SPDXRef-ITK\"\n") + string(APPEND _json " }") + + # ITK DEPENDS_ON each ThirdParty module + foreach(_spdx_id ${_thirdparty_spdx_ids}) + string(APPEND _json ",\n") + string(APPEND _json " {\n") + string(APPEND _json " \"spdxElementId\": \"SPDXRef-ITK\",\n") + string(APPEND _json " \"relationshipType\": \"DEPENDS_ON\",\n") + string(APPEND _json " \"relatedSpdxElement\": \"${_spdx_id}\"\n") + string(APPEND _json " }") + endforeach() + + # Extra packages registered by remote modules + get_property(_extra_spdx_ids GLOBAL PROPERTY ITK_SBOM_EXTRA_SPDX_IDS) + foreach(_spdx_id ${_extra_spdx_ids}) + string(APPEND _json ",\n") + string(APPEND _json " {\n") + string(APPEND _json " \"spdxElementId\": \"SPDXRef-ITK\",\n") + string(APPEND _json " \"relationshipType\": \"DEPENDS_ON\",\n") + string(APPEND _json " \"relatedSpdxElement\": \"${_spdx_id}\"\n") + string(APPEND _json " }") + endforeach() + + string(APPEND _json "\n ],\n") + + # --- hasExtractedLicensingInfo for custom LicenseRef identifiers --- + string(APPEND _json " \"hasExtractedLicensingInfo\": [\n") + string(APPEND _json " {\n") + string( + APPEND + _json + " \"licenseId\": \"LicenseRef-NIFTI-Public-Domain\",\n" + ) + string(APPEND _json " \"name\": \"NIFTI Public Domain License\",\n") + string( + APPEND + _json + " \"extractedText\": \"This software is in the public domain. The NIFTI header and library are released into the public domain.\"\n" + ) + string(APPEND _json " },\n") + string(APPEND _json " {\n") + string( + APPEND + _json + " \"licenseId\": \"LicenseRef-NITRC-Public-Domain\",\n" + ) + string( + APPEND + _json + " \"name\": \"NITRC GIFTI Public Domain License\",\n" + ) + string( + APPEND + _json + " \"extractedText\": \"The GIFTI library is released into the public domain under the NITRC project.\"\n" + ) + string(APPEND _json " },\n") + string(APPEND _json " {\n") + string(APPEND _json " \"licenseId\": \"LicenseRef-Netlib-SLATEC\",\n") + string( + APPEND + _json + " \"name\": \"Netlib SLATEC Public Domain License\",\n" + ) + string( + APPEND + _json + " \"extractedText\": \"The SLATEC Common Mathematical Library is issued by the U.S. Government and is in the public domain.\"\n" + ) + string(APPEND _json " }\n") + string(APPEND _json " ]\n") + + # --- Close JSON document --- + string(APPEND _json "}\n") + + # Write SBOM to build directory + set(_sbom_file "${CMAKE_BINARY_DIR}/sbom.spdx.json") + file(WRITE "${_sbom_file}" "${_json}") + message(STATUS "SBOM generated: ${_sbom_file}") +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index aba2ec14dd4..7452f611271 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,6 +247,14 @@ If this is not set during the initial configuration, it will have no effect." ) mark_as_advanced(ITK_USE_SYSTEM_LIBRARIES) +#----------------------------------------------------------------------------- +# Generate a Software Bill of Materials (SBOM) in SPDX 2.3 JSON format. +option( + ITK_GENERATE_SBOM + "Generate SPDX Software Bill of Materials (SBOM) at configure time" + ON +) + #----------------------------------------------------------------------------- # Enable the download and use of BrainWeb datasets. # When this data is available, additional 3D tests are enabled. @@ -744,6 +752,12 @@ add_subdirectory(Modules/Remote) # Enable modules according to user inputs and the module dependency DAG. include(ITKModuleEnablement) +# Generate SPDX Software Bill of Materials (SBOM) if enabled. +include(ITKSBOMGeneration) +if(ITK_GENERATE_SBOM) + itk_generate_sbom() +endif() + # Setup clang-tidy for code best-practices enforcement for C++11 include(ITKClangTidySetup) #---------------------------------------------------------------------- @@ -855,6 +869,15 @@ install( COMPONENT Runtime ) +if(ITK_GENERATE_SBOM) + install( + FILES + "${CMAKE_BINARY_DIR}/sbom.spdx.json" + DESTINATION ${ITK_INSTALL_DOC_DIR} + COMPONENT Runtime + ) +endif() + if(BUILD_TESTING) # If building the testing, write the test costs (i.e. time to run) # analysis to disk to more easily review long-running test times From 229c2cecbeda28bbf0b0865da2eed2dc717bb02b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:34:13 -0500 Subject: [PATCH 2/8] ENH: Distribute SBOM metadata into per-module itk-module.cmake files Move SPDX license and per-module metadata from the centralized lookup table in ITKSBOMGeneration.cmake into each ThirdParty module's itk-module.cmake file for co-location and easier maintenance. - Extend itk_module() macro to accept SPDX_LICENSE, SPDX_DOWNLOAD_LOCATION, SPDX_COPYRIGHT, SPDX_CUSTOM_LICENSE_TEXT, and SPDX_CUSTOM_LICENSE_NAME - Add SPDX metadata to all 23 ThirdParty itk-module.cmake files - Simplify ITKSBOMGeneration.cmake to read from module declarations Co-authored-by: dzenanz <1792121+dzenanz@users.noreply.github.com> --- .gitignore | 1 + CMake/ITKModuleMacros.cmake | 22 +- CMake/ITKSBOMGeneration.cmake | 293 ++++-------------- Modules/ThirdParty/DCMTK/itk-module.cmake | 18 +- .../ThirdParty/DICOMParser/itk-module.cmake | 11 +- .../DoubleConversion/itk-module.cmake | 11 +- Modules/ThirdParty/Eigen3/itk-module.cmake | 6 + Modules/ThirdParty/Expat/itk-module.cmake | 11 +- Modules/ThirdParty/GDCM/itk-module.cmake | 18 +- Modules/ThirdParty/GIFTI/itk-module.cmake | 10 + .../ThirdParty/GoogleTest/itk-module.cmake | 12 +- Modules/ThirdParty/HDF5/itk-module.cmake | 13 +- Modules/ThirdParty/JPEG/itk-module.cmake | 11 +- Modules/ThirdParty/KWSys/itk-module.cmake | 11 +- Modules/ThirdParty/MINC/itk-module.cmake | 18 +- Modules/ThirdParty/MetaIO/itk-module.cmake | 13 +- Modules/ThirdParty/NIFTI/itk-module.cmake | 17 +- Modules/ThirdParty/Netlib/itk-module.cmake | 17 +- Modules/ThirdParty/NrrdIO/itk-module.cmake | 13 +- Modules/ThirdParty/OpenJPEG/itk-module.cmake | 12 +- Modules/ThirdParty/PNG/itk-module.cmake | 13 +- Modules/ThirdParty/TBB/itk-module.cmake | 12 +- Modules/ThirdParty/TIFF/itk-module.cmake | 6 + Modules/ThirdParty/VNL/itk-module.cmake | 11 +- Modules/ThirdParty/ZLIB/itk-module.cmake | 11 +- Modules/ThirdParty/libLBFGS/itk-module.cmake | 11 +- 26 files changed, 346 insertions(+), 256 deletions(-) diff --git a/.gitignore b/.gitignore index 634d6fc26c1..b4da296cf20 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ CMakeUserPresets.json # pixi environments .pixi *.egg-info +InsightData-*.tar.gz diff --git a/CMake/ITKModuleMacros.cmake b/CMake/ITKModuleMacros.cmake index 96ef854c4db..4a933db26d8 100644 --- a/CMake/ITKModuleMacros.cmake +++ b/CMake/ITKModuleMacros.cmake @@ -69,12 +69,17 @@ macro(itk_module _name) set(ITK_MODULE_${itk-module}_DESCRIPTION "description") set(ITK_MODULE_${itk-module}_EXCLUDE_FROM_DEFAULT 0) set(ITK_MODULE_${itk-module}_ENABLE_SHARED 0) + set(ITK_MODULE_${itk-module}_SPDX_LICENSE "") + set(ITK_MODULE_${itk-module}_SPDX_DOWNLOAD_LOCATION "") + set(ITK_MODULE_${itk-module}_SPDX_COPYRIGHT "") + set(ITK_MODULE_${itk-module}_SPDX_CUSTOM_LICENSE_TEXT "") + set(ITK_MODULE_${itk-module}_SPDX_CUSTOM_LICENSE_NAME "") foreach(arg ${ARGN}) ### Parse itk_module named options if( "${arg}" MATCHES - "^((|COMPILE_|PRIVATE_|TEST_|)DEPENDS|DESCRIPTION|DEFAULT|FACTORY_NAMES)$" + "^((|COMPILE_|PRIVATE_|TEST_|)DEPENDS|DESCRIPTION|DEFAULT|FACTORY_NAMES|SPDX_LICENSE|SPDX_DOWNLOAD_LOCATION|SPDX_COPYRIGHT|SPDX_CUSTOM_LICENSE_TEXT|SPDX_CUSTOM_LICENSE_NAME)$" ) set(_doing "${arg}") elseif("${arg}" MATCHES "^EXCLUDE_FROM_DEFAULT$") @@ -104,6 +109,21 @@ macro(itk_module _name) elseif("${_doing}" MATCHES "^DESCRIPTION$") set(_doing "") set(ITK_MODULE_${itk-module}_DESCRIPTION "${arg}") + elseif("${_doing}" MATCHES "^SPDX_LICENSE$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_LICENSE "${arg}") + elseif("${_doing}" MATCHES "^SPDX_DOWNLOAD_LOCATION$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_DOWNLOAD_LOCATION "${arg}") + elseif("${_doing}" MATCHES "^SPDX_COPYRIGHT$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_COPYRIGHT "${arg}") + elseif("${_doing}" MATCHES "^SPDX_CUSTOM_LICENSE_TEXT$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_CUSTOM_LICENSE_TEXT "${arg}") + elseif("${_doing}" MATCHES "^SPDX_CUSTOM_LICENSE_NAME$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_CUSTOM_LICENSE_NAME "${arg}") elseif("${_doing}" MATCHES "^DEFAULT") message(FATAL_ERROR "Invalid argument [DEFAULT]") else() diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake index b33583728b0..041b763bc83 100644 --- a/CMake/ITKSBOMGeneration.cmake +++ b/CMake/ITKSBOMGeneration.cmake @@ -5,6 +5,14 @@ its enabled third-party dependencies at build configuration time. The SBOM includes component names, versions, licenses, and dependency relationships. + Per-module SPDX metadata is declared in each module's itk-module.cmake via + the itk_module() macro arguments: + SPDX_LICENSE - SPDX license identifier (e.g. "Apache-2.0") + SPDX_DOWNLOAD_LOCATION - URL for the upstream source + SPDX_COPYRIGHT - Copyright text + SPDX_CUSTOM_LICENSE_TEXT - Extracted text for custom LicenseRef-* IDs + SPDX_CUSTOM_LICENSE_NAME - Human-readable name for custom license refs + Usage: option(ITK_GENERATE_SBOM "Generate SPDX SBOM at configure time" ON) include(ITKSBOMGeneration) @@ -137,182 +145,6 @@ function(_itk_sbom_json_escape input_string output_var) set(${output_var} "${_str}" PARENT_SCOPE) endfunction() -#----------------------------------------------------------------------------- -# Internal: Define SBOM metadata for a known ThirdParty module. -# Sets variables in the caller's scope: -# _spdx_license, _download_location, _supplier, _copyright, _version -# -function( - _itk_sbom_get_thirdparty_metadata - module_name - out_license - out_download - out_supplier - out_copyright - out_version -) - # Defaults - set(_license "NOASSERTION") - set(_download "NOASSERTION") - set(_supplier "NOASSERTION") - set(_copyright "NOASSERTION") - set(_version "NOASSERTION") - - if("${module_name}" STREQUAL "ITKDCMTK") - set(_license "BSD-3-Clause") - set(_download "https://dicom.offis.de/dcmtk") - set(_supplier "Organization: OFFIS e.V.") - set(_copyright "Copyright OFFIS e.V.") - set(_version "3.6.9") - elseif("${module_name}" STREQUAL "ITKDICOMParser") - set(_license "BSD-3-Clause") - set(_download "https://github.com/InsightSoftwareConsortium/ITK") - set(_supplier "Organization: Kitware Inc.") - set(_copyright "Copyright Kitware Inc.") - elseif("${module_name}" STREQUAL "ITKDoubleConversion") - set(_license "BSD-3-Clause") - set(_download "https://github.com/google/double-conversion") - set(_supplier "Organization: Google Inc.") - set(_copyright "Copyright Google Inc.") - set(_version "3.1.6") - elseif("${module_name}" STREQUAL "ITKEigen3") - set(_license "MPL-2.0") - set(_download "https://eigen.tuxfamily.org") - set(_supplier "Organization: Eigen") - set(_copyright "Copyright Eigen contributors") - # Try to detect Eigen version from CMake variable - if(DEFINED EIGEN3_VERSION_STRING) - set(_version "${EIGEN3_VERSION_STRING}") - elseif(DEFINED Eigen3_VERSION) - set(_version "${Eigen3_VERSION}") - endif() - elseif("${module_name}" STREQUAL "ITKExpat") - set(_license "MIT") - set(_download "https://libexpat.github.io") - set(_supplier "Organization: Expat development team") - set(_copyright "Copyright Expat development team") - elseif("${module_name}" STREQUAL "ITKGDCM") - set(_license "BSD-3-Clause") - set(_download "https://gdcm.sourceforge.net") - set(_supplier "Organization: GDCM contributors") - set(_copyright "Copyright GDCM contributors") - set(_version "3.2.2") - elseif("${module_name}" STREQUAL "ITKGIFTI") - set(_license "LicenseRef-NITRC-Public-Domain") - set(_download "https://www.nitrc.org/projects/gifti") - set(_supplier "Organization: NITRC") - set(_copyright "NOASSERTION") - elseif("${module_name}" STREQUAL "ITKGoogleTest") - set(_license "BSD-3-Clause") - set(_download "https://github.com/google/googletest") - set(_supplier "Organization: Google Inc.") - set(_copyright "Copyright Google Inc.") - set(_version "1.17.0") - elseif("${module_name}" STREQUAL "ITKHDF5") - set(_license "BSD-3-Clause") - set(_download "https://www.hdfgroup.org/solutions/hdf5") - set(_supplier "Organization: The HDF Group") - set(_copyright "Copyright The HDF Group") - # Try to detect HDF5 version from CMake variable - if(DEFINED HDF5_VERSION) - set(_version "${HDF5_VERSION}") - endif() - elseif("${module_name}" STREQUAL "ITKJPEG") - set(_license "IJG AND BSD-3-Clause AND Zlib") - set(_download "https://libjpeg-turbo.org") - set(_supplier "Organization: libjpeg-turbo") - set(_copyright "Copyright libjpeg-turbo contributors") - elseif("${module_name}" STREQUAL "ITKKWSys") - set(_license "BSD-3-Clause") - set(_download "https://gitlab.kitware.com/utils/kwsys") - set(_supplier "Organization: Kitware Inc.") - set(_copyright "Copyright Kitware Inc.") - elseif("${module_name}" STREQUAL "ITKMINC") - set(_license "LGPL-2.1-only") - set(_download "https://github.com/BIC-MNI/libminc") - set(_supplier "Organization: McConnell Brain Imaging Centre") - set(_copyright "Copyright McConnell Brain Imaging Centre") - elseif("${module_name}" STREQUAL "ITKMetaIO") - set(_license "Apache-2.0") - set(_download "https://github.com/Kitware/MetaIO") - set(_supplier "Organization: Kitware Inc.") - set(_copyright "Copyright Kitware Inc.") - elseif("${module_name}" STREQUAL "ITKNIFTI") - set(_license "LicenseRef-NIFTI-Public-Domain") - set(_download "https://nifti.nimh.nih.gov") - set(_supplier "Organization: NITRC") - set(_copyright "NOASSERTION") - elseif("${module_name}" STREQUAL "ITKNetlib") - set(_license "LicenseRef-Netlib-SLATEC") - set(_download "https://www.netlib.org/slatec") - set(_supplier "Organization: Netlib") - set(_copyright "NOASSERTION") - elseif("${module_name}" STREQUAL "ITKNrrdIO") - set(_license "LGPL-2.1-only") - set(_download "https://teem.sourceforge.net/nrrd") - set(_supplier "Organization: Teem") - set(_copyright "Copyright Teem contributors") - elseif("${module_name}" STREQUAL "ITKOpenJPEG") - set(_license "BSD-2-Clause") - set(_download "https://www.openjpeg.org") - set(_supplier "Organization: OpenJPEG contributors") - set(_copyright "Copyright OpenJPEG contributors") - set(_version "2.5.4") - elseif("${module_name}" STREQUAL "ITKPNG") - set(_license "Libpng-2.0") - set(_download "https://www.libpng.org/pub/png/libpng.html") - set(_supplier "Organization: libpng contributors") - set(_copyright "Copyright libpng contributors") - # Try to detect PNG version from CMake variable - if(DEFINED PNG_VERSION_STRING) - set(_version "${PNG_VERSION_STRING}") - endif() - elseif("${module_name}" STREQUAL "ITKTBB") - set(_license "Apache-2.0") - set(_download "https://github.com/oneapi-src/oneTBB") - set(_supplier "Organization: Intel Corporation") - set(_copyright "Copyright Intel Corporation") - # Try to detect TBB version from CMake variable - if(DEFINED TBB_VERSION) - set(_version "${TBB_VERSION}") - endif() - elseif("${module_name}" STREQUAL "ITKTIFF") - set(_license "libtiff") - set(_download "https://libtiff.maptools.org") - set(_supplier "Organization: libtiff contributors") - set(_copyright "Copyright libtiff contributors") - # Try to detect TIFF version from CMake variable - if(DEFINED TIFF_VERSION_STRING) - set(_version "${TIFF_VERSION_STRING}") - endif() - elseif("${module_name}" STREQUAL "ITKVNL") - set(_license "BSD-3-Clause") - set(_download "https://vxl.github.io") - set(_supplier "Organization: VXL contributors") - set(_copyright "Copyright VXL contributors") - elseif("${module_name}" STREQUAL "ITKZLIB") - set(_license "Zlib") - set(_download "https://github.com/zlib-ng/zlib-ng") - set(_supplier "Organization: zlib-ng contributors") - set(_copyright "Copyright zlib-ng contributors") - # Try to detect zlib version from CMake variable - if(DEFINED ZLIB_VERSION_STRING) - set(_version "${ZLIB_VERSION_STRING}") - endif() - elseif("${module_name}" STREQUAL "ITKLIBLBFGS") - set(_license "MIT") - set(_download "https://github.com/chokkan/liblbfgs") - set(_supplier "Organization: Naoaki Okazaki") - set(_copyright "Copyright Naoaki Okazaki") - endif() - - set(${out_license} "${_license}" PARENT_SCOPE) - set(${out_download} "${_download}" PARENT_SCOPE) - set(${out_supplier} "${_supplier}" PARENT_SCOPE) - set(${out_copyright} "${_copyright}" PARENT_SCOPE) - set(${out_version} "${_version}" PARENT_SCOPE) -endfunction() - #----------------------------------------------------------------------------- # Main function: Generate the SPDX 2.3 SBOM JSON document. # @@ -370,24 +202,29 @@ function(itk_generate_sbom) # Collect ThirdParty modules from enabled modules list set(_thirdparty_spdx_ids "") + # Track custom license refs for hasExtractedLicensingInfo + set(_custom_license_ids "") + set(_custom_license_names "") + set(_custom_license_texts "") foreach(_mod ${ITK_MODULES_ENABLED}) if(${_mod}_IS_TEST) continue() endif() - # Check if this is a ThirdParty module by examining its source directory - if(NOT DEFINED ${_mod}_SOURCE_DIR) - continue() - endif() - string(FIND "${${_mod}_SOURCE_DIR}" "Modules/ThirdParty" _tp_pos) - if(_tp_pos EQUAL -1) + # Only include modules that have SPDX metadata declared + set(_pkg_license "${ITK_MODULE_${_mod}_SPDX_LICENSE}") + if(NOT _pkg_license) continue() endif() - # Get metadata for this ThirdParty module - _itk_sbom_get_thirdparty_metadata("${_mod}" - _pkg_license _pkg_download _pkg_supplier _pkg_copyright _pkg_version - ) + set(_pkg_download "${ITK_MODULE_${_mod}_SPDX_DOWNLOAD_LOCATION}") + set(_pkg_copyright "${ITK_MODULE_${_mod}_SPDX_COPYRIGHT}") + if(NOT _pkg_download) + set(_pkg_download "NOASSERTION") + endif() + if(NOT _pkg_copyright) + set(_pkg_copyright "NOASSERTION") + endif() # Get description from module declaration and escape for JSON set(_pkg_description "${ITK_MODULE_${_mod}_DESCRIPTION}") @@ -395,6 +232,15 @@ function(itk_generate_sbom) _itk_sbom_json_escape("${_pkg_description}" _pkg_description) endif() + # Collect custom license references + set(_custom_text "${ITK_MODULE_${_mod}_SPDX_CUSTOM_LICENSE_TEXT}") + set(_custom_name "${ITK_MODULE_${_mod}_SPDX_CUSTOM_LICENSE_NAME}") + if(_custom_text AND _custom_name) + list(APPEND _custom_license_ids "${_pkg_license}") + list(APPEND _custom_license_names "${_custom_name}") + list(APPEND _custom_license_texts "${_custom_text}") + endif() + # Sanitize module name for SPDX ID string(REGEX REPLACE "[^A-Za-z0-9-]" "-" _spdx_id "${_mod}") list(APPEND _thirdparty_spdx_ids "SPDXRef-${_spdx_id}") @@ -403,9 +249,9 @@ function(itk_generate_sbom) string(APPEND _json " {\n") string(APPEND _json " \"SPDXID\": \"SPDXRef-${_spdx_id}\",\n") string(APPEND _json " \"name\": \"${_mod}\",\n") - string(APPEND _json " \"versionInfo\": \"${_pkg_version}\",\n") + string(APPEND _json " \"versionInfo\": \"NOASSERTION\",\n") string(APPEND _json " \"downloadLocation\": \"${_pkg_download}\",\n") - string(APPEND _json " \"supplier\": \"${_pkg_supplier}\",\n") + string(APPEND _json " \"supplier\": \"NOASSERTION\",\n") string(APPEND _json " \"licenseConcluded\": \"${_pkg_license}\",\n") string(APPEND _json " \"licenseDeclared\": \"${_pkg_license}\",\n") string(APPEND _json " \"copyrightText\": \"${_pkg_copyright}\",\n") @@ -493,57 +339,34 @@ function(itk_generate_sbom) string(APPEND _json " }") endforeach() - string(APPEND _json "\n ],\n") + string(APPEND _json "\n ]") # --- hasExtractedLicensingInfo for custom LicenseRef identifiers --- - string(APPEND _json " \"hasExtractedLicensingInfo\": [\n") - string(APPEND _json " {\n") - string( - APPEND - _json - " \"licenseId\": \"LicenseRef-NIFTI-Public-Domain\",\n" - ) - string(APPEND _json " \"name\": \"NIFTI Public Domain License\",\n") - string( - APPEND - _json - " \"extractedText\": \"This software is in the public domain. The NIFTI header and library are released into the public domain.\"\n" - ) - string(APPEND _json " },\n") - string(APPEND _json " {\n") - string( - APPEND - _json - " \"licenseId\": \"LicenseRef-NITRC-Public-Domain\",\n" - ) - string( - APPEND - _json - " \"name\": \"NITRC GIFTI Public Domain License\",\n" - ) - string( - APPEND - _json - " \"extractedText\": \"The GIFTI library is released into the public domain under the NITRC project.\"\n" - ) - string(APPEND _json " },\n") - string(APPEND _json " {\n") - string(APPEND _json " \"licenseId\": \"LicenseRef-Netlib-SLATEC\",\n") - string( - APPEND - _json - " \"name\": \"Netlib SLATEC Public Domain License\",\n" - ) - string( - APPEND - _json - " \"extractedText\": \"The SLATEC Common Mathematical Library is issued by the U.S. Government and is in the public domain.\"\n" - ) - string(APPEND _json " }\n") - string(APPEND _json " ]\n") + list(LENGTH _custom_license_ids _num_custom) + if(_num_custom GREATER 0) + string(APPEND _json ",\n") + string(APPEND _json " \"hasExtractedLicensingInfo\": [\n") + set(_first_custom TRUE) + math(EXPR _last_idx "${_num_custom} - 1") + foreach(_idx RANGE ${_last_idx}) + list(GET _custom_license_ids ${_idx} _lic_id) + list(GET _custom_license_names ${_idx} _lic_name) + list(GET _custom_license_texts ${_idx} _lic_text) + if(NOT _first_custom) + string(APPEND _json ",\n") + endif() + set(_first_custom FALSE) + string(APPEND _json " {\n") + string(APPEND _json " \"licenseId\": \"${_lic_id}\",\n") + string(APPEND _json " \"name\": \"${_lic_name}\",\n") + string(APPEND _json " \"extractedText\": \"${_lic_text}\"\n") + string(APPEND _json " }") + endforeach() + string(APPEND _json "\n ]") + endif() # --- Close JSON document --- - string(APPEND _json "}\n") + string(APPEND _json "\n}\n") # Write SBOM to build directory set(_sbom_file "${CMAKE_BINARY_DIR}/sbom.spdx.json") diff --git a/Modules/ThirdParty/DCMTK/itk-module.cmake b/Modules/ThirdParty/DCMTK/itk-module.cmake index 83cbf13f46f..17bd2e9aade 100644 --- a/Modules/ThirdParty/DCMTK/itk-module.cmake +++ b/Modules/ThirdParty/DCMTK/itk-module.cmake @@ -6,7 +6,17 @@ library suite." ) if(ITK_USE_SYSTEM_DCMTK) - itk_module(ITKDCMTK DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT) + itk_module( + ITKDCMTK + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://dicom.offis.de/dcmtk" + SPDX_COPYRIGHT + "Copyright OFFIS e.V." + ) else() itk_module( ITKDCMTK @@ -17,5 +27,11 @@ else() ITKPNG DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://dicom.offis.de/dcmtk" + SPDX_COPYRIGHT + "Copyright OFFIS e.V." ) endif() diff --git a/Modules/ThirdParty/DICOMParser/itk-module.cmake b/Modules/ThirdParty/DICOMParser/itk-module.cmake index d452bc733d3..d517f5d72b5 100644 --- a/Modules/ThirdParty/DICOMParser/itk-module.cmake +++ b/Modules/ThirdParty/DICOMParser/itk-module.cmake @@ -6,4 +6,13 @@ DICOMParser is a small, lightweight C++ toolkit for reading DICOM format medical image files." ) -itk_module(ITKDICOMParser DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKDICOMParser + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://github.com/InsightSoftwareConsortium/ITK" + SPDX_COPYRIGHT + "Copyright Kitware Inc." +) diff --git a/Modules/ThirdParty/DoubleConversion/itk-module.cmake b/Modules/ThirdParty/DoubleConversion/itk-module.cmake index 6fb81b78546..9c256d5c355 100644 --- a/Modules/ThirdParty/DoubleConversion/itk-module.cmake +++ b/Modules/ThirdParty/DoubleConversion/itk-module.cmake @@ -5,4 +5,13 @@ double-conversion library published by Google." ) -itk_module(ITKDoubleConversion DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKDoubleConversion + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://github.com/google/double-conversion" + SPDX_COPYRIGHT + "Copyright Google Inc." +) diff --git a/Modules/ThirdParty/Eigen3/itk-module.cmake b/Modules/ThirdParty/Eigen3/itk-module.cmake index c2783bfa2f2..1d4c82661c6 100644 --- a/Modules/ThirdParty/Eigen3/itk-module.cmake +++ b/Modules/ThirdParty/Eigen3/itk-module.cmake @@ -9,4 +9,10 @@ itk_module( DEPENDS DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "MPL-2.0" + SPDX_DOWNLOAD_LOCATION + "https://eigen.tuxfamily.org" + SPDX_COPYRIGHT + "Copyright Eigen contributors" ) diff --git a/Modules/ThirdParty/Expat/itk-module.cmake b/Modules/ThirdParty/Expat/itk-module.cmake index c51714845eb..ccd21b5c63a 100644 --- a/Modules/ThirdParty/Expat/itk-module.cmake +++ b/Modules/ThirdParty/Expat/itk-module.cmake @@ -5,4 +5,13 @@ href=\"http://expat.sourceforge.net/\">Expat library. Expat is an XML parser library written in C." ) -itk_module(ITKExpat DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKExpat + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "MIT" + SPDX_DOWNLOAD_LOCATION + "https://libexpat.github.io" + SPDX_COPYRIGHT + "Copyright Expat development team" +) diff --git a/Modules/ThirdParty/GDCM/itk-module.cmake b/Modules/ThirdParty/GDCM/itk-module.cmake index c50bb710e5d..ac028a5c61b 100644 --- a/Modules/ThirdParty/GDCM/itk-module.cmake +++ b/Modules/ThirdParty/GDCM/itk-module.cmake @@ -6,7 +6,17 @@ Grassroots DiCoM is a C++ library for DICOM medical files." ) if(ITK_USE_SYSTEM_GDCM) - itk_module(ITKGDCM DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT) + itk_module( + ITKGDCM + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://gdcm.sourceforge.net" + SPDX_COPYRIGHT + "Copyright GDCM contributors" + ) else() itk_module( ITKGDCM @@ -15,5 +25,11 @@ else() ITKExpat ITKOpenJPEG DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://gdcm.sourceforge.net" + SPDX_COPYRIGHT + "Copyright GDCM contributors" ) endif() diff --git a/Modules/ThirdParty/GIFTI/itk-module.cmake b/Modules/ThirdParty/GIFTI/itk-module.cmake index f13fc091ea5..84723025637 100644 --- a/Modules/ThirdParty/GIFTI/itk-module.cmake +++ b/Modules/ThirdParty/GIFTI/itk-module.cmake @@ -12,4 +12,14 @@ itk_module( ITKExpat ITKNIFTI DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "LicenseRef-NITRC-Public-Domain" + SPDX_DOWNLOAD_LOCATION + "https://www.nitrc.org/projects/gifti" + SPDX_COPYRIGHT + "NOASSERTION" + SPDX_CUSTOM_LICENSE_NAME + "NITRC GIFTI Public Domain License" + SPDX_CUSTOM_LICENSE_TEXT + "The GIFTI library is released into the public domain under the NITRC project." ) diff --git a/Modules/ThirdParty/GoogleTest/itk-module.cmake b/Modules/ThirdParty/GoogleTest/itk-module.cmake index 7473d474488..37b672310e5 100644 --- a/Modules/ThirdParty/GoogleTest/itk-module.cmake +++ b/Modules/ThirdParty/GoogleTest/itk-module.cmake @@ -4,4 +4,14 @@ set( Google's C++ test framework. This module provides the GTest::gtest and GTest::gtest_main targets in the build directory only, and are not installed." ) -itk_module(ITKGoogleTest DEPENDS DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKGoogleTest + DEPENDS + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://github.com/google/googletest" + SPDX_COPYRIGHT + "Copyright Google Inc." +) diff --git a/Modules/ThirdParty/HDF5/itk-module.cmake b/Modules/ThirdParty/HDF5/itk-module.cmake index 4e446253184..73ea064b93c 100644 --- a/Modules/ThirdParty/HDF5/itk-module.cmake +++ b/Modules/ThirdParty/HDF5/itk-module.cmake @@ -5,4 +5,15 @@ href=\"http://www.hdfgroup.org/HDF5/\">HDF5 library. HDF5 is a data model, library, and file format for storing and managing data." ) -itk_module(ITKHDF5 DEPENDS ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKHDF5 + DEPENDS + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://www.hdfgroup.org/solutions/hdf5" + SPDX_COPYRIGHT + "Copyright The HDF Group" +) diff --git a/Modules/ThirdParty/JPEG/itk-module.cmake b/Modules/ThirdParty/JPEG/itk-module.cmake index 4da1bfdcfca..c2defcbf44a 100644 --- a/Modules/ThirdParty/JPEG/itk-module.cmake +++ b/Modules/ThirdParty/JPEG/itk-module.cmake @@ -5,4 +5,13 @@ library published by the Independent JPEG Group and libjpeg-turbo." ) -itk_module(ITKJPEG DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKJPEG + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "IJG AND BSD-3-Clause AND Zlib" + SPDX_DOWNLOAD_LOCATION + "https://libjpeg-turbo.org" + SPDX_COPYRIGHT + "Copyright libjpeg-turbo contributors" +) diff --git a/Modules/ThirdParty/KWSys/itk-module.cmake b/Modules/ThirdParty/KWSys/itk-module.cmake index 36ebf9e0ae9..c1f6defbecb 100644 --- a/Modules/ThirdParty/KWSys/itk-module.cmake +++ b/Modules/ThirdParty/KWSys/itk-module.cmake @@ -7,4 +7,13 @@ library is intended to be shared among many projects. For more information, see Modules/ThirdParty/KWSys/src/README.kwsys." ) -itk_module(ITKKWSys DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKKWSys + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://gitlab.kitware.com/utils/kwsys" + SPDX_COPYRIGHT + "Copyright Kitware Inc." +) diff --git a/Modules/ThirdParty/MINC/itk-module.cmake b/Modules/ThirdParty/MINC/itk-module.cmake index 19eb4ba58b2..516c14d7d70 100644 --- a/Modules/ThirdParty/MINC/itk-module.cmake +++ b/Modules/ThirdParty/MINC/itk-module.cmake @@ -6,7 +6,17 @@ image file format library." ) if(ITK_USE_SYSTEM_MINC) - itk_module(ITKMINC DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT) + itk_module( + ITKMINC + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "LGPL-2.1-only" + SPDX_DOWNLOAD_LOCATION + "https://github.com/BIC-MNI/libminc" + SPDX_COPYRIGHT + "Copyright McConnell Brain Imaging Centre" + ) else() itk_module( ITKMINC @@ -16,5 +26,11 @@ else() ITKZLIB DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "LGPL-2.1-only" + SPDX_DOWNLOAD_LOCATION + "https://github.com/BIC-MNI/libminc" + SPDX_COPYRIGHT + "Copyright McConnell Brain Imaging Centre" ) endif() diff --git a/Modules/ThirdParty/MetaIO/itk-module.cmake b/Modules/ThirdParty/MetaIO/itk-module.cmake index 3d9c955a10c..6c633d1704f 100644 --- a/Modules/ThirdParty/MetaIO/itk-module.cmake +++ b/Modules/ThirdParty/MetaIO/itk-module.cmake @@ -8,4 +8,15 @@ vessels, needles, etc.), blobs (for arbitrary shaped objects), cubes, spheres, etc. The complete library is known as MetaIO." ) -itk_module(ITKMetaIO DEPENDS ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKMetaIO + DEPENDS + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "Apache-2.0" + SPDX_DOWNLOAD_LOCATION + "https://github.com/Kitware/MetaIO" + SPDX_COPYRIGHT + "Copyright Kitware Inc." +) diff --git a/Modules/ThirdParty/NIFTI/itk-module.cmake b/Modules/ThirdParty/NIFTI/itk-module.cmake index 43a6bde4e44..63c15891b9f 100644 --- a/Modules/ThirdParty/NIFTI/itk-module.cmake +++ b/Modules/ThirdParty/NIFTI/itk-module.cmake @@ -6,4 +6,19 @@ Neuroimaging Informatics Technology Initiative provides an Analyze-style MRI file format." ) -itk_module(ITKNIFTI DEPENDS ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKNIFTI + DEPENDS + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "LicenseRef-NIFTI-Public-Domain" + SPDX_DOWNLOAD_LOCATION + "https://nifti.nimh.nih.gov" + SPDX_COPYRIGHT + "NOASSERTION" + SPDX_CUSTOM_LICENSE_NAME + "NIFTI Public Domain License" + SPDX_CUSTOM_LICENSE_TEXT + "This software is in the public domain. The NIFTI header and library are released into the public domain." +) diff --git a/Modules/ThirdParty/Netlib/itk-module.cmake b/Modules/ThirdParty/Netlib/itk-module.cmake index 411112d5b39..a626fd4e5d8 100644 --- a/Modules/ThirdParty/Netlib/itk-module.cmake +++ b/Modules/ThirdParty/Netlib/itk-module.cmake @@ -5,4 +5,19 @@ href=\"http://www.netlib.org/slatec/\">netlib slatec routines. They are used by the probability distributions in ITK." ) -itk_module(ITKNetlib DEPENDS ITKVNL DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKNetlib + DEPENDS + ITKVNL + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "LicenseRef-Netlib-SLATEC" + SPDX_DOWNLOAD_LOCATION + "https://www.netlib.org/slatec" + SPDX_COPYRIGHT + "NOASSERTION" + SPDX_CUSTOM_LICENSE_NAME + "Netlib SLATEC Public Domain License" + SPDX_CUSTOM_LICENSE_TEXT + "The SLATEC Common Mathematical Library is issued by the U.S. Government and is in the public domain." +) diff --git a/Modules/ThirdParty/NrrdIO/itk-module.cmake b/Modules/ThirdParty/NrrdIO/itk-module.cmake index 91123ea8cd8..9fbadb50c36 100644 --- a/Modules/ThirdParty/NrrdIO/itk-module.cmake +++ b/Modules/ThirdParty/NrrdIO/itk-module.cmake @@ -4,4 +4,15 @@ set( href=\"http://teem.sourceforge.net/nrrd/lib.html\">NRRD image file format." ) -itk_module(ITKNrrdIO DEPENDS ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKNrrdIO + DEPENDS + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "LGPL-2.1-only" + SPDX_DOWNLOAD_LOCATION + "https://teem.sourceforge.net/nrrd" + SPDX_COPYRIGHT + "Copyright Teem contributors" +) diff --git a/Modules/ThirdParty/OpenJPEG/itk-module.cmake b/Modules/ThirdParty/OpenJPEG/itk-module.cmake index 21129c30e8e..9f88f7c71a2 100644 --- a/Modules/ThirdParty/OpenJPEG/itk-module.cmake +++ b/Modules/ThirdParty/OpenJPEG/itk-module.cmake @@ -7,4 +7,14 @@ has been developed in order to promote the use of JPEG 2000, the new still-image compression standard from the Joint Photographic Experts Group (JPEG)." ) -itk_module(ITKOpenJPEG EXCLUDE_FROM_DEFAULT DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKOpenJPEG + EXCLUDE_FROM_DEFAULT + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-2-Clause" + SPDX_DOWNLOAD_LOCATION + "https://www.openjpeg.org" + SPDX_COPYRIGHT + "Copyright OpenJPEG contributors" +) diff --git a/Modules/ThirdParty/PNG/itk-module.cmake b/Modules/ThirdParty/PNG/itk-module.cmake index ad742c3b0f0..c9994c1e202 100644 --- a/Modules/ThirdParty/PNG/itk-module.cmake +++ b/Modules/ThirdParty/PNG/itk-module.cmake @@ -5,4 +5,15 @@ href=\"http://www.libpng.org/pub/png/libpng.html/\">Portable Network Graphics (PNG) image file format library." ) -itk_module(ITKPNG DEPENDS ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKPNG + DEPENDS + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "Libpng-2.0" + SPDX_DOWNLOAD_LOCATION + "https://www.libpng.org/pub/png/libpng.html" + SPDX_COPYRIGHT + "Copyright libpng contributors" +) diff --git a/Modules/ThirdParty/TBB/itk-module.cmake b/Modules/ThirdParty/TBB/itk-module.cmake index 63ebc3ad4b3..305b388fdda 100644 --- a/Modules/ThirdParty/TBB/itk-module.cmake +++ b/Modules/ThirdParty/TBB/itk-module.cmake @@ -7,4 +7,14 @@ TBB is Intel TBB threading library." # ITKTBB module needs to be defined even if ITK_USE_TBB # is OFF, otherwise ITK cannot compile. -itk_module(ITKTBB DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT) +itk_module( + ITKTBB + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT + SPDX_LICENSE + "Apache-2.0" + SPDX_DOWNLOAD_LOCATION + "https://github.com/oneapi-src/oneTBB" + SPDX_COPYRIGHT + "Copyright Intel Corporation" +) diff --git a/Modules/ThirdParty/TIFF/itk-module.cmake b/Modules/ThirdParty/TIFF/itk-module.cmake index 5d005104093..0296cda2cfe 100644 --- a/Modules/ThirdParty/TIFF/itk-module.cmake +++ b/Modules/ThirdParty/TIFF/itk-module.cmake @@ -11,4 +11,10 @@ itk_module( ITKZLIB ITKJPEG DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "libtiff" + SPDX_DOWNLOAD_LOCATION + "https://libtiff.maptools.org" + SPDX_COPYRIGHT + "Copyright libtiff contributors" ) diff --git a/Modules/ThirdParty/VNL/itk-module.cmake b/Modules/ThirdParty/VNL/itk-module.cmake index 957d4ccc0f4..ea9c4e5f07a 100644 --- a/Modules/ThirdParty/VNL/itk-module.cmake +++ b/Modules/ThirdParty/VNL/itk-module.cmake @@ -5,4 +5,13 @@ href=\"http://vxl.sourceforge.net\">VNL numeric library from the VXL vision library suite." ) -itk_module(ITKVNL DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKVNL + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "BSD-3-Clause" + SPDX_DOWNLOAD_LOCATION + "https://vxl.github.io" + SPDX_COPYRIGHT + "Copyright VXL contributors" +) diff --git a/Modules/ThirdParty/ZLIB/itk-module.cmake b/Modules/ThirdParty/ZLIB/itk-module.cmake index 547b75167d6..cd09ec6f4ec 100644 --- a/Modules/ThirdParty/ZLIB/itk-module.cmake +++ b/Modules/ThirdParty/ZLIB/itk-module.cmake @@ -6,4 +6,13 @@ general purpose data compression library, designed as a drop-in replacement for ZLIB." ) -itk_module(ITKZLIB DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKZLIB + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "Zlib" + SPDX_DOWNLOAD_LOCATION + "https://github.com/zlib-ng/zlib-ng" + SPDX_COPYRIGHT + "Copyright zlib-ng contributors" +) diff --git a/Modules/ThirdParty/libLBFGS/itk-module.cmake b/Modules/ThirdParty/libLBFGS/itk-module.cmake index 3ef2ce59ed8..1ce757eaca5 100644 --- a/Modules/ThirdParty/libLBFGS/itk-module.cmake +++ b/Modules/ThirdParty/libLBFGS/itk-module.cmake @@ -5,4 +5,13 @@ href=\"https://github.com/chokkan/liblbfgs\">libLBFGS library, a C++ implementaiton of the LBFGS implementation in Netlib." ) -itk_module(ITKLIBLBFGS DESCRIPTION "${DOCUMENTATION}") +itk_module( + ITKLIBLBFGS + DESCRIPTION "${DOCUMENTATION}" + SPDX_LICENSE + "MIT" + SPDX_DOWNLOAD_LOCATION + "https://github.com/chokkan/liblbfgs" + SPDX_COPYRIGHT + "Copyright Naoaki Okazaki" +) From a7e8acae37cd847b04edbff564b819641a5e79e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 20:34:52 -0500 Subject: [PATCH 3/8] BUG: Fix JSON escaping in SBOM hasExtractedLicensingInfo Custom license names and extracted texts were written directly into the JSON output without escaping. Any text containing quotes, backslashes, or newlines would produce invalid JSON. Apply _itk_sbom_json_escape() to both the name and extractedText fields before embedding. --- CMake/ITKSBOMGeneration.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake index 041b763bc83..f59e13827b5 100644 --- a/CMake/ITKSBOMGeneration.cmake +++ b/CMake/ITKSBOMGeneration.cmake @@ -356,10 +356,12 @@ function(itk_generate_sbom) string(APPEND _json ",\n") endif() set(_first_custom FALSE) + _itk_sbom_json_escape("${_lic_name}" _lic_name_escaped) + _itk_sbom_json_escape("${_lic_text}" _lic_text_escaped) string(APPEND _json " {\n") string(APPEND _json " \"licenseId\": \"${_lic_id}\",\n") - string(APPEND _json " \"name\": \"${_lic_name}\",\n") - string(APPEND _json " \"extractedText\": \"${_lic_text}\"\n") + string(APPEND _json " \"name\": \"${_lic_name_escaped}\",\n") + string(APPEND _json " \"extractedText\": \"${_lic_text_escaped}\"\n") string(APPEND _json " }") endforeach() string(APPEND _json "\n ]") From d77ee3a5cbef73a58cc311ec03a53754acfe20f6 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Tue, 14 Apr 2026 20:35:10 -0500 Subject: [PATCH 4/8] ENH: Add SPDX_VERSION parameter with vendored dependency versions Add SPDX_VERSION to the itk_module() macro for declaring the version of each vendored third-party dependency. Populate versions for 11 modules extractable from source headers or UpdateFromUpstream.sh tags: Eigen3 (3.4.90), Expat (2.7.4), GoogleTest (1.17.0), HDF5 (1.14.5), JPEG/libjpeg-turbo (3.0.4), MINC (2.4.06), NIFTI (3.0.0), OpenJPEG (2.5.4), PNG (1.6.54), TIFF (4.7.0), ZLIB (2.2.5) Also fix Eigen3 license from "MPL-2.0" to "MPL-2.0 OR Apache-2.0" (dual-licensed) and update NIFTI download URL to the current GitHub repository. --- CMake/ITKModuleMacros.cmake | 6 +++++- CMake/ITKSBOMGeneration.cmake | 7 ++++++- Modules/ThirdParty/Eigen3/itk-module.cmake | 6 ++++-- Modules/ThirdParty/Expat/itk-module.cmake | 2 ++ Modules/ThirdParty/GoogleTest/itk-module.cmake | 2 ++ Modules/ThirdParty/HDF5/itk-module.cmake | 2 ++ Modules/ThirdParty/JPEG/itk-module.cmake | 2 ++ Modules/ThirdParty/MINC/itk-module.cmake | 4 ++++ Modules/ThirdParty/NIFTI/itk-module.cmake | 4 +++- Modules/ThirdParty/OpenJPEG/itk-module.cmake | 2 ++ Modules/ThirdParty/PNG/itk-module.cmake | 2 ++ Modules/ThirdParty/TIFF/itk-module.cmake | 2 ++ Modules/ThirdParty/ZLIB/itk-module.cmake | 2 ++ 13 files changed, 38 insertions(+), 5 deletions(-) diff --git a/CMake/ITKModuleMacros.cmake b/CMake/ITKModuleMacros.cmake index 4a933db26d8..a5e8735dc96 100644 --- a/CMake/ITKModuleMacros.cmake +++ b/CMake/ITKModuleMacros.cmake @@ -70,6 +70,7 @@ macro(itk_module _name) set(ITK_MODULE_${itk-module}_EXCLUDE_FROM_DEFAULT 0) set(ITK_MODULE_${itk-module}_ENABLE_SHARED 0) set(ITK_MODULE_${itk-module}_SPDX_LICENSE "") + set(ITK_MODULE_${itk-module}_SPDX_VERSION "") set(ITK_MODULE_${itk-module}_SPDX_DOWNLOAD_LOCATION "") set(ITK_MODULE_${itk-module}_SPDX_COPYRIGHT "") set(ITK_MODULE_${itk-module}_SPDX_CUSTOM_LICENSE_TEXT "") @@ -79,7 +80,7 @@ macro(itk_module _name) if( "${arg}" MATCHES - "^((|COMPILE_|PRIVATE_|TEST_|)DEPENDS|DESCRIPTION|DEFAULT|FACTORY_NAMES|SPDX_LICENSE|SPDX_DOWNLOAD_LOCATION|SPDX_COPYRIGHT|SPDX_CUSTOM_LICENSE_TEXT|SPDX_CUSTOM_LICENSE_NAME)$" + "^((|COMPILE_|PRIVATE_|TEST_|)DEPENDS|DESCRIPTION|DEFAULT|FACTORY_NAMES|SPDX_LICENSE|SPDX_VERSION|SPDX_DOWNLOAD_LOCATION|SPDX_COPYRIGHT|SPDX_CUSTOM_LICENSE_TEXT|SPDX_CUSTOM_LICENSE_NAME)$" ) set(_doing "${arg}") elseif("${arg}" MATCHES "^EXCLUDE_FROM_DEFAULT$") @@ -112,6 +113,9 @@ macro(itk_module _name) elseif("${_doing}" MATCHES "^SPDX_LICENSE$") set(_doing "") set(ITK_MODULE_${itk-module}_SPDX_LICENSE "${arg}") + elseif("${_doing}" MATCHES "^SPDX_VERSION$") + set(_doing "") + set(ITK_MODULE_${itk-module}_SPDX_VERSION "${arg}") elseif("${_doing}" MATCHES "^SPDX_DOWNLOAD_LOCATION$") set(_doing "") set(ITK_MODULE_${itk-module}_SPDX_DOWNLOAD_LOCATION "${arg}") diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake index f59e13827b5..01321241f76 100644 --- a/CMake/ITKSBOMGeneration.cmake +++ b/CMake/ITKSBOMGeneration.cmake @@ -8,6 +8,7 @@ Per-module SPDX metadata is declared in each module's itk-module.cmake via the itk_module() macro arguments: SPDX_LICENSE - SPDX license identifier (e.g. "Apache-2.0") + SPDX_VERSION - Version of the vendored dependency SPDX_DOWNLOAD_LOCATION - URL for the upstream source SPDX_COPYRIGHT - Copyright text SPDX_CUSTOM_LICENSE_TEXT - Extracted text for custom LicenseRef-* IDs @@ -217,8 +218,12 @@ function(itk_generate_sbom) continue() endif() + set(_pkg_version "${ITK_MODULE_${_mod}_SPDX_VERSION}") set(_pkg_download "${ITK_MODULE_${_mod}_SPDX_DOWNLOAD_LOCATION}") set(_pkg_copyright "${ITK_MODULE_${_mod}_SPDX_COPYRIGHT}") + if(NOT _pkg_version) + set(_pkg_version "NOASSERTION") + endif() if(NOT _pkg_download) set(_pkg_download "NOASSERTION") endif() @@ -249,7 +254,7 @@ function(itk_generate_sbom) string(APPEND _json " {\n") string(APPEND _json " \"SPDXID\": \"SPDXRef-${_spdx_id}\",\n") string(APPEND _json " \"name\": \"${_mod}\",\n") - string(APPEND _json " \"versionInfo\": \"NOASSERTION\",\n") + string(APPEND _json " \"versionInfo\": \"${_pkg_version}\",\n") string(APPEND _json " \"downloadLocation\": \"${_pkg_download}\",\n") string(APPEND _json " \"supplier\": \"NOASSERTION\",\n") string(APPEND _json " \"licenseConcluded\": \"${_pkg_license}\",\n") diff --git a/Modules/ThirdParty/Eigen3/itk-module.cmake b/Modules/ThirdParty/Eigen3/itk-module.cmake index 1d4c82661c6..21a646bd1cd 100644 --- a/Modules/ThirdParty/Eigen3/itk-module.cmake +++ b/Modules/ThirdParty/Eigen3/itk-module.cmake @@ -10,9 +10,11 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT SPDX_LICENSE - "MPL-2.0" + "MPL-2.0 OR Apache-2.0" + SPDX_VERSION + "3.4.90" SPDX_DOWNLOAD_LOCATION - "https://eigen.tuxfamily.org" + "https://gitlab.com/libeigen/eigen" SPDX_COPYRIGHT "Copyright Eigen contributors" ) diff --git a/Modules/ThirdParty/Expat/itk-module.cmake b/Modules/ThirdParty/Expat/itk-module.cmake index ccd21b5c63a..aba1f536b62 100644 --- a/Modules/ThirdParty/Expat/itk-module.cmake +++ b/Modules/ThirdParty/Expat/itk-module.cmake @@ -10,6 +10,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "MIT" + SPDX_VERSION + "2.7.4" SPDX_DOWNLOAD_LOCATION "https://libexpat.github.io" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/GoogleTest/itk-module.cmake b/Modules/ThirdParty/GoogleTest/itk-module.cmake index 37b672310e5..0077baa4eae 100644 --- a/Modules/ThirdParty/GoogleTest/itk-module.cmake +++ b/Modules/ThirdParty/GoogleTest/itk-module.cmake @@ -10,6 +10,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "BSD-3-Clause" + SPDX_VERSION + "1.17.0" SPDX_DOWNLOAD_LOCATION "https://github.com/google/googletest" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/HDF5/itk-module.cmake b/Modules/ThirdParty/HDF5/itk-module.cmake index 73ea064b93c..ab10c5ded45 100644 --- a/Modules/ThirdParty/HDF5/itk-module.cmake +++ b/Modules/ThirdParty/HDF5/itk-module.cmake @@ -12,6 +12,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "BSD-3-Clause" + SPDX_VERSION + "1.14.5" SPDX_DOWNLOAD_LOCATION "https://www.hdfgroup.org/solutions/hdf5" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/JPEG/itk-module.cmake b/Modules/ThirdParty/JPEG/itk-module.cmake index c2defcbf44a..c0d033f0f89 100644 --- a/Modules/ThirdParty/JPEG/itk-module.cmake +++ b/Modules/ThirdParty/JPEG/itk-module.cmake @@ -10,6 +10,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "IJG AND BSD-3-Clause AND Zlib" + SPDX_VERSION + "3.0.4" SPDX_DOWNLOAD_LOCATION "https://libjpeg-turbo.org" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/MINC/itk-module.cmake b/Modules/ThirdParty/MINC/itk-module.cmake index 516c14d7d70..4774b78701b 100644 --- a/Modules/ThirdParty/MINC/itk-module.cmake +++ b/Modules/ThirdParty/MINC/itk-module.cmake @@ -12,6 +12,8 @@ if(ITK_USE_SYSTEM_MINC) EXCLUDE_FROM_DEFAULT SPDX_LICENSE "LGPL-2.1-only" + SPDX_VERSION + "2.4.06" SPDX_DOWNLOAD_LOCATION "https://github.com/BIC-MNI/libminc" SPDX_COPYRIGHT @@ -28,6 +30,8 @@ else() EXCLUDE_FROM_DEFAULT SPDX_LICENSE "LGPL-2.1-only" + SPDX_VERSION + "2.4.06" SPDX_DOWNLOAD_LOCATION "https://github.com/BIC-MNI/libminc" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/NIFTI/itk-module.cmake b/Modules/ThirdParty/NIFTI/itk-module.cmake index 63c15891b9f..41ccb7dcf1b 100644 --- a/Modules/ThirdParty/NIFTI/itk-module.cmake +++ b/Modules/ThirdParty/NIFTI/itk-module.cmake @@ -13,8 +13,10 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "LicenseRef-NIFTI-Public-Domain" + SPDX_VERSION + "3.0.0" SPDX_DOWNLOAD_LOCATION - "https://nifti.nimh.nih.gov" + "https://github.com/NIFTI-Imaging/nifti_clib" SPDX_COPYRIGHT "NOASSERTION" SPDX_CUSTOM_LICENSE_NAME diff --git a/Modules/ThirdParty/OpenJPEG/itk-module.cmake b/Modules/ThirdParty/OpenJPEG/itk-module.cmake index 9f88f7c71a2..d0a16b0f17e 100644 --- a/Modules/ThirdParty/OpenJPEG/itk-module.cmake +++ b/Modules/ThirdParty/OpenJPEG/itk-module.cmake @@ -13,6 +13,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "BSD-2-Clause" + SPDX_VERSION + "2.5.4" SPDX_DOWNLOAD_LOCATION "https://www.openjpeg.org" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/PNG/itk-module.cmake b/Modules/ThirdParty/PNG/itk-module.cmake index c9994c1e202..0e2c276c9f3 100644 --- a/Modules/ThirdParty/PNG/itk-module.cmake +++ b/Modules/ThirdParty/PNG/itk-module.cmake @@ -12,6 +12,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "Libpng-2.0" + SPDX_VERSION + "1.6.54" SPDX_DOWNLOAD_LOCATION "https://www.libpng.org/pub/png/libpng.html" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/TIFF/itk-module.cmake b/Modules/ThirdParty/TIFF/itk-module.cmake index 0296cda2cfe..adffb740178 100644 --- a/Modules/ThirdParty/TIFF/itk-module.cmake +++ b/Modules/ThirdParty/TIFF/itk-module.cmake @@ -13,6 +13,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "libtiff" + SPDX_VERSION + "4.7.0" SPDX_DOWNLOAD_LOCATION "https://libtiff.maptools.org" SPDX_COPYRIGHT diff --git a/Modules/ThirdParty/ZLIB/itk-module.cmake b/Modules/ThirdParty/ZLIB/itk-module.cmake index cd09ec6f4ec..4ce178fd719 100644 --- a/Modules/ThirdParty/ZLIB/itk-module.cmake +++ b/Modules/ThirdParty/ZLIB/itk-module.cmake @@ -11,6 +11,8 @@ itk_module( DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE "Zlib" + SPDX_VERSION + "2.2.5" SPDX_DOWNLOAD_LOCATION "https://github.com/zlib-ng/zlib-ng" SPDX_COPYRIGHT From 2fb9d9b6e1b2df471e7b4dedaabfa2c26db290ab Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Tue, 14 Apr 2026 20:35:42 -0500 Subject: [PATCH 5/8] ENH: Add SBOM JSON validation CTest Add ITKSBOMValidation test that validates the generated sbom.spdx.json at test time: checks JSON syntax, required SPDX 2.3 fields, at least one package, DESCRIBES relationship, unique SPDX IDs, and extracted license entries. Runs only when ITK_GENERATE_SBOM=ON and BUILD_TESTING=ON. --- CMake/ITKSBOMValidation.cmake | 90 +++++++++++++++++++++++++++++++++++ CMakeLists.txt | 1 + 2 files changed, 91 insertions(+) create mode 100644 CMake/ITKSBOMValidation.cmake diff --git a/CMake/ITKSBOMValidation.cmake b/CMake/ITKSBOMValidation.cmake new file mode 100644 index 00000000000..6024e939005 --- /dev/null +++ b/CMake/ITKSBOMValidation.cmake @@ -0,0 +1,90 @@ +#[=============================================================================[ + ITKSBOMValidation.cmake - Validate the generated SPDX SBOM document + + Adds a CTest test that validates the SBOM JSON file produced by + ITKSBOMGeneration.cmake. Checks: + 1. Valid JSON syntax + 2. Required SPDX 2.3 top-level fields present + 3. At least one package (ITK itself) + 4. DESCRIBES relationship present +#]=============================================================================] + +if(NOT ITK_GENERATE_SBOM OR NOT BUILD_TESTING) + return() +endif() + +set(_sbom_file "${CMAKE_BINARY_DIR}/sbom.spdx.json") +if(NOT EXISTS "${_sbom_file}") + return() +endif() + +add_test( + NAME ITKSBOMValidation + COMMAND + ${Python3_EXECUTABLE} -c + " +import json, sys + +with open(sys.argv[1]) as f: + doc = json.load(f) + +errors = [] + +# Required SPDX 2.3 fields +for field in ['spdxVersion', 'dataLicense', 'SPDXID', 'name', + 'documentNamespace', 'creationInfo', 'packages', + 'relationships']: + if field not in doc: + errors.append(f'Missing required field: {field}') + +if doc.get('spdxVersion') != 'SPDX-2.3': + errors.append(f'Expected spdxVersion SPDX-2.3, got {doc.get(\"spdxVersion\")}') + +if doc.get('dataLicense') != 'CC0-1.0': + errors.append(f'Expected dataLicense CC0-1.0, got {doc.get(\"dataLicense\")}') + +# Must have at least ITK package +packages = doc.get('packages', []) +if len(packages) < 1: + errors.append('No packages found') + +itk_pkg = next((p for p in packages if p.get('name') == 'ITK'), None) +if itk_pkg is None: + errors.append('ITK package not found') + +# Check DESCRIBES relationship exists +rels = doc.get('relationships', []) +describes = [r for r in rels if r.get('relationshipType') == 'DESCRIBES'] +if not describes: + errors.append('No DESCRIBES relationship found') + +# Validate package SPDX IDs are unique +spdx_ids = [p.get('SPDXID') for p in packages] +dupes = set(x for x in spdx_ids if spdx_ids.count(x) > 1) +if dupes: + errors.append(f'Duplicate SPDX IDs: {dupes}') + +# Check hasExtractedLicensingInfo references are valid +extracted = doc.get('hasExtractedLicensingInfo', []) +for entry in extracted: + if 'licenseId' not in entry: + errors.append(f'Extracted license missing licenseId') + if 'extractedText' not in entry: + errors.append(f'Extracted license missing extractedText') + +if errors: + for e in errors: + print(f'SBOM ERROR: {e}', file=sys.stderr) + sys.exit(1) + +print(f'SBOM valid: {len(packages)} packages, {len(rels)} relationships, ' + f'{len(extracted)} extracted licenses') +" + "${_sbom_file}" +) +set_tests_properties( + ITKSBOMValidation + PROPERTIES + LABELS + "SBOM" +) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7452f611271..46a1b2c0ed7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -757,6 +757,7 @@ include(ITKSBOMGeneration) if(ITK_GENERATE_SBOM) itk_generate_sbom() endif() +include(ITKSBOMValidation) # Setup clang-tidy for code best-practices enforcement for C++11 include(ITKClangTidySetup) From bc8d90bd8f7e3a885fea8f2ff5b7666e3646cca9 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Tue, 14 Apr 2026 20:36:00 -0500 Subject: [PATCH 6/8] ENH: Add SPDX version consistency test against UpdateFromUpstream.sh Add VerifySPDXVersions.py that cross-checks SPDX_VERSION declared in each itk-module.cmake against the tag in UpdateFromUpstream.sh. For modules with parseable version tags (v1.2.3, R_2_7_4, hdf5_1.14.5, bare semver), the test verifies consistency. Modules tracking master, commit SHAs, or custom ITK tags are skipped. This catches the case where a vendored dependency is updated via UpdateFromUpstream.sh but SPDX_VERSION is not bumped. Currently validates 8 of 16 modules with UpdateFromUpstream.sh scripts. --- CMake/ITKSBOMValidation.cmake | 17 +++ Utilities/Maintenance/VerifySPDXVersions.py | 141 ++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100755 Utilities/Maintenance/VerifySPDXVersions.py diff --git a/CMake/ITKSBOMValidation.cmake b/CMake/ITKSBOMValidation.cmake index 6024e939005..138a54ff41e 100644 --- a/CMake/ITKSBOMValidation.cmake +++ b/CMake/ITKSBOMValidation.cmake @@ -88,3 +88,20 @@ set_tests_properties( LABELS "SBOM" ) + +# Verify SPDX_VERSION entries match UpdateFromUpstream.sh tags. +# This catches the case where a vendored dependency is updated but +# the SPDX_VERSION in itk-module.cmake is not bumped. +add_test( + NAME ITKSBOMVersionConsistency + COMMAND + ${Python3_EXECUTABLE} + "${ITK_SOURCE_DIR}/Utilities/Maintenance/VerifySPDXVersions.py" + "${ITK_SOURCE_DIR}" +) +set_tests_properties( + ITKSBOMVersionConsistency + PROPERTIES + LABELS + "SBOM" +) diff --git a/Utilities/Maintenance/VerifySPDXVersions.py b/Utilities/Maintenance/VerifySPDXVersions.py new file mode 100755 index 00000000000..26ab562ae79 --- /dev/null +++ b/Utilities/Maintenance/VerifySPDXVersions.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""Verify SPDX_VERSION in itk-module.cmake matches UpdateFromUpstream.sh tags. + +For each ThirdParty module that has both an UpdateFromUpstream.sh with a +parseable version tag and an SPDX_VERSION in itk-module.cmake, verify +they are consistent. + +Modules tracking 'master', commit SHAs, or custom ITK tags are skipped +since their version cannot be derived from the tag alone. + +Exit code 0 if all checked modules match, 1 if any mismatch is found. +""" + +import re +import sys +from pathlib import Path + + +def extract_tag_from_upstream_script(script_path: Path) -> str | None: + """Extract the 'tag' value from UpdateFromUpstream.sh.""" + text = script_path.read_text() + # Match: readonly tag="..." or tag="..." or readonly tag='...' + m = re.search(r"""(?:readonly\s+)?tag\s*=\s*['"]([^'"]+)['"]""", text) + return m.group(1) if m else None + + +def normalize_version_from_tag(tag: str) -> str | None: + """Attempt to extract a semver-like version from a git tag. + + Returns None if the tag is a SHA, 'master', or an unrecognizable format. + """ + # Skip SHAs (40-hex or short) + if re.fullmatch(r"[0-9a-f]{7,40}", tag): + return None + # Skip 'master' or 'main' + if tag in ("master", "main"): + return None + # Skip custom ITK tags like 'for/itk-20260305-4c99fca' + if tag.startswith("for/"): + return None + + # Try common patterns: + # v1.2.3 or V1.2.3 + m = re.fullmatch(r"[vV]?(\d+\.\d+(?:\.\d+)?)", tag) + if m: + return m.group(1) + + # hdf5_1.14.5 + m = re.fullmatch(r"[a-zA-Z0-9]+[_-](\d+\.\d+(?:\.\d+)?)", tag) + if m: + return m.group(1) + + # R_2_7_4 (Expat style) + m = re.fullmatch(r"R_(\d+)_(\d+)_(\d+)", tag) + if m: + return f"{m.group(1)}.{m.group(2)}.{m.group(3)}" + + # Bare version like 2.2.5 or 3.0.4 + m = re.fullmatch(r"(\d+\.\d+(?:\.\d+)?)", tag) + if m: + return m.group(1) + + return None + + +def extract_spdx_version(module_cmake: Path) -> str | None: + """Extract SPDX_VERSION value from itk-module.cmake.""" + text = module_cmake.read_text() + m = re.search(r'SPDX_VERSION\s+"([^"]+)"', text) + return m.group(1) if m else None + + +def main() -> int: + if len(sys.argv) > 1: + itk_source = Path(sys.argv[1]) + else: + # Default: assume running from ITK source root + itk_source = Path(__file__).resolve().parents[2] + + thirdparty_dir = itk_source / "Modules" / "ThirdParty" + if not thirdparty_dir.is_dir(): + print(f"ERROR: {thirdparty_dir} not found", file=sys.stderr) + return 1 + + errors = [] + checked = 0 + skipped = 0 + + for module_dir in sorted(thirdparty_dir.iterdir()): + if not module_dir.is_dir(): + continue + + upstream_script = module_dir / "UpdateFromUpstream.sh" + module_cmake = module_dir / "itk-module.cmake" + + if not upstream_script.exists() or not module_cmake.exists(): + continue + + tag = extract_tag_from_upstream_script(upstream_script) + if tag is None: + skipped += 1 + continue + + expected_version = normalize_version_from_tag(tag) + if expected_version is None: + skipped += 1 + continue + + declared_version = extract_spdx_version(module_cmake) + if declared_version is None: + errors.append( + f"{module_dir.name}: UpdateFromUpstream.sh tag='{tag}' " + f"implies version {expected_version}, but no SPDX_VERSION " + f"declared in itk-module.cmake" + ) + checked += 1 + continue + + if declared_version != expected_version: + errors.append( + f"{module_dir.name}: SPDX_VERSION='{declared_version}' " + f"does not match UpdateFromUpstream.sh tag='{tag}' " + f"(expected '{expected_version}')" + ) + + checked += 1 + + print(f"Checked {checked} modules, skipped {skipped} " f"(master/SHA/custom tags)") + + if errors: + print(f"\n{len(errors)} version mismatch(es):", file=sys.stderr) + for e in errors: + print(f" FAIL: {e}", file=sys.stderr) + return 1 + + print("All SPDX_VERSION entries match UpdateFromUpstream.sh tags.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 6359e818a0ce46af447bdb605b72c8f1bdb2d77a Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Tue, 14 Apr 2026 20:42:15 -0500 Subject: [PATCH 7/8] ENH: Warn when ThirdParty modules lack SPDX metadata Add AUTHOR_WARNING during SBOM generation for enabled ThirdParty modules that have no SPDX_LICENSE declared in their itk-module.cmake. This encourages remote module and ThirdParty maintainers to add SPDX metadata for supply chain compliance. --- CMake/ITKSBOMGeneration.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake index 01321241f76..2e952afbf22 100644 --- a/CMake/ITKSBOMGeneration.cmake +++ b/CMake/ITKSBOMGeneration.cmake @@ -215,6 +215,13 @@ function(itk_generate_sbom) # Only include modules that have SPDX metadata declared set(_pkg_license "${ITK_MODULE_${_mod}_SPDX_LICENSE}") if(NOT _pkg_license) + if(${_mod}_THIRD_PARTY) + message( + AUTHOR_WARNING + "ThirdParty module ${_mod} has no SPDX_LICENSE in itk-module.cmake. " + "Please add SPDX metadata for SBOM compliance." + ) + endif() continue() endif() From 6d31421fb1fa9245c4797eb737656ebd9303d4f0 Mon Sep 17 00:00:00 2001 From: "Hans J. Johnson" Date: Wed, 15 Apr 2026 14:54:55 -0500 Subject: [PATCH 8/8] BUG: Fix SBOM JSON escaping, Python3 guard, and SPDX metadata issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address all findings from greptile code review: P1: Escape all user-supplied fields (version, download location, license, copyright) through _itk_sbom_json_escape() before JSON interpolation — both in itk_generate_sbom() and itk_sbom_register_package(). Previously only description was escaped. P1: Add Python3_EXECUTABLE guard in ITKSBOMValidation.cmake to skip SBOM validation tests when Python3 is not available. P2: Use foreach(... IN LISTS ...) instead of unquoted variable expansion to prevent re-splitting on embedded semicolons. P2: Fix PNG SPDX license identifier from "Libpng-2.0" to "Libpng" (the valid SPDX license list entry for libpng 1.6.x). P2: Expose licenseListVersion as ITK_SBOM_SPDX_LICENSE_LIST_VERSION cache variable (default "3.25") instead of hardcoding "3.22". --- CMake/ITKSBOMGeneration.cmake | 54 ++++++++++++++++++++----- CMake/ITKSBOMValidation.cmake | 5 +++ Modules/ThirdParty/PNG/itk-module.cmake | 2 +- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/CMake/ITKSBOMGeneration.cmake b/CMake/ITKSBOMGeneration.cmake index 2e952afbf22..a26f4edb9ce 100644 --- a/CMake/ITKSBOMGeneration.cmake +++ b/CMake/ITKSBOMGeneration.cmake @@ -26,6 +26,13 @@ if(NOT ITK_GENERATE_SBOM) return() endif() +set( + ITK_SBOM_SPDX_LICENSE_LIST_VERSION + "3.25" + CACHE STRING + "SPDX license list version recorded in the generated SBOM" +) + #----------------------------------------------------------------------------- # Allow remote modules to register SBOM package metadata. # @@ -90,24 +97,40 @@ function(itk_sbom_register_package) # Sanitize the name for use as SPDX ID (only alphanumeric and -) string(REGEX REPLACE "[^A-Za-z0-9-]" "-" _spdx_id "${_pkg_NAME}") + # Escape all user-supplied fields for JSON safety + _itk_sbom_json_escape("${_pkg_NAME}" _pkg_NAME_escaped) + _itk_sbom_json_escape("${_pkg_VERSION}" _pkg_VERSION_escaped) + _itk_sbom_json_escape("${_pkg_DOWNLOAD_LOCATION}" _pkg_DOWNLOAD_escaped) + _itk_sbom_json_escape("${_pkg_SUPPLIER}" _pkg_SUPPLIER_escaped) + _itk_sbom_json_escape("${_pkg_SPDX_LICENSE}" _pkg_LICENSE_escaped) + _itk_sbom_json_escape("${_pkg_COPYRIGHT}" _pkg_COPYRIGHT_escaped) + set(_entry "") string(APPEND _entry " {\n") string(APPEND _entry " \"SPDXID\": \"SPDXRef-${_spdx_id}\",\n") - string(APPEND _entry " \"name\": \"${_pkg_NAME}\",\n") - string(APPEND _entry " \"versionInfo\": \"${_pkg_VERSION}\",\n") + string(APPEND _entry " \"name\": \"${_pkg_NAME_escaped}\",\n") + string(APPEND _entry " \"versionInfo\": \"${_pkg_VERSION_escaped}\",\n") + string( + APPEND + _entry + " \"downloadLocation\": \"${_pkg_DOWNLOAD_escaped}\",\n" + ) + string(APPEND _entry " \"supplier\": \"${_pkg_SUPPLIER_escaped}\",\n") + string( + APPEND + _entry + " \"licenseConcluded\": \"${_pkg_LICENSE_escaped}\",\n" + ) string( APPEND _entry - " \"downloadLocation\": \"${_pkg_DOWNLOAD_LOCATION}\",\n" + " \"licenseDeclared\": \"${_pkg_LICENSE_escaped}\",\n" ) - string(APPEND _entry " \"supplier\": \"${_pkg_SUPPLIER}\",\n") string( APPEND _entry - " \"licenseConcluded\": \"${_pkg_SPDX_LICENSE}\",\n" + " \"copyrightText\": \"${_pkg_COPYRIGHT_escaped}\",\n" ) - string(APPEND _entry " \"licenseDeclared\": \"${_pkg_SPDX_LICENSE}\",\n") - string(APPEND _entry " \"copyrightText\": \"${_pkg_COPYRIGHT}\",\n") string(APPEND _entry " \"filesAnalyzed\": false\n") string(APPEND _entry " }") @@ -174,7 +197,11 @@ function(itk_generate_sbom) string(APPEND _json " \"Tool: CMake-${CMAKE_VERSION}\",\n") string(APPEND _json " \"Organization: NumFOCUS\"\n") string(APPEND _json " ],\n") - string(APPEND _json " \"licenseListVersion\": \"3.22\"\n") + string( + APPEND + _json + " \"licenseListVersion\": \"${ITK_SBOM_SPDX_LICENSE_LIST_VERSION}\"\n" + ) string(APPEND _json " },\n") # --- packages array --- @@ -238,7 +265,12 @@ function(itk_generate_sbom) set(_pkg_copyright "NOASSERTION") endif() - # Get description from module declaration and escape for JSON + # Escape all user-supplied fields for JSON safety + _itk_sbom_json_escape("${_pkg_version}" _pkg_version) + _itk_sbom_json_escape("${_pkg_download}" _pkg_download) + _itk_sbom_json_escape("${_pkg_license}" _pkg_license) + _itk_sbom_json_escape("${_pkg_copyright}" _pkg_copyright) + set(_pkg_description "${ITK_MODULE_${_mod}_DESCRIPTION}") if(_pkg_description) _itk_sbom_json_escape("${_pkg_description}" _pkg_description) @@ -314,7 +346,7 @@ function(itk_generate_sbom) # Append extra packages registered by remote modules get_property(_extra_packages GLOBAL PROPERTY ITK_SBOM_EXTRA_PACKAGES) - foreach(_extra_pkg ${_extra_packages}) + foreach(_extra_pkg IN LISTS _extra_packages) string(APPEND _json ",\n${_extra_pkg}") endforeach() @@ -342,7 +374,7 @@ function(itk_generate_sbom) # Extra packages registered by remote modules get_property(_extra_spdx_ids GLOBAL PROPERTY ITK_SBOM_EXTRA_SPDX_IDS) - foreach(_spdx_id ${_extra_spdx_ids}) + foreach(_spdx_id IN LISTS _extra_spdx_ids) string(APPEND _json ",\n") string(APPEND _json " {\n") string(APPEND _json " \"spdxElementId\": \"SPDXRef-ITK\",\n") diff --git a/CMake/ITKSBOMValidation.cmake b/CMake/ITKSBOMValidation.cmake index 138a54ff41e..2787c9462cf 100644 --- a/CMake/ITKSBOMValidation.cmake +++ b/CMake/ITKSBOMValidation.cmake @@ -18,6 +18,11 @@ if(NOT EXISTS "${_sbom_file}") return() endif() +if(NOT Python3_EXECUTABLE) + message(WARNING "Python3 not found; skipping SBOM validation tests.") + return() +endif() + add_test( NAME ITKSBOMValidation COMMAND diff --git a/Modules/ThirdParty/PNG/itk-module.cmake b/Modules/ThirdParty/PNG/itk-module.cmake index 0e2c276c9f3..65085256156 100644 --- a/Modules/ThirdParty/PNG/itk-module.cmake +++ b/Modules/ThirdParty/PNG/itk-module.cmake @@ -11,7 +11,7 @@ itk_module( ITKZLIB DESCRIPTION "${DOCUMENTATION}" SPDX_LICENSE - "Libpng-2.0" + "Libpng" SPDX_VERSION "1.6.54" SPDX_DOWNLOAD_LOCATION