1# Copyright (c) 2020 The Linux Foundation 2# 3# SPDX-License-Identifier: Apache-2.0 4 5import json 6import os 7 8from west import log 9 10import zspdx.cmakefileapi 11 12def parseReply(replyIndexPath): 13 replyDir, _ = os.path.split(replyIndexPath) 14 15 # first we need to find the codemodel reply file 16 try: 17 with open(replyIndexPath, 'r') as indexFile: 18 js = json.load(indexFile) 19 20 # get reply object 21 reply_dict = js.get("reply", {}) 22 if reply_dict == {}: 23 log.err(f"no \"reply\" field found in index file") 24 return None 25 # get codemodel object 26 cm_dict = reply_dict.get("codemodel-v2", {}) 27 if cm_dict == {}: 28 log.err(f"no \"codemodel-v2\" field found in \"reply\" object in index file") 29 return None 30 # and get codemodel filename 31 jsonFile = cm_dict.get("jsonFile", "") 32 if jsonFile == "": 33 log.err(f"no \"jsonFile\" field found in \"codemodel-v2\" object in index file") 34 return None 35 36 return parseCodemodel(replyDir, jsonFile) 37 38 except OSError as e: 39 log.err(f"Error loading {replyIndexPath}: {str(e)}") 40 return None 41 except json.decoder.JSONDecodeError as e: 42 log.err(f"Error parsing JSON in {replyIndexPath}: {str(e)}") 43 return None 44 45def parseCodemodel(replyDir, codemodelFile): 46 codemodelPath = os.path.join(replyDir, codemodelFile) 47 48 try: 49 with open(codemodelPath, 'r') as cmFile: 50 js = json.load(cmFile) 51 52 cm = zspdx.cmakefileapi.Codemodel() 53 54 # for correctness, check kind and version 55 kind = js.get("kind", "") 56 if kind != "codemodel": 57 log.err(f"Error loading CMake API reply: expected \"kind\":\"codemodel\" in {codemodelPath}, got {kind}") 58 return None 59 version = js.get("version", {}) 60 versionMajor = version.get("major", -1) 61 if versionMajor != 2: 62 if versionMajor == -1: 63 log.err(f"Error loading CMake API reply: expected major version 2 in {codemodelPath}, no version found") 64 return None 65 log.err(f"Error loading CMake API reply: expected major version 2 in {codemodelPath}, got {versionMajor}") 66 return None 67 68 # get paths 69 paths_dict = js.get("paths", {}) 70 cm.paths_source = paths_dict.get("source", "") 71 cm.paths_build = paths_dict.get("build", "") 72 73 # get configurations 74 configs_arr = js.get("configurations", []) 75 for cfg_dict in configs_arr: 76 cfg = parseConfig(cfg_dict, replyDir) 77 if cfg: 78 cm.configurations.append(cfg) 79 80 # and after parsing is done, link all the indices 81 linkCodemodel(cm) 82 83 return cm 84 85 except OSError as e: 86 log.err(f"Error loading {codemodelPath}: {str(e)}") 87 return None 88 except json.decoder.JSONDecodeError as e: 89 log.err(f"Error parsing JSON in {codemodelPath}: {str(e)}") 90 return None 91 92def parseConfig(cfg_dict, replyDir): 93 cfg = zspdx.cmakefileapi.Config() 94 cfg.name = cfg_dict.get("name", "") 95 96 # parse and add each directory 97 dirs_arr = cfg_dict.get("directories", []) 98 for dir_dict in dirs_arr: 99 if dir_dict != {}: 100 cfgdir = zspdx.cmakefileapi.ConfigDir() 101 cfgdir.source = dir_dict.get("source", "") 102 cfgdir.build = dir_dict.get("build", "") 103 cfgdir.parentIndex = dir_dict.get("parentIndex", -1) 104 cfgdir.childIndexes = dir_dict.get("childIndexes", []) 105 cfgdir.projectIndex = dir_dict.get("projectIndex", -1) 106 cfgdir.targetIndexes = dir_dict.get("targetIndexes", []) 107 minCMakeVer_dict = dir_dict.get("minimumCMakeVersion", {}) 108 cfgdir.minimumCMakeVersion = minCMakeVer_dict.get("string", "") 109 cfgdir.hasInstallRule = dir_dict.get("hasInstallRule", False) 110 cfg.directories.append(cfgdir) 111 112 # parse and add each project 113 projects_arr = cfg_dict.get("projects", []) 114 for prj_dict in projects_arr: 115 if prj_dict != {}: 116 prj = zspdx.cmakefileapi.ConfigProject() 117 prj.name = prj_dict.get("name", "") 118 prj.parentIndex = prj_dict.get("parentIndex", -1) 119 prj.childIndexes = prj_dict.get("childIndexes", []) 120 prj.directoryIndexes = prj_dict.get("directoryIndexes", []) 121 prj.targetIndexes = prj_dict.get("targetIndexes", []) 122 cfg.projects.append(prj) 123 124 # parse and add each target 125 cfgTargets_arr = cfg_dict.get("targets", []) 126 for cfgTarget_dict in cfgTargets_arr: 127 if cfgTarget_dict != {}: 128 cfgTarget = zspdx.cmakefileapi.ConfigTarget() 129 cfgTarget.name = cfgTarget_dict.get("name", "") 130 cfgTarget.id = cfgTarget_dict.get("id", "") 131 cfgTarget.directoryIndex = cfgTarget_dict.get("directoryIndex", -1) 132 cfgTarget.projectIndex = cfgTarget_dict.get("projectIndex", -1) 133 cfgTarget.jsonFile = cfgTarget_dict.get("jsonFile", "") 134 135 if cfgTarget.jsonFile != "": 136 cfgTarget.target = parseTarget(os.path.join(replyDir, cfgTarget.jsonFile)) 137 else: 138 cfgTarget.target = None 139 140 cfg.configTargets.append(cfgTarget) 141 142 return cfg 143 144def parseTarget(targetPath): 145 try: 146 with open(targetPath, 'r') as targetFile: 147 js = json.load(targetFile) 148 149 target = zspdx.cmakefileapi.Target() 150 151 target.name = js.get("name", "") 152 target.id = js.get("id", "") 153 target.type = parseTargetType(js.get("type", "UNKNOWN")) 154 target.backtrace = js.get("backtrace", -1) 155 target.folder = js.get("folder", "") 156 157 # get paths 158 paths_dict = js.get("paths", {}) 159 target.paths_source = paths_dict.get("source", "") 160 target.paths_build = paths_dict.get("build", "") 161 162 target.nameOnDisk = js.get("nameOnDisk", "") 163 164 # parse artifacts if present 165 artifacts_arr = js.get("artifacts", []) 166 target.artifacts = [] 167 for artifact_dict in artifacts_arr: 168 artifact_path = artifact_dict.get("path", "") 169 if artifact_path != "": 170 target.artifacts.append(artifact_path) 171 172 target.isGeneratorProvided = js.get("isGeneratorProvided", False) 173 174 # call separate functions to parse subsections 175 parseTargetInstall(target, js) 176 parseTargetLink(target, js) 177 parseTargetArchive(target, js) 178 parseTargetDependencies(target, js) 179 parseTargetSources(target, js) 180 parseTargetSourceGroups(target, js) 181 parseTargetCompileGroups(target, js) 182 parseTargetBacktraceGraph(target, js) 183 184 return target 185 186 except OSError as e: 187 log.err(f"Error loading {targetPath}: {str(e)}") 188 return None 189 except json.decoder.JSONDecodeError as e: 190 log.err(f"Error parsing JSON in {targetPath}: {str(e)}") 191 return None 192 193def parseTargetType(targetType): 194 if targetType == "EXECUTABLE": 195 return zspdx.cmakefileapi.TargetType.EXECUTABLE 196 elif targetType == "STATIC_LIBRARY": 197 return zspdx.cmakefileapi.TargetType.STATIC_LIBRARY 198 elif targetType == "SHARED_LIBRARY": 199 return zspdx.cmakefileapi.TargetType.SHARED_LIBRARY 200 elif targetType == "MODULE_LIBRARY": 201 return zspdx.cmakefileapi.TargetType.MODULE_LIBRARY 202 elif targetType == "OBJECT_LIBRARY": 203 return zspdx.cmakefileapi.TargetType.OBJECT_LIBRARY 204 elif targetType == "UTILITY": 205 return zspdx.cmakefileapi.TargetType.UTILITY 206 else: 207 return zspdx.cmakefileapi.TargetType.UNKNOWN 208 209def parseTargetInstall(target, js): 210 install_dict = js.get("install", {}) 211 if install_dict == {}: 212 return 213 prefix_dict = install_dict.get("prefix", {}) 214 target.install_prefix = prefix_dict.get("path", "") 215 216 destinations_arr = install_dict.get("destinations", []) 217 for destination_dict in destinations_arr: 218 dest = zspdx.cmakefileapi.TargetInstallDestination() 219 dest.path = destination_dict.get("path", "") 220 dest.backtrace = destination_dict.get("backtrace", -1) 221 target.install_destinations.append(dest) 222 223def parseTargetLink(target, js): 224 link_dict = js.get("link", {}) 225 if link_dict == {}: 226 return 227 target.link_language = link_dict.get("language", {}) 228 target.link_lto = link_dict.get("lto", False) 229 sysroot_dict = link_dict.get("sysroot", {}) 230 target.link_sysroot = sysroot_dict.get("path", "") 231 232 fragments_arr = link_dict.get("commandFragments", []) 233 for fragment_dict in fragments_arr: 234 fragment = zspdx.cmakefileapi.TargetCommandFragment() 235 fragment.fragment = fragment_dict.get("fragment", "") 236 fragment.role = fragment_dict.get("role", "") 237 target.link_commandFragments.append(fragment) 238 239def parseTargetArchive(target, js): 240 archive_dict = js.get("archive", {}) 241 if archive_dict == {}: 242 return 243 target.archive_lto = archive_dict.get("lto", False) 244 245 fragments_arr = archive_dict.get("commandFragments", []) 246 for fragment_dict in fragments_arr: 247 fragment = zspdx.cmakefileapi.TargetCommandFragment() 248 fragment.fragment = fragment_dict.get("fragment", "") 249 fragment.role = fragment_dict.get("role", "") 250 target.archive_commandFragments.append(fragment) 251 252def parseTargetDependencies(target, js): 253 dependencies_arr = js.get("dependencies", []) 254 for dependency_dict in dependencies_arr: 255 dep = zspdx.cmakefileapi.TargetDependency() 256 dep.id = dependency_dict.get("id", "") 257 dep.backtrace = dependency_dict.get("backtrace", -1) 258 target.dependencies.append(dep) 259 260def parseTargetSources(target, js): 261 sources_arr = js.get("sources", []) 262 for source_dict in sources_arr: 263 src = zspdx.cmakefileapi.TargetSource() 264 src.path = source_dict.get("path", "") 265 src.compileGroupIndex = source_dict.get("compileGroupIndex", -1) 266 src.sourceGroupIndex = source_dict.get("sourceGroupIndex", -1) 267 src.isGenerated = source_dict.get("isGenerated", False) 268 src.backtrace = source_dict.get("backtrace", -1) 269 target.sources.append(src) 270 271def parseTargetSourceGroups(target, js): 272 sourceGroups_arr = js.get("sourceGroups", []) 273 for sourceGroup_dict in sourceGroups_arr: 274 srcgrp = zspdx.cmakefileapi.TargetSourceGroup() 275 srcgrp.name = sourceGroup_dict.get("name", "") 276 srcgrp.sourceIndexes = sourceGroup_dict.get("sourceIndexes", []) 277 target.sourceGroups.append(srcgrp) 278 279def parseTargetCompileGroups(target, js): 280 compileGroups_arr = js.get("compileGroups", []) 281 for compileGroup_dict in compileGroups_arr: 282 cmpgrp = zspdx.cmakefileapi.TargetCompileGroup() 283 cmpgrp.sourceIndexes = compileGroup_dict.get("sourceIndexes", []) 284 cmpgrp.language = compileGroup_dict.get("language", "") 285 cmpgrp.sysroot = compileGroup_dict.get("sysroot", "") 286 287 commandFragments_arr = compileGroup_dict.get("compileCommandFragments", []) 288 for commandFragment_dict in commandFragments_arr: 289 fragment = commandFragment_dict.get("fragment", "") 290 if fragment != "": 291 cmpgrp.compileCommandFragments.append(fragment) 292 293 includes_arr = compileGroup_dict.get("includes", []) 294 for include_dict in includes_arr: 295 grpInclude = zspdx.cmakefileapi.TargetCompileGroupInclude() 296 grpInclude.path = include_dict.get("path", "") 297 grpInclude.isSystem = include_dict.get("isSystem", False) 298 grpInclude.backtrace = include_dict.get("backtrace", -1) 299 cmpgrp.includes.append(grpInclude) 300 301 precompileHeaders_arr = compileGroup_dict.get("precompileHeaders", []) 302 for precompileHeader_dict in precompileHeaders_arr: 303 grpHeader = zspdx.cmakefileapi.TargetCompileGroupPrecompileHeader() 304 grpHeader.header = precompileHeader_dict.get("header", "") 305 grpHeader.backtrace = precompileHeader_dict.get("backtrace", -1) 306 cmpgrp.precompileHeaders.append(grpHeader) 307 308 defines_arr = compileGroup_dict.get("defines", []) 309 for define_dict in defines_arr: 310 grpDefine = zspdx.cmakefileapi.TargetCompileGroupDefine() 311 grpDefine.define = define_dict.get("define", "") 312 grpDefine.backtrace = define_dict.get("backtrace", -1) 313 cmpgrp.defines.append(grpDefine) 314 315 target.compileGroups.append(cmpgrp) 316 317def parseTargetBacktraceGraph(target, js): 318 backtraceGraph_dict = js.get("backtraceGraph", {}) 319 if backtraceGraph_dict == {}: 320 return 321 target.backtraceGraph_commands = backtraceGraph_dict.get("commands", []) 322 target.backtraceGraph_files = backtraceGraph_dict.get("files", []) 323 324 nodes_arr = backtraceGraph_dict.get("nodes", []) 325 for node_dict in nodes_arr: 326 node = zspdx.cmakefileapi.TargetBacktraceGraphNode() 327 node.file = node_dict.get("file", -1) 328 node.line = node_dict.get("line", -1) 329 node.command = node_dict.get("command", -1) 330 node.parent = node_dict.get("parent", -1) 331 target.backtraceGraph_nodes.append(node) 332 333# Create direct pointers for all Configs in Codemodel 334# takes: Codemodel 335def linkCodemodel(cm): 336 for cfg in cm.configurations: 337 linkConfig(cfg) 338 339# Create direct pointers for all contents of Config 340# takes: Config 341def linkConfig(cfg): 342 for cfgDir in cfg.directories: 343 linkConfigDir(cfg, cfgDir) 344 for cfgPrj in cfg.projects: 345 linkConfigProject(cfg, cfgPrj) 346 for cfgTarget in cfg.configTargets: 347 linkConfigTarget(cfg, cfgTarget) 348 349# Create direct pointers for ConfigDir indices 350# takes: Config and ConfigDir 351def linkConfigDir(cfg, cfgDir): 352 if cfgDir.parentIndex == -1: 353 cfgDir.parent = None 354 else: 355 cfgDir.parent = cfg.directories[cfgDir.parentIndex] 356 357 if cfgDir.projectIndex == -1: 358 cfgDir.project = None 359 else: 360 cfgDir.project = cfg.projects[cfgDir.projectIndex] 361 362 cfgDir.children = [] 363 for childIndex in cfgDir.childIndexes: 364 cfgDir.children.append(cfg.directories[childIndex]) 365 366 cfgDir.targets = [] 367 for targetIndex in cfgDir.targetIndexes: 368 cfgDir.targets.append(cfg.configTargets[targetIndex]) 369 370# Create direct pointers for ConfigProject indices 371# takes: Config and ConfigProject 372def linkConfigProject(cfg, cfgPrj): 373 if cfgPrj.parentIndex == -1: 374 cfgPrj.parent = None 375 else: 376 cfgPrj.parent = cfg.projects[cfgPrj.parentIndex] 377 378 cfgPrj.children = [] 379 for childIndex in cfgPrj.childIndexes: 380 cfgPrj.children.append(cfg.projects[childIndex]) 381 382 cfgPrj.directories = [] 383 for dirIndex in cfgPrj.directoryIndexes: 384 cfgPrj.directories.append(cfg.directories[dirIndex]) 385 386 cfgPrj.targets = [] 387 for targetIndex in cfgPrj.targetIndexes: 388 cfgPrj.targets.append(cfg.configTargets[targetIndex]) 389 390# Create direct pointers for ConfigTarget indices 391# takes: Config and ConfigTarget 392def linkConfigTarget(cfg, cfgTarget): 393 if cfgTarget.directoryIndex == -1: 394 cfgTarget.directory = None 395 else: 396 cfgTarget.directory = cfg.directories[cfgTarget.directoryIndex] 397 398 if cfgTarget.projectIndex == -1: 399 cfgTarget.project = None 400 else: 401 cfgTarget.project = cfg.projects[cfgTarget.projectIndex] 402 403 # and link target's sources and source groups 404 for ts in cfgTarget.target.sources: 405 linkTargetSource(cfgTarget.target, ts) 406 for tsg in cfgTarget.target.sourceGroups: 407 linkTargetSourceGroup(cfgTarget.target, tsg) 408 for tcg in cfgTarget.target.compileGroups: 409 linkTargetCompileGroup(cfgTarget.target, tcg) 410 411# Create direct pointers for TargetSource indices 412# takes: Target and TargetSource 413def linkTargetSource(target, targetSrc): 414 if targetSrc.compileGroupIndex == -1: 415 targetSrc.compileGroup = None 416 else: 417 targetSrc.compileGroup = target.compileGroups[targetSrc.compileGroupIndex] 418 419 if targetSrc.sourceGroupIndex == -1: 420 targetSrc.sourceGroup = None 421 else: 422 targetSrc.sourceGroup = target.sourceGroups[targetSrc.sourceGroupIndex] 423 424# Create direct pointers for TargetSourceGroup indices 425# takes: Target and TargetSourceGroup 426def linkTargetSourceGroup(target, targetSrcGrp): 427 targetSrcGrp.sources = [] 428 for srcIndex in targetSrcGrp.sourceIndexes: 429 targetSrcGrp.sources.append(target.sources[srcIndex]) 430 431# Create direct pointers for TargetCompileGroup indices 432# takes: Target and TargetCompileGroup 433def linkTargetCompileGroup(target, targetCmpGrp): 434 targetCmpGrp.sources = [] 435 for srcIndex in targetCmpGrp.sourceIndexes: 436 targetCmpGrp.sources.append(target.sources[srcIndex]) 437