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 9from zspdx.sbom import SBOMConfig, makeSPDX, setupCmakeQuery 10from zspdx.version import SPDX_VERSION_2_3, SUPPORTED_SPDX_VERSIONS, parse 11 12SPDX_DESCRIPTION = """\ 13This command creates an SPDX 2.2 or 2.3 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('--spdx-version', choices=[str(v) for v in SUPPORTED_SPDX_VERSIONS], 45 default=str(SPDX_VERSION_2_3), 46 help="SPDX specification version to use (default: 2.3)") 47 parser.add_argument('--analyze-includes', action="store_true", 48 help="also analyze included header files") 49 parser.add_argument('--include-sdk', action="store_true", 50 help="also generate SPDX document for SDK") 51 52 return parser 53 54 def do_run(self, args, unknown_args): 55 self.dbg("running zephyr SPDX generator") 56 57 self.dbg(" --init is", args.init) 58 self.dbg(" --build-dir is", args.build_dir) 59 self.dbg(" --namespace-prefix is", args.namespace_prefix) 60 self.dbg(" --spdx-dir is", args.spdx_dir) 61 self.dbg(" --spdx-version is", args.spdx_version) 62 self.dbg(" --analyze-includes is", args.analyze_includes) 63 self.dbg(" --include-sdk is", args.include_sdk) 64 65 if args.init: 66 self.do_run_init(args) 67 else: 68 self.do_run_spdx(args) 69 70 def do_run_init(self, args): 71 self.inf("initializing CMake file-based API prior to build") 72 73 if not args.build_dir: 74 self.die("Build directory not specified; call `west spdx --init --build-dir=BUILD_DIR`") 75 76 # initialize CMake file-based API - empty query file 77 query_ready = setupCmakeQuery(args.build_dir) 78 if query_ready: 79 self.inf("initialized; run `west build` then run `west spdx`") 80 else: 81 self.die("Couldn't create CMake file-based API query directory\n" 82 "You can manually create an empty file at " 83 "$BUILDDIR/.cmake/api/v1/query/codemodel-v2") 84 85 def do_run_spdx(self, args): 86 if not args.build_dir: 87 self.die("Build directory not specified; call `west spdx --build-dir=BUILD_DIR`") 88 89 # create the SPDX files 90 cfg = SBOMConfig() 91 cfg.buildDir = args.build_dir 92 try: 93 version_obj = parse(args.spdx_version) 94 except Exception: 95 self.die(f"Invalid SPDX version: {args.spdx_version}") 96 cfg.spdxVersion = version_obj 97 if args.namespace_prefix: 98 cfg.namespacePrefix = args.namespace_prefix 99 else: 100 # create default namespace according to SPDX spec 101 # note that this is intentionally _not_ an actual URL where 102 # this document will be stored 103 cfg.namespacePrefix = f"http://spdx.org/spdxdocs/zephyr-{str(uuid.uuid4())}" 104 if args.spdx_dir: 105 cfg.spdxDir = args.spdx_dir 106 else: 107 cfg.spdxDir = os.path.join(args.build_dir, "spdx") 108 if args.analyze_includes: 109 cfg.analyzeIncludes = True 110 if args.include_sdk: 111 cfg.includeSDK = True 112 113 # make sure SPDX directory exists, or create it if it doesn't 114 if os.path.exists(cfg.spdxDir): 115 if not os.path.isdir(cfg.spdxDir): 116 self.err(f'SPDX output directory {cfg.spdxDir} exists but is not a directory') 117 return 118 # directory exists, we're good 119 else: 120 # create the directory 121 os.makedirs(cfg.spdxDir, exist_ok=False) 122 123 if not makeSPDX(cfg): 124 self.die("Failed to create SPDX output") 125