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