mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-17 04:27:46 -04:00
Add infrastructure to serialize an SPDX graph as a JSON-LD document. NamespaceMaps in the SPDX document are converted to custom prefixes in the @context field of the JSON-LD output. The SBOM tool uses NamespaceMaps solely to shorten SPDX IDs, avoiding repetition of full namespace URIs by using short prefixes. Assisted-by: Cursor:claude-sonnet-4-5 Assisted-by: OpenCode:GLM-4-7 Co-developed-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Maximilian Huber <maximilian.huber@tngtech.com> Signed-off-by: Luis Augenstein <luis.augenstein@tngtech.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
136 lines
5.6 KiB
Python
136 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-only OR MIT
|
|
# Copyright (C) 2025 TNG Technology Consulting GmbH
|
|
|
|
"""
|
|
Compute software bill of materials in SPDX format describing a kernel build.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
import uuid
|
|
import sbom.sbom_logging as sbom_logging
|
|
from sbom.config import get_config
|
|
from sbom.path_utils import is_relative_to
|
|
from sbom.spdx import JsonLdSpdxDocument, SpdxIdGenerator
|
|
from sbom.spdx.core import CreationInfo, SpdxDocument
|
|
from sbom.spdx_graph import SpdxIdGeneratorCollection, build_spdx_graphs
|
|
from sbom.cmd_graph import CmdGraph
|
|
|
|
|
|
def _exit_with_summary(write_output_on_error: bool = False) -> None:
|
|
warning_summary = sbom_logging.summarize_warnings()
|
|
error_summary = sbom_logging.summarize_errors()
|
|
if warning_summary:
|
|
logging.warning(warning_summary)
|
|
if error_summary:
|
|
logging.error(error_summary)
|
|
if not write_output_on_error:
|
|
logging.info(
|
|
"Use --write-output-on-error to generate output documents even when errors occur. "
|
|
"Note that in this case the generated documents may be incomplete."
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
# Read config
|
|
config = get_config()
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.DEBUG if config.debug else logging.INFO,
|
|
format="[%(levelname)s] %(message)s",
|
|
)
|
|
|
|
# Build cmd graph
|
|
logging.debug("Start building cmd graph")
|
|
start_time = time.time()
|
|
cmd_graph = CmdGraph.create(config.root_paths, config)
|
|
logging.debug(f"Built cmd graph in {time.time() - start_time} seconds")
|
|
|
|
# Save used files document
|
|
if config.generate_used_files:
|
|
if config.src_tree == config.obj_tree:
|
|
logging.info(
|
|
f"Extracting all files from the cmd graph to {config.used_files_file_name} "
|
|
"instead of only source files because source files cannot be "
|
|
"reliably classified when the source and object trees are identical.",
|
|
)
|
|
used_files = [os.path.relpath(node.absolute_path, config.src_tree) for node in cmd_graph]
|
|
logging.debug(f"Found {len(used_files)} files in cmd graph.")
|
|
else:
|
|
used_files = [
|
|
os.path.relpath(node.absolute_path, config.src_tree)
|
|
for node in cmd_graph
|
|
if is_relative_to(node.absolute_path, config.src_tree)
|
|
and not is_relative_to(node.absolute_path, config.obj_tree)
|
|
]
|
|
logging.debug(f"Found {len(used_files)} source files in cmd graph")
|
|
if not sbom_logging.has_errors() or config.write_output_on_error:
|
|
used_files_path = os.path.join(config.output_directory, config.used_files_file_name)
|
|
with open(used_files_path, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(str(file_path) for file_path in used_files))
|
|
logging.debug(f"Successfully saved {used_files_path}")
|
|
|
|
if config.generate_spdx is False:
|
|
_exit_with_summary(config.write_output_on_error)
|
|
return
|
|
|
|
# Build SPDX Documents
|
|
logging.debug("Start generating SPDX graph based on cmd graph")
|
|
start_time = time.time()
|
|
|
|
# The real uuid will be generated based on the content of the SPDX graphs
|
|
# to ensure that the same SPDX document is always assigned the same uuid.
|
|
PLACEHOLDER_UUID = "00000000-0000-0000-0000-000000000000"
|
|
spdx_id_base_namespace = f"{config.spdxId_prefix}{PLACEHOLDER_UUID}/"
|
|
spdx_id_generators = SpdxIdGeneratorCollection(
|
|
base=SpdxIdGenerator(prefix="p", namespace=spdx_id_base_namespace),
|
|
source=SpdxIdGenerator(prefix="s", namespace=f"{spdx_id_base_namespace}source/"),
|
|
build=SpdxIdGenerator(prefix="b", namespace=f"{spdx_id_base_namespace}build/"),
|
|
output=SpdxIdGenerator(prefix="o", namespace=f"{spdx_id_base_namespace}output/"),
|
|
)
|
|
|
|
spdx_graphs = build_spdx_graphs(
|
|
cmd_graph,
|
|
spdx_id_generators,
|
|
config,
|
|
)
|
|
spdx_id_uuid = uuid.uuid5(
|
|
uuid.NAMESPACE_URL,
|
|
"".join(
|
|
json.dumps(element.to_dict()) for spdx_graph in spdx_graphs.values() for element in spdx_graph.to_list()
|
|
),
|
|
)
|
|
logging.debug(f"Generated SPDX graph in {time.time() - start_time} seconds")
|
|
|
|
if not sbom_logging.has_errors() or config.write_output_on_error:
|
|
for kernel_sbom_kind, spdx_graph in spdx_graphs.items():
|
|
spdx_graph_objects = spdx_graph.to_list()
|
|
# Add warning and error summary to creation info comment
|
|
creation_info = next(element for element in spdx_graph_objects if isinstance(element, CreationInfo))
|
|
creation_info.comment = "\n".join([
|
|
sbom_logging.summarize_warnings(),
|
|
sbom_logging.summarize_errors(),
|
|
]).strip()
|
|
# Replace Placeholder uuid with real uuid for spdxIds
|
|
spdx_document = next(element for element in spdx_graph_objects if isinstance(element, SpdxDocument))
|
|
for namespaceMap in spdx_document.namespaceMap:
|
|
namespaceMap.namespace = namespaceMap.namespace.replace(PLACEHOLDER_UUID, str(spdx_id_uuid))
|
|
# Serialize SPDX graph to JSON-LD
|
|
spdx_doc = JsonLdSpdxDocument(graph=spdx_graph_objects)
|
|
save_path = os.path.join(config.output_directory, config.spdx_file_names[kernel_sbom_kind])
|
|
spdx_doc.save(save_path, config.prettify_json)
|
|
logging.debug(f"Successfully saved {save_path}")
|
|
|
|
_exit_with_summary(config.write_output_on_error)
|
|
|
|
|
|
# Call main method
|
|
if __name__ == "__main__":
|
|
main()
|