1#!/usr/bin/env python3 2# 3# Copyright (c) 2019, Nordic Semiconductor ASA 4# 5# SPDX-License-Identifier: Apache-2.0 6 7'''Tool for parsing a list of projects to determine if they are Zephyr 8projects. If no projects are given then the output from `west list` will be 9used as project list. 10 11Include file is generated for Kconfig using --kconfig-out. 12A <name>:<path> text file is generated for use with CMake using --cmake-out. 13 14Using --twister-out <filename> an argument file for twister script will 15be generated which would point to test and sample roots available in modules 16that can be included during a twister run. This allows testing code 17maintained in modules in addition to what is available in the main Zephyr tree. 18''' 19 20import argparse 21import hashlib 22import os 23import re 24import subprocess 25import sys 26import yaml 27import pykwalify.core 28from pathlib import Path, PurePath 29from collections import namedtuple 30 31try: 32 from yaml import CSafeLoader as SafeLoader 33except ImportError: 34 from yaml import SafeLoader 35 36METADATA_SCHEMA = ''' 37## A pykwalify schema for basic validation of the structure of a 38## metadata YAML file. 39## 40# The zephyr/module.yml file is a simple list of key value pairs to be used by 41# the build system. 42type: map 43mapping: 44 name: 45 required: false 46 type: str 47 build: 48 required: false 49 type: map 50 mapping: 51 cmake: 52 required: false 53 type: str 54 kconfig: 55 required: false 56 type: str 57 cmake-ext: 58 required: false 59 type: bool 60 default: false 61 kconfig-ext: 62 required: false 63 type: bool 64 default: false 65 sysbuild-cmake: 66 required: false 67 type: str 68 sysbuild-kconfig: 69 required: false 70 type: str 71 sysbuild-cmake-ext: 72 required: false 73 type: bool 74 default: false 75 sysbuild-kconfig-ext: 76 required: false 77 type: bool 78 default: false 79 depends: 80 required: false 81 type: seq 82 sequence: 83 - type: str 84 settings: 85 required: false 86 type: map 87 mapping: 88 board_root: 89 required: false 90 type: str 91 dts_root: 92 required: false 93 type: str 94 snippet_root: 95 required: false 96 type: str 97 soc_root: 98 required: false 99 type: str 100 arch_root: 101 required: false 102 type: str 103 module_ext_root: 104 required: false 105 type: str 106 sca_root: 107 required: false 108 type: str 109 tests: 110 required: false 111 type: seq 112 sequence: 113 - type: str 114 samples: 115 required: false 116 type: seq 117 sequence: 118 - type: str 119 boards: 120 required: false 121 type: seq 122 sequence: 123 - type: str 124 blobs: 125 required: false 126 type: seq 127 sequence: 128 - type: map 129 mapping: 130 path: 131 required: true 132 type: str 133 sha256: 134 required: true 135 type: str 136 type: 137 required: true 138 type: str 139 enum: ['img', 'lib'] 140 version: 141 required: true 142 type: str 143 license-path: 144 required: true 145 type: str 146 url: 147 required: true 148 type: str 149 description: 150 required: true 151 type: str 152 doc-url: 153 required: false 154 type: str 155 security: 156 required: false 157 type: map 158 mapping: 159 external-references: 160 required: false 161 type: seq 162 sequence: 163 - type: str 164''' 165 166MODULE_YML_PATH = PurePath('zephyr/module.yml') 167# Path to the blobs folder 168MODULE_BLOBS_PATH = PurePath('zephyr/blobs') 169BLOB_PRESENT = 'A' 170BLOB_NOT_PRESENT = 'D' 171BLOB_OUTDATED = 'M' 172 173schema = yaml.load(METADATA_SCHEMA, Loader=SafeLoader) 174 175 176def validate_setting(setting, module_path, filename=None): 177 if setting is not None: 178 if filename is not None: 179 checkfile = Path(module_path) / setting / filename 180 else: 181 checkfile = Path(module_path) / setting 182 if not checkfile.resolve().is_file(): 183 return False 184 return True 185 186 187def process_module(module): 188 module_path = PurePath(module) 189 190 # The input is a module if zephyr/module.{yml,yaml} is a valid yaml file 191 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present. 192 193 for module_yml in [module_path / MODULE_YML_PATH, 194 module_path / MODULE_YML_PATH.with_suffix('.yaml')]: 195 if Path(module_yml).is_file(): 196 with Path(module_yml).open('r') as f: 197 meta = yaml.load(f.read(), Loader=SafeLoader) 198 199 try: 200 pykwalify.core.Core(source_data=meta, schema_data=schema)\ 201 .validate() 202 except pykwalify.errors.SchemaError as e: 203 sys.exit('ERROR: Malformed "build" section in file: {}\n{}' 204 .format(module_yml.as_posix(), e)) 205 206 meta['name'] = meta.get('name', module_path.name) 207 meta['name-sanitized'] = re.sub('[^a-zA-Z0-9]', '_', meta['name']) 208 return meta 209 210 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \ 211 Path(module_path.joinpath('zephyr/Kconfig')).is_file(): 212 return {'name': module_path.name, 213 'name-sanitized': re.sub('[^a-zA-Z0-9]', '_', module_path.name), 214 'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}} 215 216 return None 217 218 219def process_cmake(module, meta): 220 section = meta.get('build', dict()) 221 module_path = PurePath(module) 222 module_yml = module_path.joinpath('zephyr/module.yml') 223 224 cmake_extern = section.get('cmake-ext', False) 225 if cmake_extern: 226 return('\"{}\":\"{}\":\"{}\"\n' 227 .format(meta['name'], 228 module_path.as_posix(), 229 "${ZEPHYR_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}")) 230 231 cmake_setting = section.get('cmake', None) 232 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'): 233 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which ' 234 'does not contain a CMakeLists.txt file.' 235 .format(module_yml.as_posix(), cmake_setting)) 236 237 cmake_path = os.path.join(module, cmake_setting or 'zephyr') 238 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt') 239 if os.path.isfile(cmake_file): 240 return('\"{}\":\"{}\":\"{}\"\n' 241 .format(meta['name'], 242 module_path.as_posix(), 243 Path(cmake_path).resolve().as_posix())) 244 else: 245 return('\"{}\":\"{}\":\"\"\n' 246 .format(meta['name'], 247 module_path.as_posix())) 248 249 250def process_sysbuildcmake(module, meta): 251 section = meta.get('build', dict()) 252 module_path = PurePath(module) 253 module_yml = module_path.joinpath('zephyr/module.yml') 254 255 cmake_extern = section.get('sysbuild-cmake-ext', False) 256 if cmake_extern: 257 return('\"{}\":\"{}\":\"{}\"\n' 258 .format(meta['name'], 259 module_path.as_posix(), 260 "${SYSBUILD_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}")) 261 262 cmake_setting = section.get('sysbuild-cmake', None) 263 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'): 264 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which ' 265 'does not contain a CMakeLists.txt file.' 266 .format(module_yml.as_posix(), cmake_setting)) 267 268 if cmake_setting is None: 269 return "" 270 271 cmake_path = os.path.join(module, cmake_setting or 'zephyr') 272 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt') 273 if os.path.isfile(cmake_file): 274 return('\"{}\":\"{}\":\"{}\"\n' 275 .format(meta['name'], 276 module_path.as_posix(), 277 Path(cmake_path).resolve().as_posix())) 278 else: 279 return('\"{}\":\"{}\":\"\"\n' 280 .format(meta['name'], 281 module_path.as_posix())) 282 283 284def process_settings(module, meta): 285 section = meta.get('build', dict()) 286 build_settings = section.get('settings', None) 287 out_text = "" 288 289 if build_settings is not None: 290 for root in ['board', 'dts', 'snippet', 'soc', 'arch', 'module_ext', 'sca']: 291 setting = build_settings.get(root+'_root', None) 292 if setting is not None: 293 root_path = PurePath(module) / setting 294 out_text += f'"{root.upper()}_ROOT":' 295 out_text += f'"{root_path.as_posix()}"\n' 296 297 return out_text 298 299 300def get_blob_status(path, sha256): 301 if not path.is_file(): 302 return BLOB_NOT_PRESENT 303 with path.open('rb') as f: 304 m = hashlib.sha256() 305 m.update(f.read()) 306 if sha256.lower() == m.hexdigest(): 307 return BLOB_PRESENT 308 else: 309 return BLOB_OUTDATED 310 311 312def process_blobs(module, meta): 313 blobs = [] 314 mblobs = meta.get('blobs', None) 315 if not mblobs: 316 return blobs 317 318 blobs_path = Path(module) / MODULE_BLOBS_PATH 319 for blob in mblobs: 320 blob['module'] = meta.get('name', None) 321 blob['abspath'] = blobs_path / Path(blob['path']) 322 blob['status'] = get_blob_status(blob['abspath'], blob['sha256']) 323 blobs.append(blob) 324 325 return blobs 326 327 328def kconfig_snippet(meta, path, kconfig_file=None, blobs=False, sysbuild=False): 329 name = meta['name'] 330 name_sanitized = meta['name-sanitized'] 331 332 snippet = [f'menu "{name} ({path.as_posix()})"', 333 f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file 334 else f'osource "$(SYSBUILD_{name_sanitized.upper()}_KCONFIG)"' if sysbuild is True 335 else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"', 336 f'config ZEPHYR_{name_sanitized.upper()}_MODULE', 337 ' bool', 338 ' default y', 339 'endmenu\n'] 340 341 if blobs: 342 snippet.insert(-1, ' select TAINT_BLOBS') 343 return '\n'.join(snippet) 344 345 346def process_kconfig(module, meta): 347 blobs = process_blobs(module, meta) 348 taint_blobs = any(b['status'] != BLOB_NOT_PRESENT for b in blobs) 349 section = meta.get('build', dict()) 350 module_path = PurePath(module) 351 module_yml = module_path.joinpath('zephyr/module.yml') 352 kconfig_extern = section.get('kconfig-ext', False) 353 if kconfig_extern: 354 return kconfig_snippet(meta, module_path, blobs=taint_blobs) 355 356 kconfig_setting = section.get('kconfig', None) 357 if not validate_setting(kconfig_setting, module): 358 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does ' 359 'not point to a valid Kconfig file.' 360 .format(module_yml, kconfig_setting)) 361 362 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig') 363 if os.path.isfile(kconfig_file): 364 return kconfig_snippet(meta, module_path, Path(kconfig_file), 365 blobs=taint_blobs) 366 else: 367 name_sanitized = meta['name-sanitized'] 368 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n' 369 f' bool\n' 370 f' default y\n') 371 372 373def process_sysbuildkconfig(module, meta): 374 section = meta.get('build', dict()) 375 module_path = PurePath(module) 376 module_yml = module_path.joinpath('zephyr/module.yml') 377 kconfig_extern = section.get('sysbuild-kconfig-ext', False) 378 if kconfig_extern: 379 return kconfig_snippet(meta, module_path, sysbuild=True) 380 381 kconfig_setting = section.get('sysbuild-kconfig', None) 382 if not validate_setting(kconfig_setting, module): 383 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does ' 384 'not point to a valid Kconfig file.' 385 .format(module_yml, kconfig_setting)) 386 387 if kconfig_setting is not None: 388 kconfig_file = os.path.join(module, kconfig_setting) 389 if os.path.isfile(kconfig_file): 390 return kconfig_snippet(meta, module_path, Path(kconfig_file)) 391 392 name_sanitized = meta['name-sanitized'] 393 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n' 394 f' bool\n' 395 f' default y\n') 396 397 398def process_twister(module, meta): 399 400 out = "" 401 tests = meta.get('tests', []) 402 samples = meta.get('samples', []) 403 boards = meta.get('boards', []) 404 405 for pth in tests + samples: 406 if pth: 407 dir = os.path.join(module, pth) 408 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir)) 409 .as_posix()) 410 411 for pth in boards: 412 if pth: 413 dir = os.path.join(module, pth) 414 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir)) 415 .as_posix()) 416 417 return out 418 419 420def _create_meta_project(project_path): 421 def git_revision(path): 422 rc = subprocess.Popen(['git', 'rev-parse', '--is-inside-work-tree'], 423 stdout=subprocess.PIPE, 424 stderr=subprocess.PIPE, 425 cwd=path).wait() 426 if rc == 0: 427 # A git repo. 428 popen = subprocess.Popen(['git', 'rev-parse', 'HEAD'], 429 stdout=subprocess.PIPE, 430 stderr=subprocess.PIPE, 431 cwd=path) 432 stdout, stderr = popen.communicate() 433 stdout = stdout.decode('utf-8') 434 435 if not (popen.returncode or stderr): 436 revision = stdout.rstrip() 437 438 rc = subprocess.Popen(['git', 'diff-index', '--quiet', 'HEAD', 439 '--'], 440 stdout=None, 441 stderr=None, 442 cwd=path).wait() 443 if rc: 444 return revision + '-dirty', True 445 return revision, False 446 return None, False 447 448 def git_remote(path): 449 popen = subprocess.Popen(['git', 'remote'], 450 stdout=subprocess.PIPE, 451 stderr=subprocess.PIPE, 452 cwd=path) 453 stdout, stderr = popen.communicate() 454 stdout = stdout.decode('utf-8') 455 456 remotes_name = [] 457 if not (popen.returncode or stderr): 458 remotes_name = stdout.rstrip().split('\n') 459 460 remote_url = None 461 462 # If more than one remote, do not return any remote 463 if len(remotes_name) == 1: 464 remote = remotes_name[0] 465 popen = subprocess.Popen(['git', 'remote', 'get-url', remote], 466 stdout=subprocess.PIPE, 467 stderr=subprocess.PIPE, 468 cwd=path) 469 stdout, stderr = popen.communicate() 470 stdout = stdout.decode('utf-8') 471 472 if not (popen.returncode or stderr): 473 remote_url = stdout.rstrip() 474 475 return remote_url 476 477 def git_tags(path, revision): 478 if not revision or len(revision) == 0: 479 return None 480 481 popen = subprocess.Popen(['git', '-P', 'tag', '--points-at', revision], 482 stdout=subprocess.PIPE, 483 stderr=subprocess.PIPE, 484 cwd=path) 485 stdout, stderr = popen.communicate() 486 stdout = stdout.decode('utf-8') 487 488 tags = None 489 if not (popen.returncode or stderr): 490 tags = stdout.rstrip().splitlines() 491 492 return tags 493 494 workspace_dirty = False 495 path = PurePath(project_path).as_posix() 496 497 revision, dirty = git_revision(path) 498 workspace_dirty |= dirty 499 remote = git_remote(path) 500 tags = git_tags(path, revision) 501 502 meta_project = {'path': path, 503 'revision': revision} 504 505 if remote: 506 meta_project['remote'] = remote 507 508 if tags: 509 meta_project['tags'] = tags 510 511 return meta_project, workspace_dirty 512 513 514def _get_meta_project(meta_projects_list, project_path): 515 projects = [ prj for prj in meta_projects_list[1:] if prj["path"] == project_path ] 516 517 return projects[0] if len(projects) == 1 else None 518 519 520def process_meta(zephyr_base, west_projs, modules, extra_modules=None, 521 propagate_state=False): 522 # Process zephyr_base, projects, and modules and create a dictionary 523 # with meta information for each input. 524 # 525 # The dictionary will contain meta info in the following lists: 526 # - zephyr: path and revision 527 # - modules: name, path, and revision 528 # - west-projects: path and revision 529 # 530 # returns the dictionary with said lists 531 532 meta = {'zephyr': None, 'modules': None, 'workspace': None} 533 534 zephyr_project, zephyr_dirty = _create_meta_project(zephyr_base) 535 zephyr_off = zephyr_project.get("remote") is None 536 537 workspace_dirty = zephyr_dirty 538 workspace_extra = extra_modules is not None 539 workspace_off = zephyr_off 540 541 if zephyr_off: 542 zephyr_project['revision'] += '-off' 543 544 meta['zephyr'] = zephyr_project 545 meta['workspace'] = {} 546 547 if west_projs is not None: 548 from west.manifest import MANIFEST_REV_BRANCH 549 projects = west_projs['projects'] 550 meta_projects = [] 551 552 manifest_path = projects[0].posixpath 553 554 # Special treatment of manifest project 555 # Git information (remote/revision) are not provided by west for the Manifest (west.yml) 556 # To mitigate this, we check if we don't use the manifest from the zephyr repository or an other project. 557 # If it's from zephyr, reuse zephyr information 558 # If it's from an other project, ignore it, it will be added later 559 # If it's not found, we extract data manually (remote/revision) from the directory 560 561 manifest_project = None 562 manifest_dirty = False 563 manifest_off = False 564 565 if zephyr_base == manifest_path: 566 manifest_project = zephyr_project 567 manifest_dirty = zephyr_dirty 568 manifest_off = zephyr_off 569 elif not [ prj for prj in projects[1:] if prj.posixpath == manifest_path ]: 570 manifest_project, manifest_dirty = _create_meta_project( 571 projects[0].posixpath) 572 manifest_off = manifest_project.get("remote") is None 573 if manifest_off: 574 manifest_project["revision"] += "-off" 575 576 if manifest_project: 577 workspace_off |= manifest_off 578 workspace_dirty |= manifest_dirty 579 meta_projects.append(manifest_project) 580 581 # Iterates on all projects except the first one (manifest) 582 for project in projects[1:]: 583 meta_project, dirty = _create_meta_project(project.posixpath) 584 workspace_dirty |= dirty 585 meta_projects.append(meta_project) 586 587 off = False 588 if not meta_project.get("remote") or project.sha(MANIFEST_REV_BRANCH) != meta_project['revision'].removesuffix("-dirty"): 589 off = True 590 if not meta_project.get('remote') or project.url != meta_project['remote']: 591 # Force manifest URL and set commit as 'off' 592 meta_project['url'] = project.url 593 off = True 594 595 if off: 596 meta_project['revision'] += '-off' 597 workspace_off |= off 598 599 # If manifest is in project, updates related variables 600 if project.posixpath == manifest_path: 601 manifest_dirty |= dirty 602 manifest_off |= off 603 manifest_project = meta_project 604 605 meta.update({'west': {'manifest': west_projs['manifest_path'], 606 'projects': meta_projects}}) 607 meta['workspace'].update({'off': workspace_off}) 608 609 # Iterates on all modules 610 meta_modules = [] 611 for module in modules: 612 # Check if modules is not in projects 613 # It allows to have the "-off" flag since `modules` variable` does not provide URL/remote 614 meta_module = _get_meta_project(meta_projects, module.project) 615 616 if not meta_module: 617 meta_module, dirty = _create_meta_project(module.project) 618 workspace_dirty |= dirty 619 620 meta_module['name'] = module.meta.get('name') 621 622 if module.meta.get('security'): 623 meta_module['security'] = module.meta.get('security') 624 meta_modules.append(meta_module) 625 626 meta['modules'] = meta_modules 627 628 meta['workspace'].update({'dirty': workspace_dirty, 629 'extra': workspace_extra}) 630 631 if propagate_state: 632 zephyr_revision = zephyr_project['revision'] 633 if workspace_dirty and not zephyr_dirty: 634 zephyr_revision += '-dirty' 635 if workspace_extra: 636 zephyr_revision += '-extra' 637 if workspace_off and not zephyr_off: 638 zephyr_revision += '-off' 639 zephyr_project.update({'revision': zephyr_revision}) 640 641 if west_projs is not None: 642 manifest_revision = manifest_project['revision'] 643 if workspace_dirty and not manifest_dirty: 644 manifest_revision += '-dirty' 645 if workspace_extra: 646 manifest_revision += '-extra' 647 if workspace_off and not manifest_off: 648 manifest_revision += '-off' 649 manifest_project.update({'revision': manifest_revision}) 650 651 return meta 652 653 654def west_projects(manifest=None): 655 manifest_path = None 656 projects = [] 657 # West is imported here, as it is optional 658 # (and thus maybe not installed) 659 # if user is providing a specific modules list. 660 try: 661 from west.manifest import Manifest 662 except ImportError: 663 # West is not installed, so don't return any projects. 664 return None 665 666 # If west *is* installed, we need all of the following imports to 667 # work. West versions that are excessively old may fail here: 668 # west.configuration.MalformedConfig was 669 # west.manifest.MalformedConfig until west v0.14.0, for example. 670 # These should be hard errors. 671 from west.manifest import \ 672 ManifestImportFailed, MalformedManifest, ManifestVersionError 673 from west.configuration import MalformedConfig 674 from west.util import WestNotFound 675 from west.version import __version__ as WestVersion 676 677 from packaging import version 678 try: 679 if not manifest: 680 manifest = Manifest.from_file() 681 if version.parse(WestVersion) >= version.parse('0.9.0'): 682 projects = [p for p in manifest.get_projects([]) 683 if manifest.is_active(p)] 684 else: 685 projects = manifest.get_projects([]) 686 manifest_path = manifest.path 687 return {'manifest_path': manifest_path, 'projects': projects} 688 except (ManifestImportFailed, MalformedManifest, 689 ManifestVersionError, MalformedConfig) as e: 690 sys.exit(f'ERROR: {e}') 691 except WestNotFound: 692 # Only accept WestNotFound, meaning we are not in a west 693 # workspace. Such setup is allowed, as west may be installed 694 # but the project is not required to use west. 695 pass 696 return None 697 698 699def parse_modules(zephyr_base, manifest=None, west_projs=None, modules=None, 700 extra_modules=None): 701 702 if modules is None: 703 west_projs = west_projs or west_projects(manifest) 704 modules = ([p.posixpath for p in west_projs['projects']] 705 if west_projs else []) 706 707 if extra_modules is None: 708 extra_modules = [] 709 710 Module = namedtuple('Module', ['project', 'meta', 'depends']) 711 712 all_modules_by_name = {} 713 # dep_modules is a list of all modules that has an unresolved dependency 714 dep_modules = [] 715 # start_modules is a list modules with no depends left (no incoming edge) 716 start_modules = [] 717 # sorted_modules is a topological sorted list of the modules 718 sorted_modules = [] 719 720 for project in modules + extra_modules: 721 # Avoid including Zephyr base project as module. 722 if project == zephyr_base: 723 continue 724 725 meta = process_module(project) 726 if meta: 727 depends = meta.get('build', {}).get('depends', []) 728 all_modules_by_name[meta['name']] = Module(project, meta, depends) 729 730 elif project in extra_modules: 731 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, ' 732 'is not a valid zephyr module') 733 734 for module in all_modules_by_name.values(): 735 if not module.depends: 736 start_modules.append(module) 737 else: 738 dep_modules.append(module) 739 740 # This will do a topological sort to ensure the modules are ordered 741 # according to dependency settings. 742 while start_modules: 743 node = start_modules.pop(0) 744 sorted_modules.append(node) 745 node_name = node.meta['name'] 746 to_remove = [] 747 for module in dep_modules: 748 if node_name in module.depends: 749 module.depends.remove(node_name) 750 if not module.depends: 751 start_modules.append(module) 752 to_remove.append(module) 753 for module in to_remove: 754 dep_modules.remove(module) 755 756 if dep_modules: 757 # If there are any modules with unresolved dependencies, then the 758 # modules contains unmet or cyclic dependencies. Error out. 759 error = 'Unmet or cyclic dependencies in modules:\n' 760 for module in dep_modules: 761 error += f'{module.project} depends on: {module.depends}\n' 762 sys.exit(error) 763 764 return sorted_modules 765 766 767def main(): 768 parser = argparse.ArgumentParser(description=''' 769 Process a list of projects and create Kconfig / CMake include files for 770 projects which are also a Zephyr module''', allow_abbrev=False) 771 772 parser.add_argument('--kconfig-out', 773 help="""File to write with resulting KConfig import 774 statements.""") 775 parser.add_argument('--twister-out', 776 help="""File to write with resulting twister 777 parameters.""") 778 parser.add_argument('--cmake-out', 779 help="""File to write with resulting <name>:<path> 780 values to use for including in CMake""") 781 parser.add_argument('--sysbuild-kconfig-out', 782 help="""File to write with resulting KConfig import 783 statements.""") 784 parser.add_argument('--sysbuild-cmake-out', 785 help="""File to write with resulting <name>:<path> 786 values to use for including in CMake""") 787 parser.add_argument('--meta-out', 788 help="""Write a build meta YaML file containing a list 789 of Zephyr modules and west projects. 790 If a module or project is also a git repository 791 the current SHA revision will also be written.""") 792 parser.add_argument('--meta-state-propagate', action='store_true', 793 help="""Propagate state of modules and west projects 794 to the suffix of the Zephyr SHA and if west is 795 used, to the suffix of the manifest SHA""") 796 parser.add_argument('--settings-out', 797 help="""File to write with resulting <name>:<value> 798 values to use for including in CMake""") 799 parser.add_argument('-m', '--modules', nargs='+', 800 help="""List of modules to parse instead of using `west 801 list`""") 802 parser.add_argument('-x', '--extra-modules', nargs='+', 803 help='List of extra modules to parse') 804 parser.add_argument('-z', '--zephyr-base', 805 help='Path to zephyr repository') 806 args = parser.parse_args() 807 808 kconfig = "" 809 cmake = "" 810 sysbuild_kconfig = "" 811 sysbuild_cmake = "" 812 settings = "" 813 twister = "" 814 815 west_projs = west_projects() 816 modules = parse_modules(args.zephyr_base, None, west_projs, 817 args.modules, args.extra_modules) 818 819 for module in modules: 820 kconfig += process_kconfig(module.project, module.meta) 821 cmake += process_cmake(module.project, module.meta) 822 sysbuild_kconfig += process_sysbuildkconfig( 823 module.project, module.meta) 824 sysbuild_cmake += process_sysbuildcmake(module.project, module.meta) 825 settings += process_settings(module.project, module.meta) 826 twister += process_twister(module.project, module.meta) 827 828 if args.kconfig_out: 829 with open(args.kconfig_out, 'w', encoding="utf-8") as fp: 830 fp.write(kconfig) 831 832 if args.cmake_out: 833 with open(args.cmake_out, 'w', encoding="utf-8") as fp: 834 fp.write(cmake) 835 836 if args.sysbuild_kconfig_out: 837 with open(args.sysbuild_kconfig_out, 'w', encoding="utf-8") as fp: 838 fp.write(sysbuild_kconfig) 839 840 if args.sysbuild_cmake_out: 841 with open(args.sysbuild_cmake_out, 'w', encoding="utf-8") as fp: 842 fp.write(sysbuild_cmake) 843 844 if args.settings_out: 845 with open(args.settings_out, 'w', encoding="utf-8") as fp: 846 fp.write('''\ 847# WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY! 848# 849# This file contains build system settings derived from your modules. 850# 851# Modules may be set via ZEPHYR_MODULES, ZEPHYR_EXTRA_MODULES, 852# and/or the west manifest file. 853# 854# See the Modules guide for more information. 855''') 856 fp.write(settings) 857 858 if args.twister_out: 859 with open(args.twister_out, 'w', encoding="utf-8") as fp: 860 fp.write(twister) 861 862 if args.meta_out: 863 meta = process_meta(args.zephyr_base, west_projs, modules, 864 args.extra_modules, args.meta_state_propagate) 865 866 with open(args.meta_out, 'w', encoding="utf-8") as fp: 867 # Ignore references and insert data instead 868 yaml.Dumper.ignore_aliases = lambda self, data: True 869 fp.write(yaml.dump(meta)) 870 871 872if __name__ == "__main__": 873 main() 874