1# Copyright (c) 2021 The Linux Foundation 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import os 6import uuid 7 8from west.commands import WestCommand 9 10from zspdx.sbom import SBOMConfig, makeSPDX, setupCmakeQuery 11 12SPDX_DESCRIPTION = """\ 13This command creates an SPDX 2.2 tag-value bill of materials 14following the completion of a Zephyr build. 15 16Prior to the build, an empty file must be created at 17BUILDDIR/.cmake/api/v1/query/codemodel-v2 in order to enable 18the CMake file-based API, which the SPDX command relies upon. 19This can be done by calling `west spdx --init` prior to 20calling `west build`.""" 21 22class ZephyrSpdx(WestCommand): 23 def __init__(self): 24 super().__init__( 25 'spdx', 26 'create SPDX bill of materials', 27 SPDX_DESCRIPTION) 28 29 def do_add_parser(self, parser_adder): 30 parser = parser_adder.add_parser(self.name, 31 help=self.help, 32 description = self.description) 33 34 # If you update these options, make sure to keep the docs in 35 # doc/guides/west/zephyr-cmds.rst up to date. 36 parser.add_argument('-i', '--init', action="store_true", 37 help="initialize CMake file-based API") 38 parser.add_argument('-d', '--build-dir', 39 help="build directory") 40 parser.add_argument('-n', '--namespace-prefix', 41 help="namespace prefix") 42 parser.add_argument('-s', '--spdx-dir', 43 help="SPDX output directory") 44 parser.add_argument('--analyze-includes', action="store_true", 45 help="also analyze included header files") 46 parser.add_argument('--include-sdk', action="store_true", 47 help="also generate SPDX document for SDK") 48 49 return parser 50 51 def do_run(self, args, unknown_args): 52 self.dbg(f"running zephyr SPDX generator") 53 54 self.dbg(f" --init is", args.init) 55 self.dbg(f" --build-dir is", args.build_dir) 56 self.dbg(f" --namespace-prefix is", args.namespace_prefix) 57 self.dbg(f" --spdx-dir is", args.spdx_dir) 58 self.dbg(f" --analyze-includes is", args.analyze_includes) 59 self.dbg(f" --include-sdk is", args.include_sdk) 60 61 if args.init: 62 self.do_run_init(args) 63 else: 64 self.do_run_spdx(args) 65 66 def do_run_init(self, args): 67 self.inf("initializing CMake file-based API prior to build") 68 69 if not args.build_dir: 70 self.die("Build directory not specified; call `west spdx --init --build-dir=BUILD_DIR`") 71 72 # initialize CMake file-based API - empty query file 73 query_ready = setupCmakeQuery(args.build_dir) 74 if query_ready: 75 self.inf("initialized; run `west build` then run `west spdx`") 76 else: 77 self.err("Couldn't create CMake file-based API query directory") 78 self.err("You can manually create an empty file at $BUILDDIR/.cmake/api/v1/query/codemodel-v2") 79 80 def do_run_spdx(self, args): 81 if not args.build_dir: 82 self.die("Build directory not specified; call `west spdx --build-dir=BUILD_DIR`") 83 84 # create the SPDX files 85 cfg = SBOMConfig() 86 cfg.buildDir = args.build_dir 87 if args.namespace_prefix: 88 cfg.namespacePrefix = args.namespace_prefix 89 else: 90 # create default namespace according to SPDX spec 91 # note that this is intentionally _not_ an actual URL where 92 # this document will be stored 93 cfg.namespacePrefix = f"http://spdx.org/spdxdocs/zephyr-{str(uuid.uuid4())}" 94 if args.spdx_dir: 95 cfg.spdxDir = args.spdx_dir 96 else: 97 cfg.spdxDir = os.path.join(args.build_dir, "spdx") 98 if args.analyze_includes: 99 cfg.analyzeIncludes = True 100 if args.include_sdk: 101 cfg.includeSDK = True 102 103 # make sure SPDX directory exists, or create it if it doesn't 104 if os.path.exists(cfg.spdxDir): 105 if not os.path.isdir(cfg.spdxDir): 106 self.err(f'SPDX output directory {cfg.spdxDir} exists but is not a directory') 107 return 108 # directory exists, we're good 109 else: 110 # create the directory 111 os.makedirs(cfg.spdxDir, exist_ok=False) 112 113 makeSPDX(cfg) 114