diff --git a/CMakeLists.txt b/CMakeLists.txt index 8036c42aad7..78b31aaabfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1406,6 +1406,21 @@ if(CONFIG_BUILD_OUTPUT_BIN AND CONFIG_BUILD_OUTPUT_UF2) ) endif() +if(CONFIG_BUILD_OUTPUT_META) + list(APPEND + post_build_commands + COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/zephyr_module.py + ${WEST_ARG} + ${ZEPHYR_MODULES_ARG} + ${ZEPHYR_EXTRA_MODULES_ARG} + --meta-out ${KERNEL_META_NAME} + ) + list(APPEND + post_build_byproducts + ${KERNEL_META_NAME} + ) +endif() + # Cleanup intermediate files if(CONFIG_CLEANUP_INTERMEDIATE_FILES) list(APPEND diff --git a/Kconfig.zephyr b/Kconfig.zephyr index 5372c0ba158..fbd47d297d1 100644 --- a/Kconfig.zephyr +++ b/Kconfig.zephyr @@ -506,6 +506,15 @@ config MAKEFILE_EXPORTS Generates a file with build information that can be read by third party Makefile-based build systems. +config BUILD_OUTPUT_META + bool "Create a build meta file" + help + Create a build meta file in the build directory containing lists of: + - Zephyr: path and revision (if git repo) + - Zephyr modules: name, path, and revision (if git repo) + - West projects: path and revision + File extension is .meta + endmenu config EXPERIMENTAL diff --git a/cmake/app/boilerplate.cmake b/cmake/app/boilerplate.cmake index 2ccc8c3515c..bba812987e3 100644 --- a/cmake/app/boilerplate.cmake +++ b/cmake/app/boilerplate.cmake @@ -614,6 +614,7 @@ set(KERNEL_S19_NAME ${KERNEL_NAME}.s19) set(KERNEL_EXE_NAME ${KERNEL_NAME}.exe) set(KERNEL_STAT_NAME ${KERNEL_NAME}.stat) set(KERNEL_STRIP_NAME ${KERNEL_NAME}.strip) +set(KERNEL_META_NAME ${KERNEL_NAME}.meta) include(${BOARD_DIR}/board.cmake OPTIONAL) diff --git a/scripts/zephyr_module.py b/scripts/zephyr_module.py index 9d100ed495e..7ef5a38e554 100755 --- a/scripts/zephyr_module.py +++ b/scripts/zephyr_module.py @@ -20,6 +20,7 @@ maintained in modules in addition to what is available in the main Zephyr tree. import argparse import os import re +import subprocess import sys import yaml import pykwalify.core @@ -245,35 +246,106 @@ def process_twister(module, meta): return out +def process_meta(zephyr_base, west_projects, modules): + # Process zephyr_base, projects, and modules and create a dictionary + # with meta information for each input. + # + # The dictionary will contain meta info in the following lists: + # - zephyr: path and revision + # - modules: name, path, and revision + # - west-projects: path and revision + # + # returns the dictionary with said lists + + meta = {'zephyr': None, 'modules': None, 'west': None} + + def git_revision(path): + rc = subprocess.Popen(['git', 'rev-parse', '--is-inside-work-tree'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=path).wait() + if rc == 0: + # A git repo. + popen = subprocess.Popen(['git', 'rev-parse', 'HEAD'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=path) + stdout, stderr = popen.communicate() + stdout = stdout.decode('utf-8') + + if not (popen.returncode or stderr): + revision = stdout.rstrip() + + rc = subprocess.Popen(['git', 'diff-index', '--quiet', 'HEAD', + '--'], + stdout=None, + stderr=None, + cwd=path).wait() + if rc: + return revision + '-dirty' + return revision + return None + + meta_project = {'path': zephyr_base, + 'revision': git_revision(zephyr_base)} + meta['zephyr'] = meta_project + + if west_projects is not None: + meta_projects = [] + for project in west_projects['projects']: + project_path = PurePath(project).as_posix() + meta_project = {'path': project_path, + 'revision': git_revision(project_path)} + meta_projects.append(meta_project) + meta['west'] = {'manifest': west_projects['manifest'], + 'projects': meta_projects} + + meta_projects = [] + for module in modules: + module_path = PurePath(module.project).as_posix() + meta_project = {'name': module.meta['name'], + 'path': module_path, + 'revision': git_revision(module_path)} + meta_projects.append(meta_project) + meta['modules'] = meta_projects + + return meta + + +def west_projects(): + manifest_file = None + projects = [] + # West is imported here, as it is optional + # (and thus maybe not installed) + # if user is providing a specific modules list. + from west.manifest import Manifest + from west.util import WestNotFound + from west.version import __version__ as WestVersion + from packaging import version + try: + manifest = Manifest.from_file() + if version.parse(WestVersion) >= version.parse('0.9.0'): + projects = [p.posixpath for p in manifest.get_projects([]) + if manifest.is_active(p)] + else: + projects = [p.posixpath for p in manifest.get_projects([])] + manifest_file = manifest.path + return {'manifest': manifest_file, 'projects': projects} + except WestNotFound: + # Only accept WestNotFound, meaning we are not in a west + # workspace. Such setup is allowed, as west may be installed + # but the project is not required to use west. + pass + return None + + def parse_modules(zephyr_base, modules=None, extra_modules=None): if modules is None: - # West is imported here, as it is optional - # (and thus maybe not installed) - # if user is providing a specific modules list. - from west.manifest import Manifest - from west.util import WestNotFound - from west.version import __version__ as WestVersion - from packaging import version - try: - manifest = Manifest.from_file() - if version.parse(WestVersion) >= version.parse('0.9.0'): - projects = [p.posixpath for p in manifest.get_projects([]) - if manifest.is_active(p)] - else: - projects = [p.posixpath for p in manifest.get_projects([])] - except WestNotFound: - # Only accept WestNotFound, meaning we are not in a west - # workspace. Such setup is allowed, as west may be installed - # but the project is not required to use west. - projects = [] - else: - projects = modules.copy() + modules = [] if extra_modules is None: extra_modules = [] - projects += extra_modules - Module = namedtuple('Module', ['project', 'meta', 'depends']) # dep_modules is a list of all modules that has an unresolved dependency dep_modules = [] @@ -282,7 +354,7 @@ def parse_modules(zephyr_base, modules=None, extra_modules=None): # sorted_modules is a topological sorted list of the modules sorted_modules = [] - for project in projects: + for project in modules + extra_modules: # Avoid including Zephyr base project as module. if project == zephyr_base: continue @@ -340,6 +412,11 @@ def main(): parser.add_argument('--cmake-out', help="""File to write with resulting : values to use for including in CMake""") + parser.add_argument('--meta-out', + help="""Write a build meta YaML file containing a list + of Zephyr modules and west projects. + If a module or project is also a git repository + the current SHA revision will also be written.""") parser.add_argument('--settings-out', help="""File to write with resulting : values to use for including in CMake""") @@ -357,7 +434,15 @@ def main(): settings = "" twister = "" - modules = parse_modules(args.zephyr_base, args.modules, args.extra_modules) + west_proj = None + if args.modules is None: + west_proj = west_projects() + modules = parse_modules(args.zephyr_base, + west_proj['projects'] if west_proj else None, + args.extra_modules) + else: + modules = parse_modules(args.zephyr_base, args.modules, + args.extra_modules) for module in modules: kconfig += process_kconfig(module.project, module.meta) @@ -391,6 +476,11 @@ def main(): with open(args.twister_out, 'w', encoding="utf-8") as fp: fp.write(twister) + if args.meta_out: + meta = process_meta(args.zephyr_base, west_proj, modules) + with open(args.meta_out, 'w', encoding="utf-8") as fp: + fp.write(yaml.dump(meta)) + if __name__ == "__main__": main()