xref: /linux/scripts/sbom/sbom.py (revision 9c16c1ea466d6c58b82c5d91353c3c6747c059bc)
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 logging
10import os
11import sys
12import time
13import sbom.sbom_logging as sbom_logging
14from sbom.config import get_config
15from sbom.path_utils import is_relative_to
16from sbom.cmd_graph import CmdGraph
17
18
19def _exit_with_summary(write_output_on_error: bool = False) -> None:
20    warning_summary = sbom_logging.summarize_warnings()
21    error_summary = sbom_logging.summarize_errors()
22    if warning_summary:
23        logging.warning(warning_summary)
24    if error_summary:
25        logging.error(error_summary)
26        if not write_output_on_error:
27            logging.info(
28                "Use --write-output-on-error to generate output documents even when errors occur. "
29                "Note that in this case the generated documents may be incomplete."
30            )
31        sys.exit(1)
32
33
34def main():
35    # Read config
36    config = get_config()
37
38    # Configure logging
39    logging.basicConfig(
40        level=logging.DEBUG if config.debug else logging.INFO,
41        format="[%(levelname)s] %(message)s",
42    )
43
44    # Build cmd graph
45    logging.debug("Start building cmd graph")
46    start_time = time.time()
47    cmd_graph = CmdGraph.create(config.root_paths, config)
48    logging.debug(f"Built cmd graph in {time.time() - start_time} seconds")
49
50    # Save used files document
51    if config.generate_used_files:
52        if config.src_tree == config.obj_tree:
53            logging.info(
54                f"Extracting all files from the cmd graph to {config.used_files_file_name} "
55                "instead of only source files because source files cannot be "
56                "reliably classified when the source and object trees are identical.",
57            )
58            used_files = [os.path.relpath(node.absolute_path, config.src_tree) for node in cmd_graph]
59            logging.debug(f"Found {len(used_files)} files in cmd graph.")
60        else:
61            used_files = [
62                os.path.relpath(node.absolute_path, config.src_tree)
63                for node in cmd_graph
64                if is_relative_to(node.absolute_path, config.src_tree)
65                and not is_relative_to(node.absolute_path, config.obj_tree)
66            ]
67            logging.debug(f"Found {len(used_files)} source files in cmd graph")
68        if not sbom_logging.has_errors() or config.write_output_on_error:
69            used_files_path = os.path.join(config.output_directory, config.used_files_file_name)
70            with open(used_files_path, "w", encoding="utf-8") as f:
71                f.write("\n".join(str(file_path) for file_path in used_files))
72            logging.debug(f"Successfully saved {used_files_path}")
73
74    _exit_with_summary(config.write_output_on_error)
75
76
77# Call main method
78if __name__ == "__main__":
79    main()
80