projects/linux·blob assoc-array-fixes · refs

135 lines · 5.6 KiB

1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0-only OR MIT
3# Copyright (C) 2025 TNG Technology Consulting GmbH
4
5"""
6Compute software bill of materials in SPDX format describing a kernel build.
7"""
8
9import json
10import logging
11import os
12import sys
13import time
14import uuid
15import sbom.sbom_logging as sbom_logging
16from sbom.config import get_config
17from sbom.path_utils import is_relative_to
18from sbom.spdx import JsonLdSpdxDocument, SpdxIdGenerator
19from sbom.spdx.core import CreationInfo, SpdxDocument
20from sbom.spdx_graph import SpdxIdGeneratorCollection, build_spdx_graphs
21from sbom.cmd_graph import CmdGraph
22
23
24def _exit_with_summary(write_output_on_error: bool = False) -> None:
25 warning_summary = sbom_logging.summarize_warnings()
26 error_summary = sbom_logging.summarize_errors()
27 if warning_summary:
28 logging.warning(warning_summary)
29 if error_summary:
30 logging.error(error_summary)
31 if not write_output_on_error:
32 logging.info(
33 "Use --write-output-on-error to generate output documents even when errors occur. "
34 "Note that in this case the generated documents may be incomplete."
35 )
36 sys.exit(1)
37
38
39def main():
40 # Read config
41 config = get_config()
42
43 # Configure logging
44 logging.basicConfig(
45 level=logging.DEBUG if config.debug else logging.INFO,
46 format="[%(levelname)s] %(message)s",
47 )
48
49 # Build cmd graph
50 logging.debug("Start building cmd graph")
51 start_time = time.time()
52 cmd_graph = CmdGraph.create(config.root_paths, config)
53 logging.debug(f"Built cmd graph in {time.time() - start_time} seconds")
54
55 # Save used files document
56 if config.generate_used_files:
57 if config.src_tree == config.obj_tree:
58 logging.info(
59 f"Extracting all files from the cmd graph to {config.used_files_file_name} "
60 "instead of only source files because source files cannot be "
61 "reliably classified when the source and object trees are identical.",
62 )
63 used_files = [os.path.relpath(node.absolute_path, config.src_tree) for node in cmd_graph]
64 logging.debug(f"Found {len(used_files)} files in cmd graph.")
65 else:
66 used_files = [
67 os.path.relpath(node.absolute_path, config.src_tree)
68 for node in cmd_graph
69 if is_relative_to(node.absolute_path, config.src_tree)
70 and not is_relative_to(node.absolute_path, config.obj_tree)
71 ]
72 logging.debug(f"Found {len(used_files)} source files in cmd graph")
73 if not sbom_logging.has_errors() or config.write_output_on_error:
74 used_files_path = os.path.join(config.output_directory, config.used_files_file_name)
75 with open(used_files_path, "w", encoding="utf-8") as f:
76 f.write("\n".join(str(file_path) for file_path in used_files))
77 logging.debug(f"Successfully saved {used_files_path}")
78
79 if config.generate_spdx is False:
80 _exit_with_summary(config.write_output_on_error)
81 return
82
83 # Build SPDX Documents
84 logging.debug("Start generating SPDX graph based on cmd graph")
85 start_time = time.time()
86
87 # The real uuid will be generated based on the content of the SPDX graphs
88 # to ensure that the same SPDX document is always assigned the same uuid.
89 PLACEHOLDER_UUID = "00000000-0000-0000-0000-000000000000"
90 spdx_id_base_namespace = f"{config.spdxId_prefix}{PLACEHOLDER_UUID}/"
91 spdx_id_generators = SpdxIdGeneratorCollection(
92 base=SpdxIdGenerator(prefix="p", namespace=spdx_id_base_namespace),
93 source=SpdxIdGenerator(prefix="s", namespace=f"{spdx_id_base_namespace}source/"),
94 build=SpdxIdGenerator(prefix="b", namespace=f"{spdx_id_base_namespace}build/"),
95 output=SpdxIdGenerator(prefix="o", namespace=f"{spdx_id_base_namespace}output/"),
96 )
97
98 spdx_graphs = build_spdx_graphs(
99 cmd_graph,
100 spdx_id_generators,
101 config,
102 )
103 spdx_id_uuid = uuid.uuid5(
104 uuid.NAMESPACE_URL,
105 "".join(
106 json.dumps(element.to_dict()) for spdx_graph in spdx_graphs.values() for element in spdx_graph.to_list()
107 ),
108 )
109 logging.debug(f"Generated SPDX graph in {time.time() - start_time} seconds")
110
111 if not sbom_logging.has_errors() or config.write_output_on_error:
112 for kernel_sbom_kind, spdx_graph in spdx_graphs.items():
113 spdx_graph_objects = spdx_graph.to_list()
114 # Add warning and error summary to creation info comment
115 creation_info = next(element for element in spdx_graph_objects if isinstance(element, CreationInfo))
116 creation_info.comment = "\n".join([
117 sbom_logging.summarize_warnings(),
118 sbom_logging.summarize_errors(),
119 ]).strip()
120 # Replace Placeholder uuid with real uuid for spdxIds
121 spdx_document = next(element for element in spdx_graph_objects if isinstance(element, SpdxDocument))
122 for namespaceMap in spdx_document.namespaceMap:
123 namespaceMap.namespace = namespaceMap.namespace.replace(PLACEHOLDER_UUID, str(spdx_id_uuid))
124 # Serialize SPDX graph to JSON-LD
125 spdx_doc = JsonLdSpdxDocument(graph=spdx_graph_objects)
126 save_path = os.path.join(config.output_directory, config.spdx_file_names[kernel_sbom_kind])
127 spdx_doc.save(save_path, config.prettify_json)
128 logging.debug(f"Successfully saved {save_path}")
129
130 _exit_with_summary(config.write_output_on_error)
131
132
133# Call main method
134if __name__ == "__main__":
135 main()