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