walker.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. # Copyright (c) 2020-2021 The Linux Foundation
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. import os
  5. from west import log
  6. from west.util import west_topdir, WestNotFound
  7. from zspdx.cmakecache import parseCMakeCacheFile
  8. from zspdx.cmakefileapijson import parseReply
  9. from zspdx.datatypes import DocumentConfig, Document, File, PackageConfig, Package, RelationshipDataElementType, RelationshipData, Relationship
  10. from zspdx.getincludes import getCIncludes
  11. import zspdx.spdxids
  12. # WalkerConfig contains configuration data for the Walker.
  13. class WalkerConfig:
  14. def __init__(self):
  15. super(WalkerConfig, self).__init__()
  16. # prefix for Document namespaces; should not end with "/"
  17. self.namespacePrefix = ""
  18. # location of build directory
  19. self.buildDir = ""
  20. # should also analyze for included header files?
  21. self.analyzeIncludes = False
  22. # should also add an SPDX document for the SDK?
  23. self.includeSDK = False
  24. # Walker is the main analysis class: it walks through the CMake codemodel,
  25. # build files, and corresponding source and SDK files, and gathers the
  26. # information needed to build the SPDX data classes.
  27. class Walker:
  28. # initialize with WalkerConfig
  29. def __init__(self, cfg):
  30. super(Walker, self).__init__()
  31. # configuration - WalkerConfig
  32. self.cfg = cfg
  33. # the various Documents that we will be building
  34. self.docBuild = None
  35. self.docZephyr = None
  36. self.docApp = None
  37. self.docSDK = None
  38. # dict of absolute file path => the Document that owns that file
  39. self.allFileLinks = {}
  40. # queue of pending source Files to create, process and assign
  41. self.pendingSources = []
  42. # queue of pending relationships to create, process and assign
  43. self.pendingRelationships = []
  44. # parsed CMake codemodel
  45. self.cm = None
  46. # parsed CMake cache dict, once we have the build path
  47. self.cmakeCache = {}
  48. # C compiler path from parsed CMake cache
  49. self.compilerPath = ""
  50. # SDK install path from parsed CMake cache
  51. self.sdkPath = ""
  52. # primary entry point
  53. def makeDocuments(self):
  54. # parse CMake cache file and get compiler path
  55. log.inf("parsing CMake Cache file")
  56. self.getCacheFile()
  57. # parse codemodel from Walker cfg's build dir
  58. log.inf("parsing CMake Codemodel files")
  59. self.cm = self.getCodemodel()
  60. if not self.cm:
  61. log.err("could not parse codemodel from CMake API reply; bailing")
  62. return False
  63. # set up Documents
  64. log.inf("setting up SPDX documents")
  65. retval = self.setupDocuments()
  66. if not retval:
  67. return False
  68. # walk through targets in codemodel to gather information
  69. log.inf("walking through targets")
  70. self.walkTargets()
  71. # walk through pending sources and create corresponding files
  72. log.inf("walking through pending sources files")
  73. self.walkPendingSources()
  74. # walk through pending relationship data and create relationships
  75. log.inf("walking through pending relationships")
  76. self.walkRelationships()
  77. return True
  78. # parse cache file and pull out relevant data
  79. def getCacheFile(self):
  80. cacheFilePath = os.path.join(self.cfg.buildDir, "CMakeCache.txt")
  81. self.cmakeCache = parseCMakeCacheFile(cacheFilePath)
  82. if self.cmakeCache:
  83. self.compilerPath = self.cmakeCache.get("CMAKE_C_COMPILER", "")
  84. self.sdkPath = self.cmakeCache.get("ZEPHYR_SDK_INSTALL_DIR", "")
  85. # determine path from build dir to CMake file-based API index file, then
  86. # parse it and return the Codemodel
  87. def getCodemodel(self):
  88. log.dbg("getting codemodel from CMake API reply files")
  89. # make sure the reply directory exists
  90. cmakeReplyDirPath = os.path.join(self.cfg.buildDir, ".cmake", "api", "v1", "reply")
  91. if not os.path.exists(cmakeReplyDirPath):
  92. log.err(f'cmake api reply directory {cmakeReplyDirPath} does not exist')
  93. log.err('was query directory created before cmake build ran?')
  94. return None
  95. if not os.path.isdir(cmakeReplyDirPath):
  96. log.err(f'cmake api reply directory {cmakeReplyDirPath} exists but is not a directory')
  97. return None
  98. # find file with "index" prefix; there should only be one
  99. indexFilePath = ""
  100. for f in os.listdir(cmakeReplyDirPath):
  101. if f.startswith("index"):
  102. indexFilePath = os.path.join(cmakeReplyDirPath, f)
  103. break
  104. if indexFilePath == "":
  105. # didn't find it
  106. log.err(f'cmake api reply index file not found in {cmakeReplyDirPath}')
  107. return None
  108. # parse it
  109. return parseReply(indexFilePath)
  110. # set up Documents before beginning
  111. def setupDocuments(self):
  112. log.dbg("setting up placeholder documents")
  113. # set up build document
  114. cfgBuild = DocumentConfig()
  115. cfgBuild.name = "build"
  116. cfgBuild.namespace = self.cfg.namespacePrefix + "/build"
  117. cfgBuild.docRefID = "DocumentRef-build"
  118. self.docBuild = Document(cfgBuild)
  119. # we'll create the build packages in walkTargets()
  120. # the DESCRIBES relationship for the build document will be
  121. # with the zephyr_final package
  122. rd = RelationshipData()
  123. rd.ownerType = RelationshipDataElementType.DOCUMENT
  124. rd.ownerDocument = self.docBuild
  125. rd.otherType = RelationshipDataElementType.TARGETNAME
  126. rd.otherTargetName = "zephyr_final"
  127. rd.rlnType = "DESCRIBES"
  128. # add it to pending relationships queue
  129. self.pendingRelationships.append(rd)
  130. # set up zephyr document
  131. cfgZephyr = DocumentConfig()
  132. cfgZephyr.name = "zephyr-sources"
  133. cfgZephyr.namespace = self.cfg.namespacePrefix + "/zephyr"
  134. cfgZephyr.docRefID = "DocumentRef-zephyr"
  135. self.docZephyr = Document(cfgZephyr)
  136. # also set up zephyr sources package
  137. cfgPackageZephyr = PackageConfig()
  138. cfgPackageZephyr.name = "zephyr-sources"
  139. cfgPackageZephyr.spdxID = "SPDXRef-zephyr-sources"
  140. # relativeBaseDir is Zephyr sources topdir
  141. try:
  142. cfgPackageZephyr.relativeBaseDir = west_topdir(self.cm.paths_source)
  143. except WestNotFound:
  144. log.err(f"cannot find west_topdir for CMake Codemodel sources path {self.cm.paths_source}; bailing")
  145. return False
  146. pkgZephyr = Package(cfgPackageZephyr, self.docZephyr)
  147. self.docZephyr.pkgs[pkgZephyr.cfg.spdxID] = pkgZephyr
  148. # create DESCRIBES relationship data
  149. rd = RelationshipData()
  150. rd.ownerType = RelationshipDataElementType.DOCUMENT
  151. rd.ownerDocument = self.docZephyr
  152. rd.otherType = RelationshipDataElementType.PACKAGEID
  153. rd.otherPackageID = cfgPackageZephyr.spdxID
  154. rd.rlnType = "DESCRIBES"
  155. # add it to pending relationships queue
  156. self.pendingRelationships.append(rd)
  157. # set up app document
  158. cfgApp = DocumentConfig()
  159. cfgApp.name = "app-sources"
  160. cfgApp.namespace = self.cfg.namespacePrefix + "/app"
  161. cfgApp.docRefID = "DocumentRef-app"
  162. self.docApp = Document(cfgApp)
  163. # also set up app sources package
  164. cfgPackageApp = PackageConfig()
  165. cfgPackageApp.name = "app-sources"
  166. cfgPackageApp.spdxID = "SPDXRef-app-sources"
  167. # relativeBaseDir is app sources dir
  168. cfgPackageApp.relativeBaseDir = self.cm.paths_source
  169. pkgApp = Package(cfgPackageApp, self.docApp)
  170. self.docApp.pkgs[pkgApp.cfg.spdxID] = pkgApp
  171. # create DESCRIBES relationship data
  172. rd = RelationshipData()
  173. rd.ownerType = RelationshipDataElementType.DOCUMENT
  174. rd.ownerDocument = self.docApp
  175. rd.otherType = RelationshipDataElementType.PACKAGEID
  176. rd.otherPackageID = cfgPackageApp.spdxID
  177. rd.rlnType = "DESCRIBES"
  178. # add it to pending relationships queue
  179. self.pendingRelationships.append(rd)
  180. if self.cfg.includeSDK:
  181. # set up SDK document
  182. cfgSDK = DocumentConfig()
  183. cfgSDK.name = "sdk"
  184. cfgSDK.namespace = self.cfg.namespacePrefix + "/sdk"
  185. cfgSDK.docRefID = "DocumentRef-sdk"
  186. self.docSDK = Document(cfgSDK)
  187. # also set up zephyr sdk package
  188. cfgPackageSDK = PackageConfig()
  189. cfgPackageSDK.name = "sdk"
  190. cfgPackageSDK.spdxID = "SPDXRef-sdk"
  191. # relativeBaseDir is SDK dir
  192. cfgPackageSDK.relativeBaseDir = self.sdkPath
  193. pkgSDK = Package(cfgPackageSDK, self.docSDK)
  194. self.docSDK.pkgs[pkgSDK.cfg.spdxID] = pkgSDK
  195. # create DESCRIBES relationship data
  196. rd = RelationshipData()
  197. rd.ownerType = RelationshipDataElementType.DOCUMENT
  198. rd.ownerDocument = self.docSDK
  199. rd.otherType = RelationshipDataElementType.PACKAGEID
  200. rd.otherPackageID = cfgPackageSDK.spdxID
  201. rd.rlnType = "DESCRIBES"
  202. # add it to pending relationships queue
  203. self.pendingRelationships.append(rd)
  204. return True
  205. # walk through targets and gather information
  206. def walkTargets(self):
  207. log.dbg("walking targets from codemodel")
  208. # assuming just one configuration; consider whether this is incorrect
  209. cfgTargets = self.cm.configurations[0].configTargets
  210. for cfgTarget in cfgTargets:
  211. # build the Package for this target
  212. pkg = self.initConfigTargetPackage(cfgTarget)
  213. # see whether this target has any build artifacts at all
  214. if len(cfgTarget.target.artifacts) > 0:
  215. # add its build file
  216. bf = self.addBuildFile(cfgTarget, pkg)
  217. # get its source files
  218. self.collectPendingSourceFiles(cfgTarget, pkg, bf)
  219. else:
  220. log.dbg(f" - target {cfgTarget.name} has no build artifacts")
  221. # get its target dependencies
  222. self.collectTargetDependencies(cfgTargets, cfgTarget, pkg)
  223. # build a Package in the Build doc for the given ConfigTarget
  224. def initConfigTargetPackage(self, cfgTarget):
  225. log.dbg(f" - initializing Package for target: {cfgTarget.name}")
  226. # create target Package's config
  227. cfg = PackageConfig()
  228. cfg.name = cfgTarget.name
  229. cfg.spdxID = "SPDXRef-" + zspdx.spdxids.convertToSPDXIDSafe(cfgTarget.name)
  230. cfg.relativeBaseDir = self.cm.paths_build
  231. # build Package
  232. pkg = Package(cfg, self.docBuild)
  233. # add Package to build Document
  234. self.docBuild.pkgs[cfg.spdxID] = pkg
  235. return pkg
  236. # create a target's build product File and add it to its Package
  237. # call with:
  238. # 1) ConfigTarget
  239. # 2) Package for that target
  240. # returns: File
  241. def addBuildFile(self, cfgTarget, pkg):
  242. # assumes only one artifact in each target
  243. artifactPath = os.path.join(pkg.cfg.relativeBaseDir, cfgTarget.target.artifacts[0])
  244. log.dbg(f" - adding File {artifactPath}")
  245. log.dbg(f" - relativeBaseDir: {pkg.cfg.relativeBaseDir}")
  246. log.dbg(f" - artifacts[0]: {cfgTarget.target.artifacts[0]}")
  247. # create build File
  248. bf = File(self.docBuild, pkg)
  249. bf.abspath = artifactPath
  250. bf.relpath = cfgTarget.target.artifacts[0]
  251. # can use nameOnDisk b/c it is just the filename w/out directory paths
  252. bf.spdxID = zspdx.spdxids.getUniqueFileID(cfgTarget.target.nameOnDisk, self.docBuild.timesSeen)
  253. # don't fill hashes / licenses / rlns now, we'll do that after walking
  254. # add File to Package
  255. pkg.files[bf.spdxID] = bf
  256. # add file path link to Document and global links
  257. self.docBuild.fileLinks[bf.abspath] = bf
  258. self.allFileLinks[bf.abspath] = self.docBuild
  259. # also set this file as the target package's build product file
  260. pkg.targetBuildFile = bf
  261. return bf
  262. # collect a target's source files, add to pending sources queue, and
  263. # create pending relationship data entry
  264. # call with:
  265. # 1) ConfigTarget
  266. # 2) Package for that target
  267. # 3) build File for that target
  268. def collectPendingSourceFiles(self, cfgTarget, pkg, bf):
  269. log.dbg(f" - collecting source files and adding to pending queue")
  270. targetIncludesSet = set()
  271. # walk through target's sources
  272. for src in cfgTarget.target.sources:
  273. log.dbg(f" - add pending source file and relationship for {src.path}")
  274. # get absolute path if we don't have it
  275. srcAbspath = src.path
  276. if not os.path.isabs(src.path):
  277. srcAbspath = os.path.join(self.cm.paths_source, src.path)
  278. # check whether it even exists
  279. if not (os.path.exists(srcAbspath) and os.path.isfile(srcAbspath)):
  280. log.dbg(f" - {srcAbspath} does not exist but is referenced in sources for target {pkg.cfg.name}; skipping")
  281. continue
  282. # add it to pending source files queue
  283. self.pendingSources.append(srcAbspath)
  284. # create relationship data
  285. rd = RelationshipData()
  286. rd.ownerType = RelationshipDataElementType.FILENAME
  287. rd.ownerFileAbspath = bf.abspath
  288. rd.otherType = RelationshipDataElementType.FILENAME
  289. rd.otherFileAbspath = srcAbspath
  290. rd.rlnType = "GENERATED_FROM"
  291. # add it to pending relationships queue
  292. self.pendingRelationships.append(rd)
  293. # collect this source file's includes
  294. if self.cfg.analyzeIncludes and self.compilerPath:
  295. includes = self.collectIncludes(cfgTarget, pkg, bf, src)
  296. for inc in includes:
  297. targetIncludesSet.add(inc)
  298. # make relationships for the overall included files,
  299. # avoiding duplicates for multiple source files including
  300. # the same headers
  301. targetIncludesList = list(targetIncludesSet)
  302. targetIncludesList.sort()
  303. for inc in targetIncludesList:
  304. # add it to pending source files queue
  305. self.pendingSources.append(inc)
  306. # create relationship data
  307. rd = RelationshipData()
  308. rd.ownerType = RelationshipDataElementType.FILENAME
  309. rd.ownerFileAbspath = bf.abspath
  310. rd.otherType = RelationshipDataElementType.FILENAME
  311. rd.otherFileAbspath = inc
  312. rd.rlnType = "GENERATED_FROM"
  313. # add it to pending relationships queue
  314. self.pendingRelationships.append(rd)
  315. # collect the include files corresponding to this source file
  316. # call with:
  317. # 1) ConfigTarget
  318. # 2) Package for this target
  319. # 3) build File for this target
  320. # 4) TargetSource entry for this source file
  321. # returns: sorted list of include files for this source file
  322. def collectIncludes(self, cfgTarget, pkg, bf, src):
  323. # get the right compile group for this source file
  324. if len(cfgTarget.target.compileGroups) < (src.compileGroupIndex + 1):
  325. log.dbg(f" - {cfgTarget.target.name} has compileGroupIndex {src.compileGroupIndex} but only {len(cfgTarget.target.compileGroups)} found; skipping included files search")
  326. return []
  327. cg = cfgTarget.target.compileGroups[src.compileGroupIndex]
  328. # currently only doing C includes
  329. if cg.language != "C":
  330. log.dbg(f" - {cfgTarget.target.name} has compile group language {cg.language} but currently only searching includes for C files; skipping included files search")
  331. return []
  332. srcAbspath = src.path
  333. if src.path[0] != "/":
  334. srcAbspath = os.path.join(self.cm.paths_source, src.path)
  335. return getCIncludes(self.compilerPath, srcAbspath, cg)
  336. # collect relationships for dependencies of this target Package
  337. # call with:
  338. # 1) all ConfigTargets from CodeModel
  339. # 2) this particular ConfigTarget
  340. # 3) Package for this Target
  341. def collectTargetDependencies(self, cfgTargets, cfgTarget, pkg):
  342. log.dbg(f" - collecting target dependencies for {pkg.cfg.name}")
  343. # walk through target's dependencies
  344. for dep in cfgTarget.target.dependencies:
  345. # extract dep name from its id
  346. depFragments = dep.id.split(":")
  347. depName = depFragments[0]
  348. log.dbg(f" - adding pending relationship for {depName}")
  349. # create relationship data between dependency packages
  350. rd = RelationshipData()
  351. rd.ownerType = RelationshipDataElementType.TARGETNAME
  352. rd.ownerTargetName = pkg.cfg.name
  353. rd.otherType = RelationshipDataElementType.TARGETNAME
  354. rd.otherTargetName = depName
  355. rd.rlnType = "HAS_PREREQUISITE"
  356. # add it to pending relationships queue
  357. self.pendingRelationships.append(rd)
  358. # if this is a target with any build artifacts (e.g. non-UTILITY),
  359. # also create STATIC_LINK relationship for dependency build files,
  360. # together with this Package's own target build file
  361. if len(cfgTarget.target.artifacts) == 0:
  362. continue
  363. # find the filename for the dependency's build product, using the
  364. # codemodel (since we might not have created this dependency's
  365. # Package or File yet)
  366. depAbspath = ""
  367. for ct in cfgTargets:
  368. if ct.name == depName:
  369. # skip utility targets
  370. if len(ct.target.artifacts) == 0:
  371. continue
  372. # all targets use the same relativeBaseDir, so this works
  373. # even though pkg is the owner package
  374. depAbspath = os.path.join(pkg.cfg.relativeBaseDir, ct.target.artifacts[0])
  375. break
  376. if depAbspath == "":
  377. continue
  378. # create relationship data between build files
  379. rd = RelationshipData()
  380. rd.ownerType = RelationshipDataElementType.FILENAME
  381. rd.ownerFileAbspath = pkg.targetBuildFile.abspath
  382. rd.otherType = RelationshipDataElementType.FILENAME
  383. rd.otherFileAbspath = depAbspath
  384. rd.rlnType = "STATIC_LINK"
  385. # add it to pending relationships queue
  386. self.pendingRelationships.append(rd)
  387. # walk through pending sources and create corresponding files,
  388. # assigning them to the appropriate Document and Package
  389. def walkPendingSources(self):
  390. log.dbg(f"walking pending sources")
  391. # only one package in each doc; get it
  392. pkgZephyr = list(self.docZephyr.pkgs.values())[0]
  393. pkgApp = list(self.docApp.pkgs.values())[0]
  394. if self.cfg.includeSDK:
  395. pkgSDK = list(self.docSDK.pkgs.values())[0]
  396. for srcAbspath in self.pendingSources:
  397. # check whether we've already seen it
  398. srcDoc = self.allFileLinks.get(srcAbspath, None)
  399. srcPkg = None
  400. if srcDoc:
  401. log.dbg(f" - {srcAbspath}: already seen, assigned to {srcDoc.cfg.name}")
  402. continue
  403. # not yet assigned; figure out where it goes
  404. pkgBuild = self.findBuildPackage(srcAbspath)
  405. if pkgBuild:
  406. log.dbg(f" - {srcAbspath}: assigning to build document, package {pkgBuild.cfg.name}")
  407. srcDoc = self.docBuild
  408. srcPkg = pkgBuild
  409. elif self.cfg.includeSDK and os.path.commonpath([srcAbspath, pkgSDK.cfg.relativeBaseDir]) == pkgSDK.cfg.relativeBaseDir:
  410. log.dbg(f" - {srcAbspath}: assigning to sdk document")
  411. srcDoc = self.docSDK
  412. srcPkg = pkgSDK
  413. elif os.path.commonpath([srcAbspath, pkgApp.cfg.relativeBaseDir]) == pkgApp.cfg.relativeBaseDir:
  414. log.dbg(f" - {srcAbspath}: assigning to app document")
  415. srcDoc = self.docApp
  416. srcPkg = pkgApp
  417. elif os.path.commonpath([srcAbspath, pkgZephyr.cfg.relativeBaseDir]) == pkgZephyr.cfg.relativeBaseDir:
  418. log.dbg(f" - {srcAbspath}: assigning to zephyr document")
  419. srcDoc = self.docZephyr
  420. srcPkg = pkgZephyr
  421. else:
  422. log.dbg(f" - {srcAbspath}: can't determine which document should own; skipping")
  423. continue
  424. # create File and assign it to the Package and Document
  425. sf = File(srcDoc, srcPkg)
  426. sf.abspath = srcAbspath
  427. sf.relpath = os.path.relpath(srcAbspath, srcPkg.cfg.relativeBaseDir)
  428. filenameOnly = os.path.split(srcAbspath)[1]
  429. sf.spdxID = zspdx.spdxids.getUniqueFileID(filenameOnly, srcDoc.timesSeen)
  430. # don't fill hashes / licenses / rlns now, we'll do that after walking
  431. # add File to Package
  432. srcPkg.files[sf.spdxID] = sf
  433. # add file path link to Document and global links
  434. srcDoc.fileLinks[sf.abspath] = sf
  435. self.allFileLinks[sf.abspath] = srcDoc
  436. # figure out which build Package contains the given file, if any
  437. # call with:
  438. # 1) absolute path for source filename being searched
  439. def findBuildPackage(self, srcAbspath):
  440. # Multiple target Packages might "contain" the file path, if they
  441. # are nested. If so, the one with the longest path would be the
  442. # most deeply-nested target directory, so that's the one which
  443. # should get the file path.
  444. pkgLongestMatch = None
  445. for pkg in self.docBuild.pkgs.values():
  446. if os.path.commonpath([srcAbspath, pkg.cfg.relativeBaseDir]) == pkg.cfg.relativeBaseDir:
  447. # the package does contain this file; is it the deepest?
  448. if pkgLongestMatch:
  449. if len(pkg.cfg.relativeBaseDir) > len(pkgLongestMatch.cfg.relativeBaseDir):
  450. pkgLongestMatch = pkg
  451. else:
  452. # first package containing it, so assign it
  453. pkgLongestMatch = pkg
  454. return pkgLongestMatch
  455. # walk through pending RelationshipData entries, create corresponding
  456. # Relationships, and assign them to the applicable Files / Packages
  457. def walkRelationships(self):
  458. for rlnData in self.pendingRelationships:
  459. rln = Relationship()
  460. # get left side of relationship data
  461. docA, spdxIDA, rlnsA = self.getRelationshipLeft(rlnData)
  462. if not docA or not spdxIDA:
  463. continue
  464. rln.refA = spdxIDA
  465. # get right side of relationship data
  466. spdxIDB = self.getRelationshipRight(rlnData, docA)
  467. if not spdxIDB:
  468. continue
  469. rln.refB = spdxIDB
  470. rln.rlnType = rlnData.rlnType
  471. rlnsA.append(rln)
  472. log.dbg(f" - adding relationship to {docA.cfg.name}: {rln.refA} {rln.rlnType} {rln.refB}")
  473. # get owner (left side) document and SPDX ID of Relationship for given RelationshipData
  474. # returns: doc, spdxID, rlnsArray (for either Document, Package, or File, as applicable)
  475. def getRelationshipLeft(self, rlnData):
  476. if rlnData.ownerType == RelationshipDataElementType.FILENAME:
  477. # find the document for this file abspath, and then the specific file's ID
  478. ownerDoc = self.allFileLinks.get(rlnData.ownerFileAbspath, None)
  479. if not ownerDoc:
  480. log.dbg(f" - searching for relationship, can't find document with file {rlnData.ownerFileAbspath}; skipping")
  481. return None, None, None
  482. sf = ownerDoc.fileLinks.get(rlnData.ownerFileAbspath, None)
  483. if not sf:
  484. log.dbg(f" - searching for relationship for file {rlnData.ownerFileAbspath} points to document {ownerDoc.cfg.name} but file not found; skipping")
  485. return None, None, None
  486. # found it
  487. if not sf.spdxID:
  488. log.dbg(f" - searching for relationship for file {rlnData.ownerFileAbspath} found file, but empty ID; skipping")
  489. return None, None, None
  490. return ownerDoc, sf.spdxID, sf.rlns
  491. elif rlnData.ownerType == RelationshipDataElementType.TARGETNAME:
  492. # find the document for this target name, and then the specific package's ID
  493. # for target names, must be docBuild
  494. ownerDoc = self.docBuild
  495. # walk through target Packages and check names
  496. for pkg in ownerDoc.pkgs.values():
  497. if pkg.cfg.name == rlnData.ownerTargetName:
  498. if not pkg.cfg.spdxID:
  499. log.dbg(f" - searching for relationship for target {rlnData.ownerTargetName} found package, but empty ID; skipping")
  500. return None, None, None
  501. return ownerDoc, pkg.cfg.spdxID, pkg.rlns
  502. log.dbg(f" - searching for relationship for target {rlnData.ownerTargetName}, target not found in build document; skipping")
  503. return None, None, None
  504. elif rlnData.ownerType == RelationshipDataElementType.DOCUMENT:
  505. # will always be SPDXRef-DOCUMENT
  506. return rlnData.ownerDocument, "SPDXRef-DOCUMENT", rlnData.ownerDocument.relationships
  507. else:
  508. log.dbg(f" - unknown relationship type {rlnData.ownerType}; skipping")
  509. return None, None, None
  510. # get other (right side) SPDX ID of Relationship for given RelationshipData
  511. def getRelationshipRight(self, rlnData, docA):
  512. if rlnData.otherType == RelationshipDataElementType.FILENAME:
  513. # find the document for this file abspath, and then the specific file's ID
  514. otherDoc = self.allFileLinks.get(rlnData.otherFileAbspath, None)
  515. if not otherDoc:
  516. log.dbg(f" - searching for relationship, can't find document with file {rlnData.otherFileAbspath}; skipping")
  517. return None
  518. bf = otherDoc.fileLinks.get(rlnData.otherFileAbspath, None)
  519. if not bf:
  520. log.dbg(f" - searching for relationship for file {rlnData.otherFileAbspath} points to document {otherDoc.cfg.name} but file not found; skipping")
  521. return None
  522. # found it
  523. if not bf.spdxID:
  524. log.dbg(f" - searching for relationship for file {rlnData.otherFileAbspath} found file, but empty ID; skipping")
  525. return None
  526. # figure out whether to append DocumentRef
  527. spdxIDB = bf.spdxID
  528. if otherDoc != docA:
  529. spdxIDB = otherDoc.cfg.docRefID + ":" + spdxIDB
  530. docA.externalDocuments.add(otherDoc)
  531. return spdxIDB
  532. elif rlnData.otherType == RelationshipDataElementType.TARGETNAME:
  533. # find the document for this target name, and then the specific package's ID
  534. # for target names, must be docBuild
  535. otherDoc = self.docBuild
  536. # walk through target Packages and check names
  537. for pkg in otherDoc.pkgs.values():
  538. if pkg.cfg.name == rlnData.otherTargetName:
  539. if not pkg.cfg.spdxID:
  540. log.dbg(f" - searching for relationship for target {rlnData.otherTargetName} found package, but empty ID; skipping")
  541. return None
  542. spdxIDB = pkg.cfg.spdxID
  543. if otherDoc != docA:
  544. spdxIDB = otherDoc.cfg.docRefID + ":" + spdxIDB
  545. docA.externalDocuments.add(otherDoc)
  546. return spdxIDB
  547. log.dbg(f" - searching for relationship for target {rlnData.otherTargetName}, target not found in build document; skipping")
  548. return None
  549. elif rlnData.otherType == RelationshipDataElementType.PACKAGEID:
  550. # will just be the package ID that was passed in
  551. return rlnData.otherPackageID
  552. else:
  553. log.dbg(f" - unknown relationship type {rlnData.otherType}; skipping")
  554. return None