135 lines · 5.6 KB · Python
RawDownload
#!/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 jsonimport loggingimport osimport sysimport timeimport uuidimport sbom.sbom_logging as sbom_loggingfrom sbom.config import get_configfrom sbom.path_utils import is_relative_tofrom sbom.spdx import JsonLdSpdxDocument, SpdxIdGeneratorfrom sbom.spdx.core import CreationInfo, SpdxDocumentfrom sbom.spdx_graph import SpdxIdGeneratorCollection, build_spdx_graphsfrom 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 methodif __name__ == "__main__":    main()