blob: 54c8de5a929e91cf14116963523eb0075cefa0d6 [file]
#!/usr/bin/env python3
#
# Copyright (c) 2023 Bjarki Arge Andreasen
#
# SPDX-License-Identifier: Apache-2.0
"""
Script to generate iterable sections from JSON encoded dictionary containing lists of items.
Supports API class inheritance: when a child API struct embeds a parent API struct as its
first member (declared via DEVICE_API_EXTENDS), the child's linker section is nested inside
the parent's address range. This allows DEVICE_API_IS(parent_class, dev) to return true for
devices with a child API. Multi-level inheritance is supported.
All device API entries are placed in a single output section. Per-class start/end symbols
and _ext_end symbols are used for runtime range checks and iteration.
"""
import argparse
import json
def parse_tagged_items(filepath: str, tag: str):
"""Parse tagged items from JSON. Each entry is either a string or an object
with "name" and "extends" keys.
Returns:
items: list of all item names (strings)
parent_children: dict mapping parent item to sorted list of child items
children: set of items that are children
"""
with open(filepath) as fp:
raw = json.load(fp)[tag]
items = []
parent_children = {}
children = set()
for entry in raw:
if isinstance(entry, str):
items.append(entry)
else:
name = entry["name"]
parent = entry["extends"]
items.append(name)
parent_children.setdefault(parent, []).append(name)
children.add(name)
# Sort children for deterministic output
for parent in parent_children:
parent_children[parent].sort()
return items, parent_children, children
def walk_tree_ld(fp, item, parent_children, indent="\t"):
"""Depth-first walk emitting Z_LINK_ITERABLE + _ext_end for item and descendants."""
fp.write(f"{indent}Z_LINK_ITERABLE({item});\n")
for child in parent_children.get(item, []):
walk_tree_ld(fp, child, parent_children, indent)
fp.write(f"{indent}PLACE_SYMBOL_HERE(_{item}_ext_end);\n")
def walk_tree_cmake(fp, item, parent_children, section_name, prio):
"""Depth-first walk emitting section_settings entries for item and descendants.
Returns the next available prio value.
"""
# This item's input entries
fp.write(
f'list(APPEND section_settings "{{SECTION\\;{section_name}\\;'
+ 'SORT\\;NAME\\;'
+ 'KEEP\\;TRUE\\;'
+ f'INPUT\\;._{item}.static.*\\;'
+ f'SYMBOLS\\;_{item}_list_start\\;_{item}_list_end\\;'
+ f'PRIO\\;{prio}}}")\n'
)
prio += 1
# Recurse into children
for child in parent_children.get(item, []):
prio = walk_tree_cmake(fp, child, parent_children, section_name, prio)
# _ext_end symbol after all descendants
fp.write(
f'list(APPEND section_settings "{{SECTION\\;{section_name}\\;'
+ f'SYMBOLS\\;_{item}_ext_end\\;'
+ f'PRIO\\;{prio}}}")\n'
)
prio += 1
return prio
def gen_ld(filepath: str, items: list, alignment: str, parent_children: dict, children: set):
with open(filepath, "w") as fp:
fp.write(f"SECTION_PROLOGUE(device_api_area,,SUBALIGN({alignment}))\n")
fp.write("{\n")
for item in items:
if item in children:
continue
walk_tree_ld(fp, item, parent_children)
fp.write("} GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)\n")
def gen_cmake(filepath: str, items: list, alignment: str, parent_children: dict, children: set):
with open(filepath, "w") as fp:
section_name = "device_api_area"
fp.write(
f'list(APPEND sections "{{NAME\\;{section_name}\\;'
+ 'GROUP\\;RODATA_REGION\\;'
+ f'SUBALIGN\\;{alignment}\\;'
+ 'NOINPUT\\;TRUE}")\n'
)
prio = 100
for item in items:
if item in children:
continue
prio = walk_tree_cmake(fp, item, parent_children, section_name, prio)
fp.write('set(DEVICE_API_SECTIONS "${sections}" CACHE INTERNAL "")\n')
fp.write('set(DEVICE_API_SECTION_SETTINGS "${section_settings}" CACHE INTERNAL "")\n')
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter,
allow_abbrev=False,
)
parser.add_argument("-i", "--input", required=True, help="Path to input list of tags")
parser.add_argument("-a", "--alignment", required=True, help="Iterable section alignment")
parser.add_argument("-t", "--tag", required=True, help="Tag to generate iterable sections for")
parser.add_argument("-l", "--ld-output", required=True, help="Path to output linker file")
parser.add_argument(
"-c", "--cmake-output", required=True, help="Path to CMake linker script inclusion file"
)
return parser.parse_args()
def main():
args = parse_args()
items, parent_children, children = parse_tagged_items(args.input, args.tag)
gen_ld(args.ld_output, items, args.alignment, parent_children, children)
gen_cmake(args.cmake_output, items, args.alignment, parent_children, children)
if __name__ == "__main__":
main()