# Copyright (c) 2020 The Linux Foundation # # SPDX-License-Identifier: Apache-2.0 import json import os from west import log import zspdx.cmakefileapi def parseReply(replyIndexPath): replyDir, _ = os.path.split(replyIndexPath) # first we need to find the codemodel reply file try: with open(replyIndexPath, 'r') as indexFile: js = json.load(indexFile) # get reply object reply_dict = js.get("reply", {}) if reply_dict == {}: log.err(f"no \"reply\" field found in index file") return None # get codemodel object cm_dict = reply_dict.get("codemodel-v2", {}) if cm_dict == {}: log.err(f"no \"codemodel-v2\" field found in \"reply\" object in index file") return None # and get codemodel filename jsonFile = cm_dict.get("jsonFile", "") if jsonFile == "": log.err(f"no \"jsonFile\" field found in \"codemodel-v2\" object in index file") return None return parseCodemodel(replyDir, jsonFile) except OSError as e: log.err(f"Error loading {replyIndexPath}: {str(e)}") return None except json.decoder.JSONDecodeError as e: log.err(f"Error parsing JSON in {replyIndexPath}: {str(e)}") return None def parseCodemodel(replyDir, codemodelFile): codemodelPath = os.path.join(replyDir, codemodelFile) try: with open(codemodelPath, 'r') as cmFile: js = json.load(cmFile) cm = zspdx.cmakefileapi.Codemodel() # for correctness, check kind and version kind = js.get("kind", "") if kind != "codemodel": log.err(f"Error loading CMake API reply: expected \"kind\":\"codemodel\" in {codemodelPath}, got {kind}") return None version = js.get("version", {}) versionMajor = version.get("major", -1) if versionMajor != 2: if versionMajor == -1: log.err(f"Error loading CMake API reply: expected major version 2 in {codemodelPath}, no version found") return None log.err(f"Error loading CMake API reply: expected major version 2 in {codemodelPath}, got {versionMajor}") return None # get paths paths_dict = js.get("paths", {}) cm.paths_source = paths_dict.get("source", "") cm.paths_build = paths_dict.get("build", "") # get configurations configs_arr = js.get("configurations", []) for cfg_dict in configs_arr: cfg = parseConfig(cfg_dict, replyDir) if cfg: cm.configurations.append(cfg) # and after parsing is done, link all the indices linkCodemodel(cm) return cm except OSError as e: log.err(f"Error loading {codemodelPath}: {str(e)}") return None except json.decoder.JSONDecodeError as e: log.err(f"Error parsing JSON in {codemodelPath}: {str(e)}") return None def parseConfig(cfg_dict, replyDir): cfg = zspdx.cmakefileapi.Config() cfg.name = cfg_dict.get("name", "") # parse and add each directory dirs_arr = cfg_dict.get("directories", []) for dir_dict in dirs_arr: if dir_dict != {}: cfgdir = zspdx.cmakefileapi.ConfigDir() cfgdir.source = dir_dict.get("source", "") cfgdir.build = dir_dict.get("build", "") cfgdir.parentIndex = dir_dict.get("parentIndex", -1) cfgdir.childIndexes = dir_dict.get("childIndexes", []) cfgdir.projectIndex = dir_dict.get("projectIndex", -1) cfgdir.targetIndexes = dir_dict.get("targetIndexes", []) minCMakeVer_dict = dir_dict.get("minimumCMakeVersion", {}) cfgdir.minimumCMakeVersion = minCMakeVer_dict.get("string", "") cfgdir.hasInstallRule = dir_dict.get("hasInstallRule", False) cfg.directories.append(cfgdir) # parse and add each project projects_arr = cfg_dict.get("projects", []) for prj_dict in projects_arr: if prj_dict != {}: prj = zspdx.cmakefileapi.ConfigProject() prj.name = prj_dict.get("name", "") prj.parentIndex = prj_dict.get("parentIndex", -1) prj.childIndexes = prj_dict.get("childIndexes", []) prj.directoryIndexes = prj_dict.get("directoryIndexes", []) prj.targetIndexes = prj_dict.get("targetIndexes", []) cfg.projects.append(prj) # parse and add each target cfgTargets_arr = cfg_dict.get("targets", []) for cfgTarget_dict in cfgTargets_arr: if cfgTarget_dict != {}: cfgTarget = zspdx.cmakefileapi.ConfigTarget() cfgTarget.name = cfgTarget_dict.get("name", "") cfgTarget.id = cfgTarget_dict.get("id", "") cfgTarget.directoryIndex = cfgTarget_dict.get("directoryIndex", -1) cfgTarget.projectIndex = cfgTarget_dict.get("projectIndex", -1) cfgTarget.jsonFile = cfgTarget_dict.get("jsonFile", "") if cfgTarget.jsonFile != "": cfgTarget.target = parseTarget(os.path.join(replyDir, cfgTarget.jsonFile)) else: cfgTarget.target = None cfg.configTargets.append(cfgTarget) return cfg def parseTarget(targetPath): try: with open(targetPath, 'r') as targetFile: js = json.load(targetFile) target = zspdx.cmakefileapi.Target() target.name = js.get("name", "") target.id = js.get("id", "") target.type = parseTargetType(js.get("type", "UNKNOWN")) target.backtrace = js.get("backtrace", -1) target.folder = js.get("folder", "") # get paths paths_dict = js.get("paths", {}) target.paths_source = paths_dict.get("source", "") target.paths_build = paths_dict.get("build", "") target.nameOnDisk = js.get("nameOnDisk", "") # parse artifacts if present artifacts_arr = js.get("artifacts", []) target.artifacts = [] for artifact_dict in artifacts_arr: artifact_path = artifact_dict.get("path", "") if artifact_path != "": target.artifacts.append(artifact_path) target.isGeneratorProvided = js.get("isGeneratorProvided", False) # call separate functions to parse subsections parseTargetInstall(target, js) parseTargetLink(target, js) parseTargetArchive(target, js) parseTargetDependencies(target, js) parseTargetSources(target, js) parseTargetSourceGroups(target, js) parseTargetCompileGroups(target, js) parseTargetBacktraceGraph(target, js) return target except OSError as e: log.err(f"Error loading {targetPath}: {str(e)}") return None except json.decoder.JSONDecodeError as e: log.err(f"Error parsing JSON in {targetPath}: {str(e)}") return None def parseTargetType(targetType): if targetType == "EXECUTABLE": return zspdx.cmakefileapi.TargetType.EXECUTABLE elif targetType == "STATIC_LIBRARY": return zspdx.cmakefileapi.TargetType.STATIC_LIBRARY elif targetType == "SHARED_LIBRARY": return zspdx.cmakefileapi.TargetType.SHARED_LIBRARY elif targetType == "MODULE_LIBRARY": return zspdx.cmakefileapi.TargetType.MODULE_LIBRARY elif targetType == "OBJECT_LIBRARY": return zspdx.cmakefileapi.TargetType.OBJECT_LIBRARY elif targetType == "UTILITY": return zspdx.cmakefileapi.TargetType.UTILITY else: return zspdx.cmakefileapi.TargetType.UNKNOWN def parseTargetInstall(target, js): install_dict = js.get("install", {}) if install_dict == {}: return prefix_dict = install_dict.get("prefix", {}) target.install_prefix = prefix_dict.get("path", "") destinations_arr = install_dict.get("destinations", []) for destination_dict in destinations_arr: dest = zspdx.cmakefileapi.TargetInstallDestination() dest.path = destination_dict.get("path", "") dest.backtrace = destination_dict.get("backtrace", -1) target.install_destinations.append(dest) def parseTargetLink(target, js): link_dict = js.get("link", {}) if link_dict == {}: return target.link_language = link_dict.get("language", {}) target.link_lto = link_dict.get("lto", False) sysroot_dict = link_dict.get("sysroot", {}) target.link_sysroot = sysroot_dict.get("path", "") fragments_arr = link_dict.get("commandFragments", []) for fragment_dict in fragments_arr: fragment = zspdx.cmakefileapi.TargetCommandFragment() fragment.fragment = fragment_dict.get("fragment", "") fragment.role = fragment_dict.get("role", "") target.link_commandFragments.append(fragment) def parseTargetArchive(target, js): archive_dict = js.get("archive", {}) if archive_dict == {}: return target.archive_lto = archive_dict.get("lto", False) fragments_arr = archive_dict.get("commandFragments", []) for fragment_dict in fragments_arr: fragment = zspdx.cmakefileapi.TargetCommandFragment() fragment.fragment = fragment_dict.get("fragment", "") fragment.role = fragment_dict.get("role", "") target.archive_commandFragments.append(fragment) def parseTargetDependencies(target, js): dependencies_arr = js.get("dependencies", []) for dependency_dict in dependencies_arr: dep = zspdx.cmakefileapi.TargetDependency() dep.id = dependency_dict.get("id", "") dep.backtrace = dependency_dict.get("backtrace", -1) target.dependencies.append(dep) def parseTargetSources(target, js): sources_arr = js.get("sources", []) for source_dict in sources_arr: src = zspdx.cmakefileapi.TargetSource() src.path = source_dict.get("path", "") src.compileGroupIndex = source_dict.get("compileGroupIndex", -1) src.sourceGroupIndex = source_dict.get("sourceGroupIndex", -1) src.isGenerated = source_dict.get("isGenerated", False) src.backtrace = source_dict.get("backtrace", -1) target.sources.append(src) def parseTargetSourceGroups(target, js): sourceGroups_arr = js.get("sourceGroups", []) for sourceGroup_dict in sourceGroups_arr: srcgrp = zspdx.cmakefileapi.TargetSourceGroup() srcgrp.name = sourceGroup_dict.get("name", "") srcgrp.sourceIndexes = sourceGroup_dict.get("sourceIndexes", []) target.sourceGroups.append(srcgrp) def parseTargetCompileGroups(target, js): compileGroups_arr = js.get("compileGroups", []) for compileGroup_dict in compileGroups_arr: cmpgrp = zspdx.cmakefileapi.TargetCompileGroup() cmpgrp.sourceIndexes = compileGroup_dict.get("sourceIndexes", []) cmpgrp.language = compileGroup_dict.get("language", "") cmpgrp.sysroot = compileGroup_dict.get("sysroot", "") commandFragments_arr = compileGroup_dict.get("compileCommandFragments", []) for commandFragment_dict in commandFragments_arr: fragment = commandFragment_dict.get("fragment", "") if fragment != "": cmpgrp.compileCommandFragments.append(fragment) includes_arr = compileGroup_dict.get("includes", []) for include_dict in includes_arr: grpInclude = zspdx.cmakefileapi.TargetCompileGroupInclude() grpInclude.path = include_dict.get("path", "") grpInclude.isSystem = include_dict.get("isSystem", False) grpInclude.backtrace = include_dict.get("backtrace", -1) cmpgrp.includes.append(grpInclude) precompileHeaders_arr = compileGroup_dict.get("precompileHeaders", []) for precompileHeader_dict in precompileHeaders_arr: grpHeader = zspdx.cmakefileapi.TargetCompileGroupPrecompileHeader() grpHeader.header = precompileHeader_dict.get("header", "") grpHeader.backtrace = precompileHeader_dict.get("backtrace", -1) cmpgrp.precompileHeaders.append(grpHeader) defines_arr = compileGroup_dict.get("defines", []) for define_dict in defines_arr: grpDefine = zspdx.cmakefileapi.TargetCompileGroupDefine() grpDefine.define = define_dict.get("define", "") grpDefine.backtrace = define_dict.get("backtrace", -1) cmpgrp.defines.append(grpDefine) target.compileGroups.append(cmpgrp) def parseTargetBacktraceGraph(target, js): backtraceGraph_dict = js.get("backtraceGraph", {}) if backtraceGraph_dict == {}: return target.backtraceGraph_commands = backtraceGraph_dict.get("commands", []) target.backtraceGraph_files = backtraceGraph_dict.get("files", []) nodes_arr = backtraceGraph_dict.get("nodes", []) for node_dict in nodes_arr: node = zspdx.cmakefileapi.TargetBacktraceGraphNode() node.file = node_dict.get("file", -1) node.line = node_dict.get("line", -1) node.command = node_dict.get("command", -1) node.parent = node_dict.get("parent", -1) target.backtraceGraph_nodes.append(node) # Create direct pointers for all Configs in Codemodel # takes: Codemodel def linkCodemodel(cm): for cfg in cm.configurations: linkConfig(cfg) # Create direct pointers for all contents of Config # takes: Config def linkConfig(cfg): for cfgDir in cfg.directories: linkConfigDir(cfg, cfgDir) for cfgPrj in cfg.projects: linkConfigProject(cfg, cfgPrj) for cfgTarget in cfg.configTargets: linkConfigTarget(cfg, cfgTarget) # Create direct pointers for ConfigDir indices # takes: Config and ConfigDir def linkConfigDir(cfg, cfgDir): if cfgDir.parentIndex == -1: cfgDir.parent = None else: cfgDir.parent = cfg.directories[cfgDir.parentIndex] if cfgDir.projectIndex == -1: cfgDir.project = None else: cfgDir.project = cfg.projects[cfgDir.projectIndex] cfgDir.children = [] for childIndex in cfgDir.childIndexes: cfgDir.children.append(cfg.directories[childIndex]) cfgDir.targets = [] for targetIndex in cfgDir.targetIndexes: cfgDir.targets.append(cfg.configTargets[targetIndex]) # Create direct pointers for ConfigProject indices # takes: Config and ConfigProject def linkConfigProject(cfg, cfgPrj): if cfgPrj.parentIndex == -1: cfgPrj.parent = None else: cfgPrj.parent = cfg.projects[cfgPrj.parentIndex] cfgPrj.children = [] for childIndex in cfgPrj.childIndexes: cfgPrj.children.append(cfg.projects[childIndex]) cfgPrj.directories = [] for dirIndex in cfgPrj.directoryIndexes: cfgPrj.directories.append(cfg.directories[dirIndex]) cfgPrj.targets = [] for targetIndex in cfgPrj.targetIndexes: cfgPrj.targets.append(cfg.configTargets[targetIndex]) # Create direct pointers for ConfigTarget indices # takes: Config and ConfigTarget def linkConfigTarget(cfg, cfgTarget): if cfgTarget.directoryIndex == -1: cfgTarget.directory = None else: cfgTarget.directory = cfg.directories[cfgTarget.directoryIndex] if cfgTarget.projectIndex == -1: cfgTarget.project = None else: cfgTarget.project = cfg.projects[cfgTarget.projectIndex] # and link target's sources and source groups for ts in cfgTarget.target.sources: linkTargetSource(cfgTarget.target, ts) for tsg in cfgTarget.target.sourceGroups: linkTargetSourceGroup(cfgTarget.target, tsg) for tcg in cfgTarget.target.compileGroups: linkTargetCompileGroup(cfgTarget.target, tcg) # Create direct pointers for TargetSource indices # takes: Target and TargetSource def linkTargetSource(target, targetSrc): if targetSrc.compileGroupIndex == -1: targetSrc.compileGroup = None else: targetSrc.compileGroup = target.compileGroups[targetSrc.compileGroupIndex] if targetSrc.sourceGroupIndex == -1: targetSrc.sourceGroup = None else: targetSrc.sourceGroup = target.sourceGroups[targetSrc.sourceGroupIndex] # Create direct pointers for TargetSourceGroup indices # takes: Target and TargetSourceGroup def linkTargetSourceGroup(target, targetSrcGrp): targetSrcGrp.sources = [] for srcIndex in targetSrcGrp.sourceIndexes: targetSrcGrp.sources.append(target.sources[srcIndex]) # Create direct pointers for TargetCompileGroup indices # takes: Target and TargetCompileGroup def linkTargetCompileGroup(target, targetCmpGrp): targetCmpGrp.sources = [] for srcIndex in targetCmpGrp.sourceIndexes: targetCmpGrp.sources.append(target.sources[srcIndex])