blob: 2a1a3cd2732bf2390bbc8bc340d5cf208da101b6 [file]
# Copyright (c) 2021 The Linux Foundation
#
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import sys
import uuid
from west.commands import WestCommand
from build_helpers import forward_logging_to_west
script_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, os.path.join(script_dir, "pylib/"))
from zspdx.sbom import SBOMConfig, make_spdx, setup_cmake_query # noqa: E402
from zspdx.version import ( # noqa: E402
SPDX_VERSION_2_3,
SPDX_VERSION_3_1,
SUPPORTED_SPDX_VERSIONS,
parse,
)
SPDX_DESCRIPTION = """\
This command creates an SPDX 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', '', description=SPDX_DESCRIPTION)
def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(self.name, 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(
'--spdx-version',
choices=[str(v) for v in SUPPORTED_SPDX_VERSIONS],
default=str(SPDX_VERSION_2_3),
help="SPDX specification version to use (default: 2.3)",
)
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):
# Forward debug output from the zspdx package so module-level
# logging is visible under "west -v" / "west -vv".
forward_logging_to_west(self, 'zspdx')
logging.getLogger('zspdx').propagate = False
self.dbg("running zephyr SPDX generator")
self.dbg(" --init is", args.init)
self.dbg(" --build-dir is", args.build_dir)
self.dbg(" --namespace-prefix is", args.namespace_prefix)
self.dbg(" --spdx-dir is", args.spdx_dir)
self.dbg(" --spdx-version is", args.spdx_version)
self.dbg(" --analyze-includes is", args.analyze_includes)
self.dbg(" --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 = setup_cmake_query(args.build_dir)
if query_ready:
self.inf("initialized; run `west build` then run `west spdx`")
else:
self.die(
"Couldn't create CMake file-based API query directory\n"
"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.build_dir = args.build_dir
try:
version_obj = parse(args.spdx_version)
except Exception:
self.die(f"Invalid SPDX version: {args.spdx_version}")
cfg.spdx_version = version_obj
if version_obj == SPDX_VERSION_3_1:
self.wrn("SPDX 3.1 support is experimental; the 3.1 spec is still in development.")
if args.namespace_prefix:
cfg.namespace_prefix = 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.namespace_prefix = f"http://spdx.org/spdxdocs/zephyr-{str(uuid.uuid4())}"
if args.spdx_dir:
cfg.spdx_dir = args.spdx_dir
else:
cfg.spdx_dir = os.path.join(args.build_dir, "spdx")
if args.analyze_includes:
cfg.analyze_includes = True
if args.include_sdk:
cfg.include_sdk = True
# make sure SPDX directory exists, or create it if it doesn't
if os.path.exists(cfg.spdx_dir):
if not os.path.isdir(cfg.spdx_dir):
self.err(f'SPDX output directory {cfg.spdx_dir} exists but is not a directory')
return
# directory exists, we're good
else:
# create the directory
os.makedirs(cfg.spdx_dir, exist_ok=False)
if not make_spdx(cfg):
self.die("Failed to create SPDX output")