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