| # Copyright (c) 2020-2021 The Linux Foundation |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import os |
| |
| from west import log |
| from west.util import west_topdir, WestNotFound |
| |
| from zspdx.cmakecache import parseCMakeCacheFile |
| from zspdx.cmakefileapijson import parseReply |
| from zspdx.datatypes import DocumentConfig, Document, File, PackageConfig, Package, RelationshipDataElementType, RelationshipData, Relationship |
| from zspdx.getincludes import getCIncludes |
| import zspdx.spdxids |
| |
| # WalkerConfig contains configuration data for the Walker. |
| class WalkerConfig: |
| def __init__(self): |
| super(WalkerConfig, self).__init__() |
| |
| # prefix for Document namespaces; should not end with "/" |
| self.namespacePrefix = "" |
| |
| # location of build directory |
| self.buildDir = "" |
| |
| # should also analyze for included header files? |
| self.analyzeIncludes = False |
| |
| # should also add an SPDX document for the SDK? |
| self.includeSDK = False |
| |
| # Walker is the main analysis class: it walks through the CMake codemodel, |
| # build files, and corresponding source and SDK files, and gathers the |
| # information needed to build the SPDX data classes. |
| class Walker: |
| # initialize with WalkerConfig |
| def __init__(self, cfg): |
| super(Walker, self).__init__() |
| |
| # configuration - WalkerConfig |
| self.cfg = cfg |
| |
| # the various Documents that we will be building |
| self.docBuild = None |
| self.docZephyr = None |
| self.docApp = None |
| self.docSDK = None |
| |
| # dict of absolute file path => the Document that owns that file |
| self.allFileLinks = {} |
| |
| # queue of pending source Files to create, process and assign |
| self.pendingSources = [] |
| |
| # queue of pending relationships to create, process and assign |
| self.pendingRelationships = [] |
| |
| # parsed CMake codemodel |
| self.cm = None |
| |
| # parsed CMake cache dict, once we have the build path |
| self.cmakeCache = {} |
| |
| # C compiler path from parsed CMake cache |
| self.compilerPath = "" |
| |
| # SDK install path from parsed CMake cache |
| self.sdkPath = "" |
| |
| # primary entry point |
| def makeDocuments(self): |
| # parse CMake cache file and get compiler path |
| log.inf("parsing CMake Cache file") |
| self.getCacheFile() |
| |
| # parse codemodel from Walker cfg's build dir |
| log.inf("parsing CMake Codemodel files") |
| self.cm = self.getCodemodel() |
| if not self.cm: |
| log.err("could not parse codemodel from CMake API reply; bailing") |
| return False |
| |
| # set up Documents |
| log.inf("setting up SPDX documents") |
| retval = self.setupDocuments() |
| if not retval: |
| return False |
| |
| # walk through targets in codemodel to gather information |
| log.inf("walking through targets") |
| self.walkTargets() |
| |
| # walk through pending sources and create corresponding files |
| log.inf("walking through pending sources files") |
| self.walkPendingSources() |
| |
| # walk through pending relationship data and create relationships |
| log.inf("walking through pending relationships") |
| self.walkRelationships() |
| |
| return True |
| |
| # parse cache file and pull out relevant data |
| def getCacheFile(self): |
| cacheFilePath = os.path.join(self.cfg.buildDir, "CMakeCache.txt") |
| self.cmakeCache = parseCMakeCacheFile(cacheFilePath) |
| if self.cmakeCache: |
| self.compilerPath = self.cmakeCache.get("CMAKE_C_COMPILER", "") |
| self.sdkPath = self.cmakeCache.get("ZEPHYR_SDK_INSTALL_DIR", "") |
| |
| # determine path from build dir to CMake file-based API index file, then |
| # parse it and return the Codemodel |
| def getCodemodel(self): |
| log.dbg("getting codemodel from CMake API reply files") |
| |
| # make sure the reply directory exists |
| cmakeReplyDirPath = os.path.join(self.cfg.buildDir, ".cmake", "api", "v1", "reply") |
| if not os.path.exists(cmakeReplyDirPath): |
| log.err(f'cmake api reply directory {cmakeReplyDirPath} does not exist') |
| log.err('was query directory created before cmake build ran?') |
| return None |
| if not os.path.isdir(cmakeReplyDirPath): |
| log.err(f'cmake api reply directory {cmakeReplyDirPath} exists but is not a directory') |
| return None |
| |
| # find file with "index" prefix; there should only be one |
| indexFilePath = "" |
| for f in os.listdir(cmakeReplyDirPath): |
| if f.startswith("index"): |
| indexFilePath = os.path.join(cmakeReplyDirPath, f) |
| break |
| if indexFilePath == "": |
| # didn't find it |
| log.err(f'cmake api reply index file not found in {cmakeReplyDirPath}') |
| return None |
| |
| # parse it |
| return parseReply(indexFilePath) |
| |
| # set up Documents before beginning |
| def setupDocuments(self): |
| log.dbg("setting up placeholder documents") |
| |
| # set up build document |
| cfgBuild = DocumentConfig() |
| cfgBuild.name = "build" |
| cfgBuild.namespace = self.cfg.namespacePrefix + "/build" |
| cfgBuild.docRefID = "DocumentRef-build" |
| self.docBuild = Document(cfgBuild) |
| |
| # we'll create the build packages in walkTargets() |
| |
| # the DESCRIBES relationship for the build document will be |
| # with the zephyr_final package |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.DOCUMENT |
| rd.ownerDocument = self.docBuild |
| rd.otherType = RelationshipDataElementType.TARGETNAME |
| rd.otherTargetName = "zephyr_final" |
| rd.rlnType = "DESCRIBES" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # set up zephyr document |
| cfgZephyr = DocumentConfig() |
| cfgZephyr.name = "zephyr-sources" |
| cfgZephyr.namespace = self.cfg.namespacePrefix + "/zephyr" |
| cfgZephyr.docRefID = "DocumentRef-zephyr" |
| self.docZephyr = Document(cfgZephyr) |
| |
| # also set up zephyr sources package |
| cfgPackageZephyr = PackageConfig() |
| cfgPackageZephyr.name = "zephyr-sources" |
| cfgPackageZephyr.spdxID = "SPDXRef-zephyr-sources" |
| # relativeBaseDir is Zephyr sources topdir |
| try: |
| cfgPackageZephyr.relativeBaseDir = west_topdir(self.cm.paths_source) |
| except WestNotFound: |
| log.err(f"cannot find west_topdir for CMake Codemodel sources path {self.cm.paths_source}; bailing") |
| return False |
| pkgZephyr = Package(cfgPackageZephyr, self.docZephyr) |
| self.docZephyr.pkgs[pkgZephyr.cfg.spdxID] = pkgZephyr |
| |
| # create DESCRIBES relationship data |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.DOCUMENT |
| rd.ownerDocument = self.docZephyr |
| rd.otherType = RelationshipDataElementType.PACKAGEID |
| rd.otherPackageID = cfgPackageZephyr.spdxID |
| rd.rlnType = "DESCRIBES" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # set up app document |
| cfgApp = DocumentConfig() |
| cfgApp.name = "app-sources" |
| cfgApp.namespace = self.cfg.namespacePrefix + "/app" |
| cfgApp.docRefID = "DocumentRef-app" |
| self.docApp = Document(cfgApp) |
| |
| # also set up app sources package |
| cfgPackageApp = PackageConfig() |
| cfgPackageApp.name = "app-sources" |
| cfgPackageApp.spdxID = "SPDXRef-app-sources" |
| # relativeBaseDir is app sources dir |
| cfgPackageApp.relativeBaseDir = self.cm.paths_source |
| pkgApp = Package(cfgPackageApp, self.docApp) |
| self.docApp.pkgs[pkgApp.cfg.spdxID] = pkgApp |
| |
| # create DESCRIBES relationship data |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.DOCUMENT |
| rd.ownerDocument = self.docApp |
| rd.otherType = RelationshipDataElementType.PACKAGEID |
| rd.otherPackageID = cfgPackageApp.spdxID |
| rd.rlnType = "DESCRIBES" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| if self.cfg.includeSDK: |
| # set up SDK document |
| cfgSDK = DocumentConfig() |
| cfgSDK.name = "sdk" |
| cfgSDK.namespace = self.cfg.namespacePrefix + "/sdk" |
| cfgSDK.docRefID = "DocumentRef-sdk" |
| self.docSDK = Document(cfgSDK) |
| |
| # also set up zephyr sdk package |
| cfgPackageSDK = PackageConfig() |
| cfgPackageSDK.name = "sdk" |
| cfgPackageSDK.spdxID = "SPDXRef-sdk" |
| # relativeBaseDir is SDK dir |
| cfgPackageSDK.relativeBaseDir = self.sdkPath |
| pkgSDK = Package(cfgPackageSDK, self.docSDK) |
| self.docSDK.pkgs[pkgSDK.cfg.spdxID] = pkgSDK |
| |
| # create DESCRIBES relationship data |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.DOCUMENT |
| rd.ownerDocument = self.docSDK |
| rd.otherType = RelationshipDataElementType.PACKAGEID |
| rd.otherPackageID = cfgPackageSDK.spdxID |
| rd.rlnType = "DESCRIBES" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| return True |
| |
| # walk through targets and gather information |
| def walkTargets(self): |
| log.dbg("walking targets from codemodel") |
| |
| # assuming just one configuration; consider whether this is incorrect |
| cfgTargets = self.cm.configurations[0].configTargets |
| for cfgTarget in cfgTargets: |
| # build the Package for this target |
| pkg = self.initConfigTargetPackage(cfgTarget) |
| |
| # see whether this target has any build artifacts at all |
| if len(cfgTarget.target.artifacts) > 0: |
| # add its build file |
| bf = self.addBuildFile(cfgTarget, pkg) |
| |
| # get its source files if build file is found |
| if bf: |
| self.collectPendingSourceFiles(cfgTarget, pkg, bf) |
| else: |
| log.dbg(f" - target {cfgTarget.name} has no build artifacts") |
| |
| # get its target dependencies |
| self.collectTargetDependencies(cfgTargets, cfgTarget, pkg) |
| |
| # build a Package in the Build doc for the given ConfigTarget |
| def initConfigTargetPackage(self, cfgTarget): |
| log.dbg(f" - initializing Package for target: {cfgTarget.name}") |
| |
| # create target Package's config |
| cfg = PackageConfig() |
| cfg.name = cfgTarget.name |
| cfg.spdxID = "SPDXRef-" + zspdx.spdxids.convertToSPDXIDSafe(cfgTarget.name) |
| cfg.relativeBaseDir = self.cm.paths_build |
| |
| # build Package |
| pkg = Package(cfg, self.docBuild) |
| |
| # add Package to build Document |
| self.docBuild.pkgs[cfg.spdxID] = pkg |
| return pkg |
| |
| # create a target's build product File and add it to its Package |
| # call with: |
| # 1) ConfigTarget |
| # 2) Package for that target |
| # returns: File |
| def addBuildFile(self, cfgTarget, pkg): |
| # assumes only one artifact in each target |
| artifactPath = os.path.join(pkg.cfg.relativeBaseDir, cfgTarget.target.artifacts[0]) |
| log.dbg(f" - adding File {artifactPath}") |
| log.dbg(f" - relativeBaseDir: {pkg.cfg.relativeBaseDir}") |
| log.dbg(f" - artifacts[0]: {cfgTarget.target.artifacts[0]}") |
| |
| # don't create build File if artifact path points to nonexistent file |
| if not os.path.exists(artifactPath): |
| log.dbg(f" - target {cfgTarget.name} lists build artifact {artifactPath} but file not found after build; skipping") |
| return None |
| |
| # create build File |
| bf = File(self.docBuild, pkg) |
| bf.abspath = artifactPath |
| bf.relpath = cfgTarget.target.artifacts[0] |
| # can use nameOnDisk b/c it is just the filename w/out directory paths |
| bf.spdxID = zspdx.spdxids.getUniqueFileID(cfgTarget.target.nameOnDisk, self.docBuild.timesSeen) |
| # don't fill hashes / licenses / rlns now, we'll do that after walking |
| |
| # add File to Package |
| pkg.files[bf.spdxID] = bf |
| |
| # add file path link to Document and global links |
| self.docBuild.fileLinks[bf.abspath] = bf |
| self.allFileLinks[bf.abspath] = self.docBuild |
| |
| # also set this file as the target package's build product file |
| pkg.targetBuildFile = bf |
| |
| return bf |
| |
| # collect a target's source files, add to pending sources queue, and |
| # create pending relationship data entry |
| # call with: |
| # 1) ConfigTarget |
| # 2) Package for that target |
| # 3) build File for that target |
| def collectPendingSourceFiles(self, cfgTarget, pkg, bf): |
| log.dbg(f" - collecting source files and adding to pending queue") |
| |
| targetIncludesSet = set() |
| |
| # walk through target's sources |
| for src in cfgTarget.target.sources: |
| log.dbg(f" - add pending source file and relationship for {src.path}") |
| # get absolute path if we don't have it |
| srcAbspath = src.path |
| if not os.path.isabs(src.path): |
| srcAbspath = os.path.join(self.cm.paths_source, src.path) |
| |
| # check whether it even exists |
| if not (os.path.exists(srcAbspath) and os.path.isfile(srcAbspath)): |
| log.dbg(f" - {srcAbspath} does not exist but is referenced in sources for target {pkg.cfg.name}; skipping") |
| continue |
| |
| # add it to pending source files queue |
| self.pendingSources.append(srcAbspath) |
| |
| # create relationship data |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.FILENAME |
| rd.ownerFileAbspath = bf.abspath |
| rd.otherType = RelationshipDataElementType.FILENAME |
| rd.otherFileAbspath = srcAbspath |
| rd.rlnType = "GENERATED_FROM" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # collect this source file's includes |
| if self.cfg.analyzeIncludes and self.compilerPath: |
| includes = self.collectIncludes(cfgTarget, pkg, bf, src) |
| for inc in includes: |
| targetIncludesSet.add(inc) |
| |
| # make relationships for the overall included files, |
| # avoiding duplicates for multiple source files including |
| # the same headers |
| targetIncludesList = list(targetIncludesSet) |
| targetIncludesList.sort() |
| for inc in targetIncludesList: |
| # add it to pending source files queue |
| self.pendingSources.append(inc) |
| |
| # create relationship data |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.FILENAME |
| rd.ownerFileAbspath = bf.abspath |
| rd.otherType = RelationshipDataElementType.FILENAME |
| rd.otherFileAbspath = inc |
| rd.rlnType = "GENERATED_FROM" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # collect the include files corresponding to this source file |
| # call with: |
| # 1) ConfigTarget |
| # 2) Package for this target |
| # 3) build File for this target |
| # 4) TargetSource entry for this source file |
| # returns: sorted list of include files for this source file |
| def collectIncludes(self, cfgTarget, pkg, bf, src): |
| # get the right compile group for this source file |
| if len(cfgTarget.target.compileGroups) < (src.compileGroupIndex + 1): |
| log.dbg(f" - {cfgTarget.target.name} has compileGroupIndex {src.compileGroupIndex} but only {len(cfgTarget.target.compileGroups)} found; skipping included files search") |
| return [] |
| cg = cfgTarget.target.compileGroups[src.compileGroupIndex] |
| |
| # currently only doing C includes |
| if cg.language != "C": |
| log.dbg(f" - {cfgTarget.target.name} has compile group language {cg.language} but currently only searching includes for C files; skipping included files search") |
| return [] |
| |
| srcAbspath = src.path |
| if src.path[0] != "/": |
| srcAbspath = os.path.join(self.cm.paths_source, src.path) |
| return getCIncludes(self.compilerPath, srcAbspath, cg) |
| |
| # collect relationships for dependencies of this target Package |
| # call with: |
| # 1) all ConfigTargets from CodeModel |
| # 2) this particular ConfigTarget |
| # 3) Package for this Target |
| def collectTargetDependencies(self, cfgTargets, cfgTarget, pkg): |
| log.dbg(f" - collecting target dependencies for {pkg.cfg.name}") |
| |
| # walk through target's dependencies |
| for dep in cfgTarget.target.dependencies: |
| # extract dep name from its id |
| depFragments = dep.id.split(":") |
| depName = depFragments[0] |
| log.dbg(f" - adding pending relationship for {depName}") |
| |
| # create relationship data between dependency packages |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.TARGETNAME |
| rd.ownerTargetName = pkg.cfg.name |
| rd.otherType = RelationshipDataElementType.TARGETNAME |
| rd.otherTargetName = depName |
| rd.rlnType = "HAS_PREREQUISITE" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # if this is a target with any build artifacts (e.g. non-UTILITY), |
| # also create STATIC_LINK relationship for dependency build files, |
| # together with this Package's own target build file |
| if len(cfgTarget.target.artifacts) == 0: |
| continue |
| |
| # find the filename for the dependency's build product, using the |
| # codemodel (since we might not have created this dependency's |
| # Package or File yet) |
| depAbspath = "" |
| for ct in cfgTargets: |
| if ct.name == depName: |
| # skip utility targets |
| if len(ct.target.artifacts) == 0: |
| continue |
| # all targets use the same relativeBaseDir, so this works |
| # even though pkg is the owner package |
| depAbspath = os.path.join(pkg.cfg.relativeBaseDir, ct.target.artifacts[0]) |
| break |
| if depAbspath == "": |
| continue |
| |
| # create relationship data between build files |
| rd = RelationshipData() |
| rd.ownerType = RelationshipDataElementType.FILENAME |
| rd.ownerFileAbspath = pkg.targetBuildFile.abspath |
| rd.otherType = RelationshipDataElementType.FILENAME |
| rd.otherFileAbspath = depAbspath |
| rd.rlnType = "STATIC_LINK" |
| |
| # add it to pending relationships queue |
| self.pendingRelationships.append(rd) |
| |
| # walk through pending sources and create corresponding files, |
| # assigning them to the appropriate Document and Package |
| def walkPendingSources(self): |
| log.dbg(f"walking pending sources") |
| |
| # only one package in each doc; get it |
| pkgZephyr = list(self.docZephyr.pkgs.values())[0] |
| pkgApp = list(self.docApp.pkgs.values())[0] |
| if self.cfg.includeSDK: |
| pkgSDK = list(self.docSDK.pkgs.values())[0] |
| |
| for srcAbspath in self.pendingSources: |
| # check whether we've already seen it |
| srcDoc = self.allFileLinks.get(srcAbspath, None) |
| srcPkg = None |
| if srcDoc: |
| log.dbg(f" - {srcAbspath}: already seen, assigned to {srcDoc.cfg.name}") |
| continue |
| |
| # not yet assigned; figure out where it goes |
| pkgBuild = self.findBuildPackage(srcAbspath) |
| |
| if pkgBuild: |
| log.dbg(f" - {srcAbspath}: assigning to build document, package {pkgBuild.cfg.name}") |
| srcDoc = self.docBuild |
| srcPkg = pkgBuild |
| elif self.cfg.includeSDK and os.path.commonpath([srcAbspath, pkgSDK.cfg.relativeBaseDir]) == pkgSDK.cfg.relativeBaseDir: |
| log.dbg(f" - {srcAbspath}: assigning to sdk document") |
| srcDoc = self.docSDK |
| srcPkg = pkgSDK |
| elif os.path.commonpath([srcAbspath, pkgApp.cfg.relativeBaseDir]) == pkgApp.cfg.relativeBaseDir: |
| log.dbg(f" - {srcAbspath}: assigning to app document") |
| srcDoc = self.docApp |
| srcPkg = pkgApp |
| elif os.path.commonpath([srcAbspath, pkgZephyr.cfg.relativeBaseDir]) == pkgZephyr.cfg.relativeBaseDir: |
| log.dbg(f" - {srcAbspath}: assigning to zephyr document") |
| srcDoc = self.docZephyr |
| srcPkg = pkgZephyr |
| else: |
| log.dbg(f" - {srcAbspath}: can't determine which document should own; skipping") |
| continue |
| |
| # create File and assign it to the Package and Document |
| sf = File(srcDoc, srcPkg) |
| sf.abspath = srcAbspath |
| sf.relpath = os.path.relpath(srcAbspath, srcPkg.cfg.relativeBaseDir) |
| filenameOnly = os.path.split(srcAbspath)[1] |
| sf.spdxID = zspdx.spdxids.getUniqueFileID(filenameOnly, srcDoc.timesSeen) |
| # don't fill hashes / licenses / rlns now, we'll do that after walking |
| |
| # add File to Package |
| srcPkg.files[sf.spdxID] = sf |
| |
| # add file path link to Document and global links |
| srcDoc.fileLinks[sf.abspath] = sf |
| self.allFileLinks[sf.abspath] = srcDoc |
| |
| # figure out which build Package contains the given file, if any |
| # call with: |
| # 1) absolute path for source filename being searched |
| def findBuildPackage(self, srcAbspath): |
| # Multiple target Packages might "contain" the file path, if they |
| # are nested. If so, the one with the longest path would be the |
| # most deeply-nested target directory, so that's the one which |
| # should get the file path. |
| pkgLongestMatch = None |
| for pkg in self.docBuild.pkgs.values(): |
| if os.path.commonpath([srcAbspath, pkg.cfg.relativeBaseDir]) == pkg.cfg.relativeBaseDir: |
| # the package does contain this file; is it the deepest? |
| if pkgLongestMatch: |
| if len(pkg.cfg.relativeBaseDir) > len(pkgLongestMatch.cfg.relativeBaseDir): |
| pkgLongestMatch = pkg |
| else: |
| # first package containing it, so assign it |
| pkgLongestMatch = pkg |
| |
| return pkgLongestMatch |
| |
| # walk through pending RelationshipData entries, create corresponding |
| # Relationships, and assign them to the applicable Files / Packages |
| def walkRelationships(self): |
| for rlnData in self.pendingRelationships: |
| rln = Relationship() |
| # get left side of relationship data |
| docA, spdxIDA, rlnsA = self.getRelationshipLeft(rlnData) |
| if not docA or not spdxIDA: |
| continue |
| rln.refA = spdxIDA |
| # get right side of relationship data |
| spdxIDB = self.getRelationshipRight(rlnData, docA) |
| if not spdxIDB: |
| continue |
| rln.refB = spdxIDB |
| rln.rlnType = rlnData.rlnType |
| rlnsA.append(rln) |
| log.dbg(f" - adding relationship to {docA.cfg.name}: {rln.refA} {rln.rlnType} {rln.refB}") |
| |
| # get owner (left side) document and SPDX ID of Relationship for given RelationshipData |
| # returns: doc, spdxID, rlnsArray (for either Document, Package, or File, as applicable) |
| def getRelationshipLeft(self, rlnData): |
| if rlnData.ownerType == RelationshipDataElementType.FILENAME: |
| # find the document for this file abspath, and then the specific file's ID |
| ownerDoc = self.allFileLinks.get(rlnData.ownerFileAbspath, None) |
| if not ownerDoc: |
| log.dbg(f" - searching for relationship, can't find document with file {rlnData.ownerFileAbspath}; skipping") |
| return None, None, None |
| sf = ownerDoc.fileLinks.get(rlnData.ownerFileAbspath, None) |
| if not sf: |
| log.dbg(f" - searching for relationship for file {rlnData.ownerFileAbspath} points to document {ownerDoc.cfg.name} but file not found; skipping") |
| return None, None, None |
| # found it |
| if not sf.spdxID: |
| log.dbg(f" - searching for relationship for file {rlnData.ownerFileAbspath} found file, but empty ID; skipping") |
| return None, None, None |
| return ownerDoc, sf.spdxID, sf.rlns |
| elif rlnData.ownerType == RelationshipDataElementType.TARGETNAME: |
| # find the document for this target name, and then the specific package's ID |
| # for target names, must be docBuild |
| ownerDoc = self.docBuild |
| # walk through target Packages and check names |
| for pkg in ownerDoc.pkgs.values(): |
| if pkg.cfg.name == rlnData.ownerTargetName: |
| if not pkg.cfg.spdxID: |
| log.dbg(f" - searching for relationship for target {rlnData.ownerTargetName} found package, but empty ID; skipping") |
| return None, None, None |
| return ownerDoc, pkg.cfg.spdxID, pkg.rlns |
| log.dbg(f" - searching for relationship for target {rlnData.ownerTargetName}, target not found in build document; skipping") |
| return None, None, None |
| elif rlnData.ownerType == RelationshipDataElementType.DOCUMENT: |
| # will always be SPDXRef-DOCUMENT |
| return rlnData.ownerDocument, "SPDXRef-DOCUMENT", rlnData.ownerDocument.relationships |
| else: |
| log.dbg(f" - unknown relationship type {rlnData.ownerType}; skipping") |
| return None, None, None |
| |
| # get other (right side) SPDX ID of Relationship for given RelationshipData |
| def getRelationshipRight(self, rlnData, docA): |
| if rlnData.otherType == RelationshipDataElementType.FILENAME: |
| # find the document for this file abspath, and then the specific file's ID |
| otherDoc = self.allFileLinks.get(rlnData.otherFileAbspath, None) |
| if not otherDoc: |
| log.dbg(f" - searching for relationship, can't find document with file {rlnData.otherFileAbspath}; skipping") |
| return None |
| bf = otherDoc.fileLinks.get(rlnData.otherFileAbspath, None) |
| if not bf: |
| log.dbg(f" - searching for relationship for file {rlnData.otherFileAbspath} points to document {otherDoc.cfg.name} but file not found; skipping") |
| return None |
| # found it |
| if not bf.spdxID: |
| log.dbg(f" - searching for relationship for file {rlnData.otherFileAbspath} found file, but empty ID; skipping") |
| return None |
| # figure out whether to append DocumentRef |
| spdxIDB = bf.spdxID |
| if otherDoc != docA: |
| spdxIDB = otherDoc.cfg.docRefID + ":" + spdxIDB |
| docA.externalDocuments.add(otherDoc) |
| return spdxIDB |
| elif rlnData.otherType == RelationshipDataElementType.TARGETNAME: |
| # find the document for this target name, and then the specific package's ID |
| # for target names, must be docBuild |
| otherDoc = self.docBuild |
| # walk through target Packages and check names |
| for pkg in otherDoc.pkgs.values(): |
| if pkg.cfg.name == rlnData.otherTargetName: |
| if not pkg.cfg.spdxID: |
| log.dbg(f" - searching for relationship for target {rlnData.otherTargetName} found package, but empty ID; skipping") |
| return None |
| spdxIDB = pkg.cfg.spdxID |
| if otherDoc != docA: |
| spdxIDB = otherDoc.cfg.docRefID + ":" + spdxIDB |
| docA.externalDocuments.add(otherDoc) |
| return spdxIDB |
| log.dbg(f" - searching for relationship for target {rlnData.otherTargetName}, target not found in build document; skipping") |
| return None |
| elif rlnData.otherType == RelationshipDataElementType.PACKAGEID: |
| # will just be the package ID that was passed in |
| return rlnData.otherPackageID |
| else: |
| log.dbg(f" - unknown relationship type {rlnData.otherType}; skipping") |
| return None |