# Copyright (c) 2021 The Linux Foundation # # SPDX-License-Identifier: Apache-2.0 import os import uuid from west.commands import WestCommand from zspdx.sbom import SBOMConfig, makeSPDX, setupCmakeQuery SPDX_DESCRIPTION = """\ This command creates an SPDX 2.2 tag-value bill of materials following the completion of a Zephyr build. Prior to the build, an empty file must be created at BUILDDIR/.cmake/api/v1/query/codemodel-v2 in order to enable the CMake file-based API, which the SPDX command relies upon. This can be done by calling `west spdx --init` prior to calling `west build`.""" class ZephyrSpdx(WestCommand): def __init__(self): super().__init__( 'spdx', 'create SPDX bill of materials', SPDX_DESCRIPTION) def do_add_parser(self, parser_adder): parser = parser_adder.add_parser(self.name, help=self.help, description = self.description) # If you update these options, make sure to keep the docs in # doc/guides/west/zephyr-cmds.rst up to date. parser.add_argument('-i', '--init', action="store_true", help="initialize CMake file-based API") parser.add_argument('-d', '--build-dir', help="build directory") parser.add_argument('-n', '--namespace-prefix', help="namespace prefix") parser.add_argument('-s', '--spdx-dir', help="SPDX output directory") parser.add_argument('--analyze-includes', action="store_true", help="also analyze included header files") parser.add_argument('--include-sdk', action="store_true", help="also generate SPDX document for SDK") return parser def do_run(self, args, unknown_args): self.dbg(f"running zephyr SPDX generator") self.dbg(f" --init is", args.init) self.dbg(f" --build-dir is", args.build_dir) self.dbg(f" --namespace-prefix is", args.namespace_prefix) self.dbg(f" --spdx-dir is", args.spdx_dir) self.dbg(f" --analyze-includes is", args.analyze_includes) self.dbg(f" --include-sdk is", args.include_sdk) if args.init: self.do_run_init(args) else: self.do_run_spdx(args) def do_run_init(self, args): self.inf("initializing CMake file-based API prior to build") if not args.build_dir: self.die("Build directory not specified; call `west spdx --init --build-dir=BUILD_DIR`") # initialize CMake file-based API - empty query file query_ready = setupCmakeQuery(args.build_dir) if query_ready: self.inf("initialized; run `west build` then run `west spdx`") else: self.err("Couldn't create CMake file-based API query directory") self.err("You can manually create an empty file at $BUILDDIR/.cmake/api/v1/query/codemodel-v2") def do_run_spdx(self, args): if not args.build_dir: self.die("Build directory not specified; call `west spdx --build-dir=BUILD_DIR`") # create the SPDX files cfg = SBOMConfig() cfg.buildDir = args.build_dir if args.namespace_prefix: cfg.namespacePrefix = args.namespace_prefix else: # create default namespace according to SPDX spec # note that this is intentionally _not_ an actual URL where # this document will be stored cfg.namespacePrefix = f"http://spdx.org/spdxdocs/zephyr-{str(uuid.uuid4())}" if args.spdx_dir: cfg.spdxDir = args.spdx_dir else: cfg.spdxDir = os.path.join(args.build_dir, "spdx") if args.analyze_includes: cfg.analyzeIncludes = True if args.include_sdk: cfg.includeSDK = True # make sure SPDX directory exists, or create it if it doesn't if os.path.exists(cfg.spdxDir): if not os.path.isdir(cfg.spdxDir): self.err(f'SPDX output directory {cfg.spdxDir} exists but is not a directory') return # directory exists, we're good else: # create the directory os.makedirs(cfg.spdxDir, exist_ok=False) makeSPDX(cfg)