1# Copyright (c) 2020, 2021 The Linux Foundation
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import os
6from dataclasses import dataclass
7
8from west import log
9
10from zspdx.scanner import ScannerConfig, scanDocument
11from zspdx.version import SPDX_VERSION_2_3
12from zspdx.walker import Walker, WalkerConfig
13from zspdx.writer import writeSPDX
14
15
16# SBOMConfig contains settings that will be passed along to the various
17# SBOM maker subcomponents.
18@dataclass(eq=True)
19class SBOMConfig:
20    # prefix for Document namespaces; should not end with "/"
21    namespacePrefix: str = ""
22
23    # location of build directory
24    buildDir: str = ""
25
26    # location of SPDX document output directory
27    spdxDir: str = ""
28
29    # SPDX specification version to use
30    spdxVersion: str = SPDX_VERSION_2_3
31
32    # should also analyze for included header files?
33    analyzeIncludes: bool = False
34
35    # should also add an SPDX document for the SDK?
36    includeSDK: bool = False
37
38
39# create Cmake file-based API directories and query file
40# Arguments:
41#   1) build_dir: build directory
42def setupCmakeQuery(build_dir):
43    # check that query dir exists as a directory, or else create it
44    cmakeApiDirPath = os.path.join(build_dir, ".cmake", "api", "v1", "query")
45    if os.path.exists(cmakeApiDirPath):
46        if not os.path.isdir(cmakeApiDirPath):
47            log.err(f'cmake api query directory {cmakeApiDirPath} exists and is not a directory')
48            return False
49        # directory exists, we're good
50    else:
51        # create the directory
52        os.makedirs(cmakeApiDirPath, exist_ok=False)
53
54    # check that codemodel-v2 exists as a file, or else create it
55    queryFilePath = os.path.join(cmakeApiDirPath, "codemodel-v2")
56    if os.path.exists(queryFilePath):
57        if not os.path.isfile(queryFilePath):
58            log.err(f'cmake api query file {queryFilePath} exists and is not a directory')
59            return False
60        # file exists, we're good
61        return True
62    else:
63        # file doesn't exist, let's create an empty file
64        with open(queryFilePath, "w"):
65            pass
66        return True
67
68
69# main entry point for SBOM maker
70# Arguments:
71#   1) cfg: SBOMConfig
72def makeSPDX(cfg):
73    # report any odd configuration settings
74    if cfg.analyzeIncludes and not cfg.includeSDK:
75        log.wrn("config: requested to analyze includes but not to generate SDK SPDX document;")
76        log.wrn("config: will proceed but will discard detected includes for SDK header files")
77
78    # set up walker configuration
79    walkerCfg = WalkerConfig()
80    walkerCfg.namespacePrefix = cfg.namespacePrefix
81    walkerCfg.buildDir = cfg.buildDir
82    walkerCfg.analyzeIncludes = cfg.analyzeIncludes
83    walkerCfg.includeSDK = cfg.includeSDK
84
85    # make and run the walker
86    w = Walker(walkerCfg)
87    retval = w.makeDocuments()
88    if not retval:
89        log.err("SPDX walker failed; bailing")
90        return False
91
92    # set up scanner configuration
93    scannerCfg = ScannerConfig()
94
95    # scan each document from walker
96    if cfg.includeSDK:
97        scanDocument(scannerCfg, w.docSDK)
98    scanDocument(scannerCfg, w.docApp)
99    scanDocument(scannerCfg, w.docZephyr)
100    scanDocument(scannerCfg, w.docBuild)
101
102    # write each document, in this particular order so that the
103    # hashes for external references are calculated
104
105    # write SDK document, if we made one
106    if cfg.includeSDK:
107        retval = writeSPDX(os.path.join(cfg.spdxDir, "sdk.spdx"), w.docSDK, cfg.spdxVersion)
108        if not retval:
109            log.err("SPDX writer failed for SDK document; bailing")
110            return False
111
112    # write app document
113    retval = writeSPDX(os.path.join(cfg.spdxDir, "app.spdx"), w.docApp, cfg.spdxVersion)
114    if not retval:
115        log.err("SPDX writer failed for app document; bailing")
116        return False
117
118    # write zephyr document
119    retval = writeSPDX(os.path.join(cfg.spdxDir, "zephyr.spdx"), w.docZephyr, cfg.spdxVersion)
120    if not retval:
121        log.err("SPDX writer failed for zephyr document; bailing")
122        return False
123
124    # write build document
125    retval = writeSPDX(os.path.join(cfg.spdxDir, "build.spdx"), w.docBuild, cfg.spdxVersion)
126    if not retval:
127        log.err("SPDX writer failed for build document; bailing")
128        return False
129
130    # write modules document
131    retval = writeSPDX(
132        os.path.join(cfg.spdxDir, "modules-deps.spdx"), w.docModulesExtRefs, cfg.spdxVersion
133    )
134    if not retval:
135        log.err("SPDX writer failed for modules-deps document; bailing")
136        return False
137
138    return True
139