blob: b5b257ed712ca2f86445e4f841a64e0e46bd0031 [file] [log] [blame]
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +01001#!/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.
Anas Nashif286a9ed2019-12-11 10:13:23 -050013
Anas Nashif54713982020-12-07 14:52:10 -050014Using --twister-out <filename> an argument file for twister script will
Anas Nashif286a9ed2019-12-11 10:13:23 -050015be generated which would point to test and sample roots available in modules
Anas Nashif54713982020-12-07 14:52:10 -050016that can be included during a twister run. This allows testing code
Anas Nashif286a9ed2019-12-11 10:13:23 -050017maintained in modules in addition to what is available in the main Zephyr tree.
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010018'''
19
20import argparse
Carles Cufib662bc92022-08-24 15:42:55 +020021import hashlib
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010022import os
Torsten Rasmussen3d880832021-01-19 12:01:38 +010023import re
Torsten Rasmussenfffaf052021-10-12 23:08:36 +020024import subprocess
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010025import sys
26import yaml
27import pykwalify.core
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +010028from pathlib import Path, PurePath
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020029from collections import namedtuple
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010030
31METADATA_SCHEMA = '''
32## A pykwalify schema for basic validation of the structure of a
33## metadata YAML file.
34##
35# The zephyr/module.yml file is a simple list of key value pairs to be used by
36# the build system.
37type: map
38mapping:
Torsten Rasmussenf24f8832021-01-07 15:27:53 +010039 name:
40 required: false
41 type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010042 build:
Anas Nashif286a9ed2019-12-11 10:13:23 -050043 required: false
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010044 type: map
45 mapping:
46 cmake:
47 required: false
48 type: str
49 kconfig:
50 required: false
51 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +010052 cmake-ext:
53 required: false
54 type: bool
55 default: false
56 kconfig-ext:
57 required: false
58 type: bool
59 default: false
Jamie McCraedf9027a2023-02-20 10:00:38 +000060 sysbuild-cmake:
61 required: false
62 type: str
63 sysbuild-kconfig:
64 required: false
65 type: str
66 sysbuild-cmake-ext:
67 required: false
68 type: bool
69 default: false
70 sysbuild-kconfig-ext:
71 required: false
72 type: bool
73 default: false
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020074 depends:
75 required: false
76 type: seq
77 sequence:
78 - type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020079 settings:
80 required: false
81 type: map
82 mapping:
83 board_root:
84 required: false
85 type: str
86 dts_root:
87 required: false
88 type: str
Marti Bolivar3f282da02023-01-07 15:25:10 -080089 snippet_root:
90 required: false
91 type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020092 soc_root:
93 required: false
94 type: str
95 arch_root:
96 required: false
97 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +010098 module_ext_root:
99 required: false
100 type: str
Torsten Rasmussencb690ec2022-11-30 12:14:35 +0100101 sca_root:
102 required: false
103 type: str
Anas Nashif286a9ed2019-12-11 10:13:23 -0500104 tests:
105 required: false
106 type: seq
107 sequence:
108 - type: str
109 samples:
110 required: false
111 type: seq
112 sequence:
113 - type: str
114 boards:
115 required: false
116 type: seq
117 sequence:
118 - type: str
Carles Cufi336aa9d2022-08-04 19:08:22 +0200119 blobs:
120 required: false
121 type: seq
122 sequence:
123 - type: map
124 mapping:
125 path:
126 required: true
127 type: str
128 sha256:
129 required: true
130 type: str
131 type:
132 required: true
133 type: str
134 enum: ['img', 'lib']
135 version:
136 required: true
137 type: str
138 license-path:
139 required: true
140 type: str
141 url:
142 required: true
143 type: str
144 description:
145 required: true
146 type: str
147 doc-url:
148 required: false
149 type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100150'''
151
Carles Cufi57e5cd42022-08-11 17:37:34 +0200152MODULE_YML_PATH = PurePath('zephyr/module.yml')
Carles Cufi336aa9d2022-08-04 19:08:22 +0200153# Path to the blobs folder
154MODULE_BLOBS_PATH = PurePath('zephyr/blobs')
Carles Cufib662bc92022-08-24 15:42:55 +0200155BLOB_PRESENT = 'A'
156BLOB_NOT_PRESENT = 'D'
157BLOB_OUTDATED = 'M'
Carles Cufi57e5cd42022-08-11 17:37:34 +0200158
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100159schema = yaml.safe_load(METADATA_SCHEMA)
160
161
162def validate_setting(setting, module_path, filename=None):
163 if setting is not None:
164 if filename is not None:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600165 checkfile = Path(module_path) / setting / filename
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100166 else:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600167 checkfile = Path(module_path) / setting
168 if not checkfile.resolve().is_file():
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100169 return False
170 return True
171
172
Anas Nashif286a9ed2019-12-11 10:13:23 -0500173def process_module(module):
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100174 module_path = PurePath(module)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500175
Carles Cufi57e5cd42022-08-11 17:37:34 +0200176 # The input is a module if zephyr/module.{yml,yaml} is a valid yaml file
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100177 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present.
178
Carles Cufi57e5cd42022-08-11 17:37:34 +0200179 for module_yml in [module_path / MODULE_YML_PATH,
180 module_path / MODULE_YML_PATH.with_suffix('.yaml')]:
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200181 if Path(module_yml).is_file():
182 with Path(module_yml).open('r') as f:
183 meta = yaml.safe_load(f.read())
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100184
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200185 try:
186 pykwalify.core.Core(source_data=meta, schema_data=schema)\
187 .validate()
188 except pykwalify.errors.SchemaError as e:
189 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
190 .format(module_yml.as_posix(), e))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100191
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200192 meta['name'] = meta.get('name', module_path.name)
193 meta['name-sanitized'] = re.sub('[^a-zA-Z0-9]', '_', meta['name'])
194 return meta
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100195
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100196 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \
197 Path(module_path.joinpath('zephyr/Kconfig')).is_file():
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100198 return {'name': module_path.name,
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100199 'name-sanitized': re.sub('[^a-zA-Z0-9]', '_', module_path.name),
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100200 'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}}
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100201
Anas Nashif286a9ed2019-12-11 10:13:23 -0500202 return None
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100203
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100204
Anas Nashif286a9ed2019-12-11 10:13:23 -0500205def process_cmake(module, meta):
206 section = meta.get('build', dict())
207 module_path = PurePath(module)
208 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100209
210 cmake_extern = section.get('cmake-ext', False)
211 if cmake_extern:
212 return('\"{}\":\"{}\":\"{}\"\n'
Andrzej Głąbek02819482021-01-19 07:39:47 +0100213 .format(meta['name'],
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100214 module_path.as_posix(),
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100215 "${ZEPHYR_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}"))
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100216
Anas Nashif286a9ed2019-12-11 10:13:23 -0500217 cmake_setting = section.get('cmake', None)
218 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
219 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200220 'does not contain a CMakeLists.txt file.'
221 .format(module_yml.as_posix(), cmake_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500222
223 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
224 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
225 if os.path.isfile(cmake_file):
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200226 return('\"{}\":\"{}\":\"{}\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100227 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200228 module_path.as_posix(),
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200229 Path(cmake_path).resolve().as_posix()))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500230 else:
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200231 return('\"{}\":\"{}\":\"\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100232 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200233 module_path.as_posix()))
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200234
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100235
Jamie McCraedf9027a2023-02-20 10:00:38 +0000236def process_sysbuildcmake(module, meta):
237 section = meta.get('build', dict())
238 module_path = PurePath(module)
239 module_yml = module_path.joinpath('zephyr/module.yml')
240
241 cmake_extern = section.get('sysbuild-cmake-ext', False)
242 if cmake_extern:
243 return('\"{}\":\"{}\":\"{}\"\n'
244 .format(meta['name'],
245 module_path.as_posix(),
246 "${SYSBUILD_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}"))
247
248 cmake_setting = section.get('sysbuild-cmake', None)
249 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
250 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
251 'does not contain a CMakeLists.txt file.'
252 .format(module_yml.as_posix(), cmake_setting))
253
254 if cmake_setting is None:
255 return ""
256
257 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
258 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
259 if os.path.isfile(cmake_file):
260 return('\"{}\":\"{}\":\"{}\"\n'
261 .format(meta['name'],
262 module_path.as_posix(),
263 Path(cmake_path).resolve().as_posix()))
264 else:
265 return('\"{}\":\"{}\":\"\"\n'
266 .format(meta['name'],
267 module_path.as_posix()))
268
269
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200270def process_settings(module, meta):
271 section = meta.get('build', dict())
272 build_settings = section.get('settings', None)
273 out_text = ""
274
275 if build_settings is not None:
Marti Bolivar3f282da02023-01-07 15:25:10 -0800276 for root in ['board', 'dts', 'snippet', 'soc', 'arch', 'module_ext', 'sca']:
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200277 setting = build_settings.get(root+'_root', None)
278 if setting is not None:
279 root_path = PurePath(module) / setting
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100280 out_text += f'"{root.upper()}_ROOT":'
281 out_text += f'"{root_path.as_posix()}"\n'
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200282
283 return out_text
284
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200285
Carles Cufib662bc92022-08-24 15:42:55 +0200286def get_blob_status(path, sha256):
287 if not path.is_file():
288 return BLOB_NOT_PRESENT
289 with path.open('rb') as f:
290 m = hashlib.sha256()
291 m.update(f.read())
292 if sha256.lower() == m.hexdigest():
293 return BLOB_PRESENT
294 else:
295 return BLOB_OUTDATED
296
297
298def process_blobs(module, meta):
299 blobs = []
300 mblobs = meta.get('blobs', None)
301 if not mblobs:
302 return blobs
303
304 blobs_path = Path(module) / MODULE_BLOBS_PATH
305 for blob in mblobs:
306 blob['module'] = meta.get('name', None)
307 blob['abspath'] = blobs_path / Path(blob['path'])
308 blob['status'] = get_blob_status(blob['abspath'], blob['sha256'])
309 blobs.append(blob)
310
311 return blobs
312
313
Jamie McCraedf9027a2023-02-20 10:00:38 +0000314def kconfig_snippet(meta, path, kconfig_file=None, blobs=False, sysbuild=False):
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100315 name = meta['name']
316 name_sanitized = meta['name-sanitized']
317
Carles Cufi035dffc2022-08-24 16:56:01 +0200318 snippet = [f'menu "{name} ({path.as_posix()})"',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100319 f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file
Jamie McCraedf9027a2023-02-20 10:00:38 +0000320 else f'osource "$(SYSBUILD_{name_sanitized.upper()}_KCONFIG)"' if sysbuild is True
321 else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"',
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100322 f'config ZEPHYR_{name_sanitized.upper()}_MODULE',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100323 ' bool',
324 ' default y',
Carles Cufi035dffc2022-08-24 16:56:01 +0200325 'endmenu\n']
326
327 if blobs:
328 snippet.insert(-1, ' select TAINT_BLOBS')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100329 return '\n'.join(snippet)
330
331
Anas Nashif286a9ed2019-12-11 10:13:23 -0500332def process_kconfig(module, meta):
Carles Cufi035dffc2022-08-24 16:56:01 +0200333 blobs = process_blobs(module, meta)
334 taint_blobs = len(tuple(filter(lambda b: b['status'] != 'D', blobs))) != 0
Anas Nashif286a9ed2019-12-11 10:13:23 -0500335 section = meta.get('build', dict())
336 module_path = PurePath(module)
337 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100338 kconfig_extern = section.get('kconfig-ext', False)
339 if kconfig_extern:
Carles Cufi035dffc2022-08-24 16:56:01 +0200340 return kconfig_snippet(meta, module_path, blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500341
342 kconfig_setting = section.get('kconfig', None)
343 if not validate_setting(kconfig_setting, module):
344 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200345 'not point to a valid Kconfig file.'
346 .format(module_yml, kconfig_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500347
348 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
349 if os.path.isfile(kconfig_file):
Carles Cufi035dffc2022-08-24 16:56:01 +0200350 return kconfig_snippet(meta, module_path, Path(kconfig_file),
351 blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500352 else:
Flavio Ceolin2e5c3712023-05-10 21:40:04 +0000353 name_sanitized = meta['name-sanitized']
354 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n'
355 f' bool\n'
356 f' default y\n')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500357
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100358
Jamie McCraedf9027a2023-02-20 10:00:38 +0000359def process_sysbuildkconfig(module, meta):
360 section = meta.get('build', dict())
361 module_path = PurePath(module)
362 module_yml = module_path.joinpath('zephyr/module.yml')
363 kconfig_extern = section.get('sysbuild-kconfig-ext', False)
364 if kconfig_extern:
365 return kconfig_snippet(meta, module_path, sysbuild=True)
366
367 kconfig_setting = section.get('sysbuild-kconfig', None)
368 if not validate_setting(kconfig_setting, module):
369 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
370 'not point to a valid Kconfig file.'
371 .format(module_yml, kconfig_setting))
372
Jamie McCraeacd14f82024-04-29 09:40:24 +0100373 if kconfig_setting is not None:
374 kconfig_file = os.path.join(module, kconfig_setting)
375 if os.path.isfile(kconfig_file):
376 return kconfig_snippet(meta, module_path, Path(kconfig_file))
Jamie McCraedf9027a2023-02-20 10:00:38 +0000377
Jamie McCraeacd14f82024-04-29 09:40:24 +0100378 name_sanitized = meta['name-sanitized']
379 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n'
380 f' bool\n'
381 f' default y\n')
Jamie McCraedf9027a2023-02-20 10:00:38 +0000382
383
Anas Nashif54713982020-12-07 14:52:10 -0500384def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500385
386 out = ""
387 tests = meta.get('tests', [])
388 samples = meta.get('samples', [])
389 boards = meta.get('boards', [])
390
391 for pth in tests + samples:
392 if pth:
393 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200394 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
395 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500396
397 for pth in boards:
398 if pth:
399 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200400 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
401 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500402
403 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100404
405
Carles Cufia580b3d2022-08-09 11:13:54 +0200406def process_meta(zephyr_base, west_projs, modules, extra_modules=None,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100407 propagate_state=False):
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200408 # Process zephyr_base, projects, and modules and create a dictionary
409 # with meta information for each input.
410 #
411 # The dictionary will contain meta info in the following lists:
412 # - zephyr: path and revision
413 # - modules: name, path, and revision
414 # - west-projects: path and revision
415 #
416 # returns the dictionary with said lists
417
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100418 meta = {'zephyr': None, 'modules': None, 'workspace': None}
419
420 workspace_dirty = False
421 workspace_extra = extra_modules is not None
422 workspace_off = False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200423
424 def git_revision(path):
425 rc = subprocess.Popen(['git', 'rev-parse', '--is-inside-work-tree'],
426 stdout=subprocess.PIPE,
427 stderr=subprocess.PIPE,
428 cwd=path).wait()
429 if rc == 0:
430 # A git repo.
431 popen = subprocess.Popen(['git', 'rev-parse', 'HEAD'],
432 stdout=subprocess.PIPE,
433 stderr=subprocess.PIPE,
434 cwd=path)
435 stdout, stderr = popen.communicate()
436 stdout = stdout.decode('utf-8')
437
438 if not (popen.returncode or stderr):
439 revision = stdout.rstrip()
440
441 rc = subprocess.Popen(['git', 'diff-index', '--quiet', 'HEAD',
442 '--'],
443 stdout=None,
444 stderr=None,
445 cwd=path).wait()
446 if rc:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100447 return revision + '-dirty', True
448 return revision, False
449 return None, False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200450
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100451 zephyr_revision, zephyr_dirty = git_revision(zephyr_base)
452 zephyr_project = {'path': zephyr_base,
453 'revision': zephyr_revision}
454 meta['zephyr'] = zephyr_project
455 meta['workspace'] = {}
456 workspace_dirty |= zephyr_dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200457
Carles Cufia580b3d2022-08-09 11:13:54 +0200458 if west_projs is not None:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100459 from west.manifest import MANIFEST_REV_BRANCH
Carles Cufia580b3d2022-08-09 11:13:54 +0200460 projects = west_projs['projects']
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200461 meta_projects = []
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100462
463 # Special treatment of manifest project.
Carles Cufia580b3d2022-08-09 11:13:54 +0200464 manifest_proj_path = PurePath(projects[0].posixpath).as_posix()
465 manifest_revision, manifest_dirty = git_revision(manifest_proj_path)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100466 workspace_dirty |= manifest_dirty
Carles Cufia580b3d2022-08-09 11:13:54 +0200467 manifest_project = {'path': manifest_proj_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100468 'revision': manifest_revision}
469 meta_projects.append(manifest_project)
470
471 for project in projects[1:]:
472 project_path = PurePath(project.posixpath).as_posix()
473 revision, dirty = git_revision(project_path)
474 workspace_dirty |= dirty
475 if project.sha(MANIFEST_REV_BRANCH) != revision:
476 revision += '-off'
477 workspace_off = True
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200478 meta_project = {'path': project_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100479 'revision': revision}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200480 meta_projects.append(meta_project)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100481
Carles Cufia580b3d2022-08-09 11:13:54 +0200482 meta.update({'west': {'manifest': west_projs['manifest_path'],
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100483 'projects': meta_projects}})
484 meta['workspace'].update({'off': workspace_off})
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200485
486 meta_projects = []
487 for module in modules:
488 module_path = PurePath(module.project).as_posix()
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100489 revision, dirty = git_revision(module_path)
490 workspace_dirty |= dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200491 meta_project = {'name': module.meta['name'],
492 'path': module_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100493 'revision': revision}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200494 meta_projects.append(meta_project)
495 meta['modules'] = meta_projects
496
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100497 meta['workspace'].update({'dirty': workspace_dirty,
498 'extra': workspace_extra})
499
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100500 if propagate_state:
501 if workspace_dirty and not zephyr_dirty:
502 zephyr_revision += '-dirty'
503 if workspace_extra:
504 zephyr_revision += '-extra'
505 if workspace_off:
506 zephyr_revision += '-off'
507 zephyr_project.update({'revision': zephyr_revision})
508
Carles Cufia580b3d2022-08-09 11:13:54 +0200509 if west_projs is not None:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100510 if workspace_dirty and not manifest_dirty:
511 manifest_revision += '-dirty'
512 if workspace_extra:
513 manifest_revision += '-extra'
514 if workspace_off:
515 manifest_revision += '-off'
516 manifest_project.update({'revision': manifest_revision})
517
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200518 return meta
519
520
Carles Cufia580b3d2022-08-09 11:13:54 +0200521def west_projects(manifest = None):
522 manifest_path = None
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200523 projects = []
524 # West is imported here, as it is optional
525 # (and thus maybe not installed)
526 # if user is providing a specific modules list.
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600527 try:
Martí Bolívar251f2692023-05-19 11:22:01 -0700528 from west.manifest import Manifest
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600529 except ImportError:
530 # West is not installed, so don't return any projects.
531 return None
532
Martí Bolívar251f2692023-05-19 11:22:01 -0700533 # If west *is* installed, we need all of the following imports to
534 # work. West versions that are excessively old may fail here:
535 # west.configuration.MalformedConfig was
536 # west.manifest.MalformedConfig until west v0.14.0, for example.
537 # These should be hard errors.
538 from west.manifest import \
539 ManifestImportFailed, MalformedManifest, ManifestVersionError
540 from west.configuration import MalformedConfig
541 from west.util import WestNotFound
542 from west.version import __version__ as WestVersion
543
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200544 from packaging import version
545 try:
Carles Cufia580b3d2022-08-09 11:13:54 +0200546 if not manifest:
547 manifest = Manifest.from_file()
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200548 if version.parse(WestVersion) >= version.parse('0.9.0'):
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100549 projects = [p for p in manifest.get_projects([])
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200550 if manifest.is_active(p)]
551 else:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100552 projects = manifest.get_projects([])
Carles Cufia580b3d2022-08-09 11:13:54 +0200553 manifest_path = manifest.path
554 return {'manifest_path': manifest_path, 'projects': projects}
Martí Bolívarce2a7d92023-05-18 13:21:34 -0700555 except (ManifestImportFailed, MalformedManifest,
556 ManifestVersionError, MalformedConfig) as e:
557 sys.exit(f'ERROR: {e}')
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200558 except WestNotFound:
559 # Only accept WestNotFound, meaning we are not in a west
560 # workspace. Such setup is allowed, as west may be installed
561 # but the project is not required to use west.
562 pass
563 return None
564
565
Carles Cufia580b3d2022-08-09 11:13:54 +0200566def parse_modules(zephyr_base, manifest=None, west_projs=None, modules=None,
567 extra_modules=None):
568
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200569 if modules is None:
Carles Cufia580b3d2022-08-09 11:13:54 +0200570 west_projs = west_projs or west_projects(manifest)
571 modules = ([p.posixpath for p in west_projs['projects']]
572 if west_projs else [])
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100573
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200574 if extra_modules is None:
575 extra_modules = []
Anas Nashif286a9ed2019-12-11 10:13:23 -0500576
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200577 Module = namedtuple('Module', ['project', 'meta', 'depends'])
Damian Krolika5051482022-03-01 18:36:16 +0100578
579 all_modules_by_name = {}
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200580 # dep_modules is a list of all modules that has an unresolved dependency
581 dep_modules = []
582 # start_modules is a list modules with no depends left (no incoming edge)
583 start_modules = []
584 # sorted_modules is a topological sorted list of the modules
585 sorted_modules = []
586
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200587 for project in modules + extra_modules:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500588 # Avoid including Zephyr base project as module.
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200589 if project == zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500590 continue
591
592 meta = process_module(project)
593 if meta:
Damian Krolika5051482022-03-01 18:36:16 +0100594 depends = meta.get('build', {}).get('depends', [])
595 all_modules_by_name[meta['name']] = Module(project, meta, depends)
596
Martí Bolívar34346c42020-01-29 12:38:58 -0800597 elif project in extra_modules:
598 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
599 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500600
Damian Krolika5051482022-03-01 18:36:16 +0100601 for module in all_modules_by_name.values():
602 if not module.depends:
603 start_modules.append(module)
604 else:
605 dep_modules.append(module)
606
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200607 # This will do a topological sort to ensure the modules are ordered
608 # according to dependency settings.
609 while start_modules:
610 node = start_modules.pop(0)
611 sorted_modules.append(node)
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100612 node_name = node.meta['name']
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200613 to_remove = []
614 for module in dep_modules:
615 if node_name in module.depends:
616 module.depends.remove(node_name)
617 if not module.depends:
618 start_modules.append(module)
619 to_remove.append(module)
620 for module in to_remove:
621 dep_modules.remove(module)
622
623 if dep_modules:
624 # If there are any modules with unresolved dependencies, then the
625 # modules contains unmet or cyclic dependencies. Error out.
626 error = 'Unmet or cyclic dependencies in modules:\n'
627 for module in dep_modules:
628 error += f'{module.project} depends on: {module.depends}\n'
629 sys.exit(error)
630
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200631 return sorted_modules
632
633
634def main():
635 parser = argparse.ArgumentParser(description='''
636 Process a list of projects and create Kconfig / CMake include files for
Jamie McCraeec704442023-01-04 16:08:36 +0000637 projects which are also a Zephyr module''', allow_abbrev=False)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200638
639 parser.add_argument('--kconfig-out',
640 help="""File to write with resulting KConfig import
641 statements.""")
642 parser.add_argument('--twister-out',
643 help="""File to write with resulting twister
644 parameters.""")
645 parser.add_argument('--cmake-out',
646 help="""File to write with resulting <name>:<path>
647 values to use for including in CMake""")
Jamie McCraedf9027a2023-02-20 10:00:38 +0000648 parser.add_argument('--sysbuild-kconfig-out',
649 help="""File to write with resulting KConfig import
650 statements.""")
651 parser.add_argument('--sysbuild-cmake-out',
652 help="""File to write with resulting <name>:<path>
653 values to use for including in CMake""")
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200654 parser.add_argument('--meta-out',
655 help="""Write a build meta YaML file containing a list
656 of Zephyr modules and west projects.
657 If a module or project is also a git repository
658 the current SHA revision will also be written.""")
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100659 parser.add_argument('--meta-state-propagate', action='store_true',
660 help="""Propagate state of modules and west projects
661 to the suffix of the Zephyr SHA and if west is
662 used, to the suffix of the manifest SHA""")
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200663 parser.add_argument('--settings-out',
664 help="""File to write with resulting <name>:<value>
665 values to use for including in CMake""")
666 parser.add_argument('-m', '--modules', nargs='+',
667 help="""List of modules to parse instead of using `west
668 list`""")
669 parser.add_argument('-x', '--extra-modules', nargs='+',
670 help='List of extra modules to parse')
671 parser.add_argument('-z', '--zephyr-base',
672 help='Path to zephyr repository')
673 args = parser.parse_args()
674
675 kconfig = ""
676 cmake = ""
Jamie McCraedf9027a2023-02-20 10:00:38 +0000677 sysbuild_kconfig = ""
678 sysbuild_cmake = ""
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200679 settings = ""
680 twister = ""
681
Carles Cufia580b3d2022-08-09 11:13:54 +0200682 west_projs = west_projects()
683 modules = parse_modules(args.zephyr_base, None, west_projs,
684 args.modules, args.extra_modules)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200685
686 for module in modules:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200687 kconfig += process_kconfig(module.project, module.meta)
688 cmake += process_cmake(module.project, module.meta)
Jamie McCraedf9027a2023-02-20 10:00:38 +0000689 sysbuild_kconfig += process_sysbuildkconfig(module.project, module.meta)
690 sysbuild_cmake += process_sysbuildcmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200691 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500692 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200693
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100694 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500695 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
696 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100697
698 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500699 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
700 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100701
Jamie McCraedf9027a2023-02-20 10:00:38 +0000702 if args.sysbuild_kconfig_out:
703 with open(args.sysbuild_kconfig_out, 'w', encoding="utf-8") as fp:
704 fp.write(sysbuild_kconfig)
705
706 if args.sysbuild_cmake_out:
707 with open(args.sysbuild_cmake_out, 'w', encoding="utf-8") as fp:
708 fp.write(sysbuild_cmake)
709
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200710 if args.settings_out:
711 with open(args.settings_out, 'w', encoding="utf-8") as fp:
Martí Bolívar361956b2021-08-09 10:46:17 -0700712 fp.write('''\
713# WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!
714#
715# This file contains build system settings derived from your modules.
716#
717# Modules may be set via ZEPHYR_MODULES, ZEPHYR_EXTRA_MODULES,
718# and/or the west manifest file.
719#
720# See the Modules guide for more information.
721''')
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200722 fp.write(settings)
723
Anas Nashif54713982020-12-07 14:52:10 -0500724 if args.twister_out:
725 with open(args.twister_out, 'w', encoding="utf-8") as fp:
726 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100727
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200728 if args.meta_out:
Carles Cufia580b3d2022-08-09 11:13:54 +0200729 meta = process_meta(args.zephyr_base, west_projs, modules,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100730 args.extra_modules, args.meta_state_propagate)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100731
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200732 with open(args.meta_out, 'w', encoding="utf-8") as fp:
733 fp.write(yaml.dump(meta))
734
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200735
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100736if __name__ == "__main__":
737 main()