1#!/usr/bin/env python3 2 3# Copyright (c) 2023 Nordic Semiconductor ASA 4# SPDX-License-Identifier: Apache-2.0 5 6import argparse 7import re 8import sys 9from dataclasses import dataclass 10from pathlib import Path, PurePath 11 12import pykwalify.core 13import yaml 14 15try: 16 from yaml import CSafeLoader as SafeLoader 17except ImportError: 18 from yaml import SafeLoader 19 20 21SOC_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'soc-schema.yml') 22with open(SOC_SCHEMA_PATH) as f: 23 soc_schema = yaml.load(f.read(), Loader=SafeLoader) 24 25SOC_VALIDATOR = pykwalify.core.Core(schema_data=soc_schema, source_data={}) 26 27ARCH_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'arch-schema.yml') 28with open(ARCH_SCHEMA_PATH) as f: 29 arch_schema = yaml.load(f.read(), Loader=SafeLoader) 30 31ARCH_VALIDATOR = pykwalify.core.Core(schema_data=arch_schema, source_data={}) 32 33SOC_YML = 'soc.yml' 34ARCHS_YML_PATH = PurePath('arch/archs.yml') 35 36class Systems: 37 38 def __init__(self, folder='', soc_yaml=None): 39 self._socs = [] 40 self._series = [] 41 self._families = [] 42 self._extended_socs = [] 43 44 if soc_yaml is None: 45 return 46 47 try: 48 data = yaml.load(soc_yaml, Loader=SafeLoader) 49 SOC_VALIDATOR.source = data 50 SOC_VALIDATOR.validate() 51 except (yaml.YAMLError, pykwalify.errors.SchemaError) as e: 52 sys.exit(f'ERROR: Malformed yaml {soc_yaml.as_posix()}', e) 53 54 for f in data.get('family', []): 55 family = Family(f['name'], [folder], [], []) 56 for s in f.get('series', []): 57 series = Series(s['name'], [folder], f['name'], []) 58 socs = [(Soc(soc['name'], 59 [c['name'] for c in soc.get('cpuclusters', [])], 60 [folder], s['name'], f['name'])) 61 for soc in s.get('socs', [])] 62 series.socs.extend(socs) 63 self._series.append(series) 64 self._socs.extend(socs) 65 family.series.append(series) 66 family.socs.extend(socs) 67 socs = [(Soc(soc['name'], 68 [c['name'] for c in soc.get('cpuclusters', [])], 69 [folder], None, f['name'])) 70 for soc in f.get('socs', [])] 71 self._socs.extend(socs) 72 self._families.append(family) 73 74 for s in data.get('series', []): 75 series = Series(s['name'], [folder], '', []) 76 socs = [(Soc(soc['name'], 77 [c['name'] for c in soc.get('cpuclusters', [])], 78 [folder], s['name'], '')) 79 for soc in s.get('socs', [])] 80 series.socs.extend(socs) 81 self._series.append(series) 82 self._socs.extend(socs) 83 84 for soc in data.get('socs', []): 85 mutual_exclusive = {'name', 'extend'} 86 if len(mutual_exclusive - soc.keys()) < 1: 87 sys.exit(f'ERROR: Malformed content in SoC file: {soc_yaml}\n' 88 f'{mutual_exclusive} are mutual exclusive at this level.') 89 if soc.get('name') is not None: 90 self._socs.append(Soc(soc['name'], [c['name'] for c in soc.get('cpuclusters', [])], 91 [folder], '', '')) 92 elif soc.get('extend') is not None: 93 self._extended_socs.append(Soc(soc['extend'], 94 [c['name'] for c in soc.get('cpuclusters', [])], 95 [folder], '', '')) 96 else: 97 sys.exit(f'ERROR: Malformed "socs" section in SoC file: {soc_yaml}\n' 98 f'Cannot find one of required keys {mutual_exclusive}.') 99 100 # Ensure that any runner configuration matches socs and cpuclusters declared in the same 101 # soc.yml file 102 if 'runners' in data and 'run_once' in data['runners']: 103 for grp in data['runners']['run_once']: 104 for item_data in data['runners']['run_once'][grp]: 105 for group in item_data['groups']: 106 for qualifiers in group['qualifiers']: 107 soc_name = qualifiers.split('/')[0] 108 found_match = False 109 110 for soc in self._socs + self._extended_socs: 111 if re.match(fr'^{soc_name}$', soc.name) is not None: 112 found_match = True 113 break 114 115 if found_match is False: 116 sys.exit(f'ERROR: SoC qualifier match unresolved: {qualifiers}') 117 118 @staticmethod 119 def from_file(socs_file): 120 '''Load SoCs from a soc.yml file. 121 ''' 122 try: 123 with open(socs_file) as f: 124 socs_yaml = f.read() 125 except FileNotFoundError as e: 126 sys.exit(f'ERROR: socs.yml file not found: {socs_file.as_posix()}', e) 127 128 return Systems(str(socs_file.parent), socs_yaml) 129 130 @staticmethod 131 def from_yaml(socs_yaml): 132 '''Load socs from a string with YAML contents. 133 ''' 134 return Systems('', socs_yaml) 135 136 def extend(self, systems): 137 self._families.extend(systems.get_families()) 138 self._series.extend(systems.get_series()) 139 140 for es in self._extended_socs[:]: 141 for s in systems.get_socs(): 142 if s.name == es.name: 143 s.extend(es) 144 self._extended_socs.remove(es) 145 break 146 self._socs.extend(systems.get_socs()) 147 148 for es in systems.get_extended_socs(): 149 for s in self._socs: 150 if s.name == es.name: 151 s.extend(es) 152 break 153 else: 154 self._extended_socs.append(es) 155 156 def get_families(self): 157 return self._families 158 159 def get_series(self): 160 return self._series 161 162 def get_socs(self): 163 return self._socs 164 165 def get_extended_socs(self): 166 return self._extended_socs 167 168 def get_soc(self, name): 169 try: 170 return next(s for s in self._socs if s.name == name) 171 except StopIteration: 172 sys.exit(f"ERROR: SoC '{name}' is not found, please ensure that the SoC exists " 173 f"and that soc-root containing '{name}' has been correctly defined.") 174 175 176@dataclass 177class Soc: 178 name: str 179 cpuclusters: list[str] 180 folder: list[str] 181 series: str = '' 182 family: str = '' 183 184 def extend(self, soc): 185 if self.name == soc.name: 186 self.cpuclusters.extend(soc.cpuclusters) 187 self.folder.extend(soc.folder) 188 189 190@dataclass 191class Series: 192 name: str 193 folder: list[str] 194 family: str 195 socs: list[Soc] 196 197 198@dataclass 199class Family: 200 name: str 201 folder: list[str] 202 series: list[Series] 203 socs: list[Soc] 204 205 206def unique_paths(paths): 207 # Using dict keys ensures both uniqueness and a deterministic order. 208 yield from dict.fromkeys(map(Path.resolve, paths)).keys() 209 210 211def find_v2_archs(args): 212 ret = {'archs': []} 213 for root in unique_paths(args.arch_roots): 214 archs_yml = root / ARCHS_YML_PATH 215 216 if Path(archs_yml).is_file(): 217 with Path(archs_yml).open('r', encoding='utf-8') as f: 218 archs = yaml.load(f.read(), Loader=SafeLoader) 219 220 try: 221 ARCH_VALIDATOR.source = archs 222 ARCH_VALIDATOR.validate() 223 except pykwalify.errors.SchemaError as e: 224 sys.exit(f'ERROR: Malformed "build" section in file: {archs_yml.as_posix()}\n{e}') 225 226 if args.arch is not None: 227 archs = {'archs': list(filter( 228 lambda arch: arch.get('name') == args.arch, archs['archs']))} 229 for arch in archs['archs']: 230 arch.update({'path': root / 'arch' / arch['path']}) 231 arch.update({'hwm': 'v2'}) 232 arch.update({'type': 'arch'}) 233 234 ret['archs'].extend(archs['archs']) 235 236 return ret 237 238 239def find_v2_systems(args): 240 yml_files = [] 241 systems = Systems() 242 for root in unique_paths(args.soc_roots): 243 yml_files.extend(sorted((root / 'soc').rglob(SOC_YML))) 244 245 for soc_yml in yml_files: 246 if soc_yml.is_file(): 247 systems.extend(Systems.from_file(soc_yml)) 248 249 return systems 250 251 252def parse_args(): 253 parser = argparse.ArgumentParser(allow_abbrev=False) 254 add_args(parser) 255 return parser.parse_args() 256 257 258def add_args(parser): 259 default_fmt = '{name}' 260 261 parser.add_argument("--soc-root", dest='soc_roots', default=[], 262 type=Path, action='append', 263 help='add a SoC root, may be given more than once') 264 parser.add_argument("--soc", default=None, help='lookup the specific soc') 265 parser.add_argument("--soc-series", default=None, help='lookup the specific soc series') 266 parser.add_argument("--soc-family", default=None, help='lookup the specific family') 267 parser.add_argument("--socs", action='store_true', help='lookup all socs') 268 parser.add_argument("--arch-root", dest='arch_roots', default=[], 269 type=Path, action='append', 270 help='add a arch root, may be given more than once') 271 parser.add_argument("--arch", default=None, help='lookup the specific arch') 272 parser.add_argument("--archs", action='store_true', help='lookup all archs') 273 parser.add_argument("--format", default=default_fmt, 274 help='''Format string to use to list each soc.''') 275 parser.add_argument("--cmakeformat", default=None, 276 help='''CMake format string to use to list each arch/soc.''') 277 278 279def dump_v2_archs(args): 280 archs = find_v2_archs(args) 281 282 for arch in archs['archs']: 283 if args.cmakeformat is not None: 284 info = args.cmakeformat.format( 285 TYPE='TYPE;' + arch['type'], 286 NAME='NAME;' + arch['name'], 287 DIR='DIR;' + str(arch['path'].as_posix()), 288 HWM='HWM;' + arch['hwm'], 289 # Below is non exising for arch but is defined here to support 290 # common formatting string. 291 SERIES='', 292 FAMILY='', 293 ARCH='', 294 VENDOR='' 295 ) 296 else: 297 info = args.format.format( 298 type=arch.get('type'), 299 name=arch.get('name'), 300 dir=arch.get('path'), 301 hwm=arch.get('hwm'), 302 # Below is non exising for arch but is defined here to support 303 # common formatting string. 304 series='', 305 family='', 306 arch='', 307 vendor='' 308 ) 309 310 print(info) 311 312 313def dump_v2_system(args, type, system): 314 if args.cmakeformat is not None: 315 info = args.cmakeformat.format( 316 TYPE='TYPE;' + type, 317 NAME='NAME;' + system.name, 318 DIR='DIR;' + ';'.join([Path(x).as_posix() for x in system.folder]), 319 HWM='HWM;' + 'v2' 320 ) 321 else: 322 info = args.format.format( 323 type=type, 324 name=system.name, 325 dir=system.folder, 326 hwm='v2' 327 ) 328 329 print(info) 330 331 332def dump_v2_systems(args): 333 systems = find_v2_systems(args) 334 335 for f in systems.get_families(): 336 dump_v2_system(args, 'family', f) 337 338 for s in systems.get_series(): 339 dump_v2_system(args, 'series', s) 340 341 for s in systems.get_socs(): 342 dump_v2_system(args, 'soc', s) 343 344 345if __name__ == '__main__': 346 args = parse_args() 347 if any([args.socs, args.soc, args.soc_series, args.soc_family]): 348 dump_v2_systems(args) 349 if args.archs or args.arch is not None: 350 dump_v2_archs(args) 351