zephyr/scripts/west_commands/zspdx/cmakefileapijson.py

437 lines
17 KiB
Python

# 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])