blob: 22369aa8295b1d5dd8719d73a2e32cc9793edfa3 [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
Lukasz Mrugalab2f43212024-04-19 10:37:20 +000031try:
32 from yaml import CSafeLoader as SafeLoader
33except ImportError:
34 from yaml import SafeLoader
35
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010036METADATA_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:
Torsten Rasmussenf24f8832021-01-07 15:27:53 +010044 name:
45 required: false
46 type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010047 build:
Anas Nashif286a9ed2019-12-11 10:13:23 -050048 required: false
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010049 type: map
50 mapping:
51 cmake:
52 required: false
53 type: str
54 kconfig:
55 required: false
56 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +010057 cmake-ext:
58 required: false
59 type: bool
60 default: false
61 kconfig-ext:
62 required: false
63 type: bool
64 default: false
Jamie McCraedf9027a2023-02-20 10:00:38 +000065 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
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020079 depends:
80 required: false
81 type: seq
82 sequence:
83 - type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020084 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
Marti Bolivar3f282da02023-01-07 15:25:10 -080094 snippet_root:
95 required: false
96 type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020097 soc_root:
98 required: false
99 type: str
100 arch_root:
101 required: false
102 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100103 module_ext_root:
104 required: false
105 type: str
Torsten Rasmussencb690ec2022-11-30 12:14:35 +0100106 sca_root:
107 required: false
108 type: str
Anas Nashif286a9ed2019-12-11 10:13:23 -0500109 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
Carles Cufi336aa9d2022-08-04 19:08:22 +0200124 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
Thomas Gagneret0d053182024-01-18 17:31:46 +0100155 security:
156 required: false
157 type: map
158 mapping:
159 external-references:
160 required: false
161 type: seq
162 sequence:
163 - type: str
Pieter De Gendt2ad91522024-11-02 16:46:56 +0100164 package-managers:
165 required: false
166 type: map
167 mapping:
168 pip:
169 required: false
170 type: map
171 mapping:
172 requirement-files:
173 required: false
174 type: seq
175 sequence:
176 - type: str
Pieter De Gendt640a8b92024-12-19 07:31:22 +0100177 runners:
178 required: false
179 type: seq
180 sequence:
181 - type: map
182 mapping:
183 file:
184 required: true
185 type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100186'''
187
Carles Cufi57e5cd42022-08-11 17:37:34 +0200188MODULE_YML_PATH = PurePath('zephyr/module.yml')
Carles Cufi336aa9d2022-08-04 19:08:22 +0200189# Path to the blobs folder
190MODULE_BLOBS_PATH = PurePath('zephyr/blobs')
Carles Cufib662bc92022-08-24 15:42:55 +0200191BLOB_PRESENT = 'A'
192BLOB_NOT_PRESENT = 'D'
193BLOB_OUTDATED = 'M'
Carles Cufi57e5cd42022-08-11 17:37:34 +0200194
Lukasz Mrugalab2f43212024-04-19 10:37:20 +0000195schema = yaml.load(METADATA_SCHEMA, Loader=SafeLoader)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100196
197
198def validate_setting(setting, module_path, filename=None):
199 if setting is not None:
200 if filename is not None:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600201 checkfile = Path(module_path) / setting / filename
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100202 else:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600203 checkfile = Path(module_path) / setting
204 if not checkfile.resolve().is_file():
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100205 return False
206 return True
207
208
Anas Nashif286a9ed2019-12-11 10:13:23 -0500209def process_module(module):
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100210 module_path = PurePath(module)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500211
Carles Cufi57e5cd42022-08-11 17:37:34 +0200212 # The input is a module if zephyr/module.{yml,yaml} is a valid yaml file
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100213 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present.
214
Carles Cufi57e5cd42022-08-11 17:37:34 +0200215 for module_yml in [module_path / MODULE_YML_PATH,
216 module_path / MODULE_YML_PATH.with_suffix('.yaml')]:
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200217 if Path(module_yml).is_file():
Pieter De Gendtca3dc012024-10-15 08:58:36 +0200218 with Path(module_yml).open('r', encoding='utf-8') as f:
Lukasz Mrugalab2f43212024-04-19 10:37:20 +0000219 meta = yaml.load(f.read(), Loader=SafeLoader)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100220
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200221 try:
222 pykwalify.core.Core(source_data=meta, schema_data=schema)\
223 .validate()
224 except pykwalify.errors.SchemaError as e:
225 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
226 .format(module_yml.as_posix(), e))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100227
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200228 meta['name'] = meta.get('name', module_path.name)
229 meta['name-sanitized'] = re.sub('[^a-zA-Z0-9]', '_', meta['name'])
230 return meta
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100231
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100232 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \
233 Path(module_path.joinpath('zephyr/Kconfig')).is_file():
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100234 return {'name': module_path.name,
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100235 'name-sanitized': re.sub('[^a-zA-Z0-9]', '_', module_path.name),
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100236 'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}}
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100237
Anas Nashif286a9ed2019-12-11 10:13:23 -0500238 return None
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100239
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100240
Anas Nashif286a9ed2019-12-11 10:13:23 -0500241def process_cmake(module, meta):
242 section = meta.get('build', dict())
243 module_path = PurePath(module)
244 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100245
246 cmake_extern = section.get('cmake-ext', False)
247 if cmake_extern:
248 return('\"{}\":\"{}\":\"{}\"\n'
Andrzej Głąbek02819482021-01-19 07:39:47 +0100249 .format(meta['name'],
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100250 module_path.as_posix(),
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100251 "${ZEPHYR_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}"))
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100252
Anas Nashif286a9ed2019-12-11 10:13:23 -0500253 cmake_setting = section.get('cmake', None)
254 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
255 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200256 'does not contain a CMakeLists.txt file.'
257 .format(module_yml.as_posix(), cmake_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500258
259 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
260 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
261 if os.path.isfile(cmake_file):
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200262 return('\"{}\":\"{}\":\"{}\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100263 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200264 module_path.as_posix(),
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200265 Path(cmake_path).resolve().as_posix()))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500266 else:
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200267 return('\"{}\":\"{}\":\"\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100268 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200269 module_path.as_posix()))
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200270
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100271
Jamie McCraedf9027a2023-02-20 10:00:38 +0000272def process_sysbuildcmake(module, meta):
273 section = meta.get('build', dict())
274 module_path = PurePath(module)
275 module_yml = module_path.joinpath('zephyr/module.yml')
276
277 cmake_extern = section.get('sysbuild-cmake-ext', False)
278 if cmake_extern:
279 return('\"{}\":\"{}\":\"{}\"\n'
280 .format(meta['name'],
281 module_path.as_posix(),
282 "${SYSBUILD_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}"))
283
284 cmake_setting = section.get('sysbuild-cmake', None)
285 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
286 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
287 'does not contain a CMakeLists.txt file.'
288 .format(module_yml.as_posix(), cmake_setting))
289
290 if cmake_setting is None:
291 return ""
292
293 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
294 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
295 if os.path.isfile(cmake_file):
296 return('\"{}\":\"{}\":\"{}\"\n'
297 .format(meta['name'],
298 module_path.as_posix(),
299 Path(cmake_path).resolve().as_posix()))
300 else:
301 return('\"{}\":\"{}\":\"\"\n'
302 .format(meta['name'],
303 module_path.as_posix()))
304
305
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200306def process_settings(module, meta):
307 section = meta.get('build', dict())
308 build_settings = section.get('settings', None)
309 out_text = ""
310
311 if build_settings is not None:
Marti Bolivar3f282da02023-01-07 15:25:10 -0800312 for root in ['board', 'dts', 'snippet', 'soc', 'arch', 'module_ext', 'sca']:
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200313 setting = build_settings.get(root+'_root', None)
314 if setting is not None:
315 root_path = PurePath(module) / setting
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100316 out_text += f'"{root.upper()}_ROOT":'
317 out_text += f'"{root_path.as_posix()}"\n'
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200318
319 return out_text
320
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200321
Carles Cufib662bc92022-08-24 15:42:55 +0200322def get_blob_status(path, sha256):
323 if not path.is_file():
324 return BLOB_NOT_PRESENT
325 with path.open('rb') as f:
326 m = hashlib.sha256()
327 m.update(f.read())
328 if sha256.lower() == m.hexdigest():
329 return BLOB_PRESENT
330 else:
331 return BLOB_OUTDATED
332
333
334def process_blobs(module, meta):
335 blobs = []
336 mblobs = meta.get('blobs', None)
337 if not mblobs:
338 return blobs
339
340 blobs_path = Path(module) / MODULE_BLOBS_PATH
341 for blob in mblobs:
342 blob['module'] = meta.get('name', None)
343 blob['abspath'] = blobs_path / Path(blob['path'])
344 blob['status'] = get_blob_status(blob['abspath'], blob['sha256'])
345 blobs.append(blob)
346
347 return blobs
348
349
Johan Hedberg5562bb92024-08-22 11:24:36 +0300350def kconfig_module_opts(name_sanitized, blobs, taint_blobs):
351 snippet = [f'config ZEPHYR_{name_sanitized.upper()}_MODULE',
352 ' bool',
353 ' default y']
354
355 if taint_blobs:
356 snippet += [' select TAINT_BLOBS']
357
358 if blobs:
359 snippet += [f'\nconfig ZEPHYR_{name_sanitized.upper()}_MODULE_BLOBS',
360 ' bool']
361 if taint_blobs:
362 snippet += [' default y']
363
364 return snippet
365
366
367def kconfig_snippet(meta, path, kconfig_file=None, blobs=False, taint_blobs=False, sysbuild=False):
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100368 name = meta['name']
369 name_sanitized = meta['name-sanitized']
370
Johan Hedberg5562bb92024-08-22 11:24:36 +0300371 snippet = [f'menu "{name} ({path.as_posix()})"']
Carles Cufi035dffc2022-08-24 16:56:01 +0200372
Johan Hedberg5562bb92024-08-22 11:24:36 +0300373 snippet += [f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file
374 else f'osource "$(SYSBUILD_{name_sanitized.upper()}_KCONFIG)"' if sysbuild is True
375 else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"']
376
377 snippet += kconfig_module_opts(name_sanitized, blobs, taint_blobs)
378
379 snippet += ['endmenu\n']
380
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100381 return '\n'.join(snippet)
382
383
Anas Nashif286a9ed2019-12-11 10:13:23 -0500384def process_kconfig(module, meta):
Carles Cufi035dffc2022-08-24 16:56:01 +0200385 blobs = process_blobs(module, meta)
Christophe Dufazaae9326c2024-07-15 15:48:13 +0200386 taint_blobs = any(b['status'] != BLOB_NOT_PRESENT for b in blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500387 section = meta.get('build', dict())
388 module_path = PurePath(module)
389 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100390 kconfig_extern = section.get('kconfig-ext', False)
391 if kconfig_extern:
Johan Hedberg5562bb92024-08-22 11:24:36 +0300392 return kconfig_snippet(meta, module_path, blobs=blobs, taint_blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500393
394 kconfig_setting = section.get('kconfig', None)
395 if not validate_setting(kconfig_setting, module):
396 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200397 'not point to a valid Kconfig file.'
398 .format(module_yml, kconfig_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500399
400 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
401 if os.path.isfile(kconfig_file):
Carles Cufi035dffc2022-08-24 16:56:01 +0200402 return kconfig_snippet(meta, module_path, Path(kconfig_file),
Johan Hedberg5562bb92024-08-22 11:24:36 +0300403 blobs=blobs, taint_blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500404 else:
Flavio Ceolin2e5c3712023-05-10 21:40:04 +0000405 name_sanitized = meta['name-sanitized']
Johan Hedberg5562bb92024-08-22 11:24:36 +0300406 snippet = kconfig_module_opts(name_sanitized, blobs, taint_blobs)
407 return '\n'.join(snippet) + '\n'
Anas Nashif286a9ed2019-12-11 10:13:23 -0500408
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100409
Jamie McCraedf9027a2023-02-20 10:00:38 +0000410def process_sysbuildkconfig(module, meta):
411 section = meta.get('build', dict())
412 module_path = PurePath(module)
413 module_yml = module_path.joinpath('zephyr/module.yml')
414 kconfig_extern = section.get('sysbuild-kconfig-ext', False)
415 if kconfig_extern:
416 return kconfig_snippet(meta, module_path, sysbuild=True)
417
418 kconfig_setting = section.get('sysbuild-kconfig', None)
419 if not validate_setting(kconfig_setting, module):
420 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
421 'not point to a valid Kconfig file.'
422 .format(module_yml, kconfig_setting))
423
Jamie McCraeacd14f82024-04-29 09:40:24 +0100424 if kconfig_setting is not None:
425 kconfig_file = os.path.join(module, kconfig_setting)
426 if os.path.isfile(kconfig_file):
427 return kconfig_snippet(meta, module_path, Path(kconfig_file))
Jamie McCraedf9027a2023-02-20 10:00:38 +0000428
Jamie McCraeacd14f82024-04-29 09:40:24 +0100429 name_sanitized = meta['name-sanitized']
430 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n'
431 f' bool\n'
432 f' default y\n')
Jamie McCraedf9027a2023-02-20 10:00:38 +0000433
434
Anas Nashif54713982020-12-07 14:52:10 -0500435def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500436
437 out = ""
438 tests = meta.get('tests', [])
439 samples = meta.get('samples', [])
440 boards = meta.get('boards', [])
441
442 for pth in tests + samples:
443 if pth:
444 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200445 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
446 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500447
448 for pth in boards:
449 if pth:
450 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200451 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
452 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500453
454 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100455
456
Thomas Gagneret0d053182024-01-18 17:31:46 +0100457def _create_meta_project(project_path):
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200458 def git_revision(path):
459 rc = subprocess.Popen(['git', 'rev-parse', '--is-inside-work-tree'],
460 stdout=subprocess.PIPE,
461 stderr=subprocess.PIPE,
462 cwd=path).wait()
463 if rc == 0:
464 # A git repo.
465 popen = subprocess.Popen(['git', 'rev-parse', 'HEAD'],
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 revision = stdout.rstrip()
474
475 rc = subprocess.Popen(['git', 'diff-index', '--quiet', 'HEAD',
476 '--'],
477 stdout=None,
478 stderr=None,
479 cwd=path).wait()
480 if rc:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100481 return revision + '-dirty', True
482 return revision, False
483 return None, False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200484
Thomas Gagneret0d053182024-01-18 17:31:46 +0100485 def git_remote(path):
486 popen = subprocess.Popen(['git', 'remote'],
487 stdout=subprocess.PIPE,
488 stderr=subprocess.PIPE,
489 cwd=path)
490 stdout, stderr = popen.communicate()
491 stdout = stdout.decode('utf-8')
492
493 remotes_name = []
494 if not (popen.returncode or stderr):
495 remotes_name = stdout.rstrip().split('\n')
496
497 remote_url = None
498
499 # If more than one remote, do not return any remote
500 if len(remotes_name) == 1:
501 remote = remotes_name[0]
502 popen = subprocess.Popen(['git', 'remote', 'get-url', remote],
503 stdout=subprocess.PIPE,
504 stderr=subprocess.PIPE,
505 cwd=path)
506 stdout, stderr = popen.communicate()
507 stdout = stdout.decode('utf-8')
508
509 if not (popen.returncode or stderr):
510 remote_url = stdout.rstrip()
511
512 return remote_url
513
514 def git_tags(path, revision):
515 if not revision or len(revision) == 0:
516 return None
517
518 popen = subprocess.Popen(['git', '-P', 'tag', '--points-at', revision],
519 stdout=subprocess.PIPE,
520 stderr=subprocess.PIPE,
521 cwd=path)
522 stdout, stderr = popen.communicate()
523 stdout = stdout.decode('utf-8')
524
525 tags = None
526 if not (popen.returncode or stderr):
527 tags = stdout.rstrip().splitlines()
528
529 return tags
530
531 workspace_dirty = False
532 path = PurePath(project_path).as_posix()
533
534 revision, dirty = git_revision(path)
535 workspace_dirty |= dirty
536 remote = git_remote(path)
537 tags = git_tags(path, revision)
538
539 meta_project = {'path': path,
540 'revision': revision}
541
542 if remote:
543 meta_project['remote'] = remote
544
545 if tags:
546 meta_project['tags'] = tags
547
548 return meta_project, workspace_dirty
549
550
551def _get_meta_project(meta_projects_list, project_path):
552 projects = [ prj for prj in meta_projects_list[1:] if prj["path"] == project_path ]
553
554 return projects[0] if len(projects) == 1 else None
555
556
557def process_meta(zephyr_base, west_projs, modules, extra_modules=None,
558 propagate_state=False):
559 # Process zephyr_base, projects, and modules and create a dictionary
560 # with meta information for each input.
561 #
562 # The dictionary will contain meta info in the following lists:
563 # - zephyr: path and revision
564 # - modules: name, path, and revision
565 # - west-projects: path and revision
566 #
567 # returns the dictionary with said lists
568
569 meta = {'zephyr': None, 'modules': None, 'workspace': None}
570
571 zephyr_project, zephyr_dirty = _create_meta_project(zephyr_base)
572 zephyr_off = zephyr_project.get("remote") is None
573
574 workspace_dirty = zephyr_dirty
575 workspace_extra = extra_modules is not None
576 workspace_off = zephyr_off
577
578 if zephyr_off:
579 zephyr_project['revision'] += '-off'
580
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100581 meta['zephyr'] = zephyr_project
582 meta['workspace'] = {}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200583
Carles Cufia580b3d2022-08-09 11:13:54 +0200584 if west_projs is not None:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100585 from west.manifest import MANIFEST_REV_BRANCH
Carles Cufia580b3d2022-08-09 11:13:54 +0200586 projects = west_projs['projects']
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200587 meta_projects = []
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100588
Thomas Gagneret0d053182024-01-18 17:31:46 +0100589 manifest_path = projects[0].posixpath
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100590
Thomas Gagneret0d053182024-01-18 17:31:46 +0100591 # Special treatment of manifest project
592 # Git information (remote/revision) are not provided by west for the Manifest (west.yml)
593 # To mitigate this, we check if we don't use the manifest from the zephyr repository or an other project.
594 # If it's from zephyr, reuse zephyr information
595 # If it's from an other project, ignore it, it will be added later
596 # If it's not found, we extract data manually (remote/revision) from the directory
597
598 manifest_project = None
599 manifest_dirty = False
600 manifest_off = False
601
602 if zephyr_base == manifest_path:
603 manifest_project = zephyr_project
604 manifest_dirty = zephyr_dirty
605 manifest_off = zephyr_off
606 elif not [ prj for prj in projects[1:] if prj.posixpath == manifest_path ]:
607 manifest_project, manifest_dirty = _create_meta_project(
608 projects[0].posixpath)
609 manifest_off = manifest_project.get("remote") is None
610 if manifest_off:
611 manifest_project["revision"] += "-off"
612
613 if manifest_project:
614 workspace_off |= manifest_off
615 workspace_dirty |= manifest_dirty
616 meta_projects.append(manifest_project)
617
618 # Iterates on all projects except the first one (manifest)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100619 for project in projects[1:]:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100620 meta_project, dirty = _create_meta_project(project.posixpath)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100621 workspace_dirty |= dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200622 meta_projects.append(meta_project)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100623
Thomas Gagneret0d053182024-01-18 17:31:46 +0100624 off = False
625 if not meta_project.get("remote") or project.sha(MANIFEST_REV_BRANCH) != meta_project['revision'].removesuffix("-dirty"):
626 off = True
627 if not meta_project.get('remote') or project.url != meta_project['remote']:
628 # Force manifest URL and set commit as 'off'
629 meta_project['url'] = project.url
630 off = True
631
632 if off:
633 meta_project['revision'] += '-off'
634 workspace_off |= off
635
636 # If manifest is in project, updates related variables
637 if project.posixpath == manifest_path:
638 manifest_dirty |= dirty
639 manifest_off |= off
640 manifest_project = meta_project
641
Carles Cufia580b3d2022-08-09 11:13:54 +0200642 meta.update({'west': {'manifest': west_projs['manifest_path'],
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100643 'projects': meta_projects}})
644 meta['workspace'].update({'off': workspace_off})
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200645
Thomas Gagneret0d053182024-01-18 17:31:46 +0100646 # Iterates on all modules
647 meta_modules = []
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200648 for module in modules:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100649 # Check if modules is not in projects
650 # It allows to have the "-off" flag since `modules` variable` does not provide URL/remote
651 meta_module = _get_meta_project(meta_projects, module.project)
652
653 if not meta_module:
654 meta_module, dirty = _create_meta_project(module.project)
655 workspace_dirty |= dirty
656
657 meta_module['name'] = module.meta.get('name')
658
659 if module.meta.get('security'):
660 meta_module['security'] = module.meta.get('security')
661 meta_modules.append(meta_module)
662
663 meta['modules'] = meta_modules
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200664
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100665 meta['workspace'].update({'dirty': workspace_dirty,
666 'extra': workspace_extra})
667
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100668 if propagate_state:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100669 zephyr_revision = zephyr_project['revision']
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100670 if workspace_dirty and not zephyr_dirty:
671 zephyr_revision += '-dirty'
672 if workspace_extra:
673 zephyr_revision += '-extra'
Thomas Gagneret0d053182024-01-18 17:31:46 +0100674 if workspace_off and not zephyr_off:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100675 zephyr_revision += '-off'
676 zephyr_project.update({'revision': zephyr_revision})
677
Carles Cufia580b3d2022-08-09 11:13:54 +0200678 if west_projs is not None:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100679 manifest_revision = manifest_project['revision']
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100680 if workspace_dirty and not manifest_dirty:
681 manifest_revision += '-dirty'
682 if workspace_extra:
683 manifest_revision += '-extra'
Thomas Gagneret0d053182024-01-18 17:31:46 +0100684 if workspace_off and not manifest_off:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100685 manifest_revision += '-off'
686 manifest_project.update({'revision': manifest_revision})
687
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200688 return meta
689
690
Thomas Gagneret0d053182024-01-18 17:31:46 +0100691def west_projects(manifest=None):
Carles Cufia580b3d2022-08-09 11:13:54 +0200692 manifest_path = None
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200693 projects = []
694 # West is imported here, as it is optional
695 # (and thus maybe not installed)
696 # if user is providing a specific modules list.
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600697 try:
Martí Bolívar251f2692023-05-19 11:22:01 -0700698 from west.manifest import Manifest
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600699 except ImportError:
700 # West is not installed, so don't return any projects.
701 return None
702
Martí Bolívar251f2692023-05-19 11:22:01 -0700703 # If west *is* installed, we need all of the following imports to
704 # work. West versions that are excessively old may fail here:
705 # west.configuration.MalformedConfig was
706 # west.manifest.MalformedConfig until west v0.14.0, for example.
707 # These should be hard errors.
708 from west.manifest import \
709 ManifestImportFailed, MalformedManifest, ManifestVersionError
710 from west.configuration import MalformedConfig
711 from west.util import WestNotFound
712 from west.version import __version__ as WestVersion
713
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200714 from packaging import version
715 try:
Carles Cufia580b3d2022-08-09 11:13:54 +0200716 if not manifest:
717 manifest = Manifest.from_file()
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200718 if version.parse(WestVersion) >= version.parse('0.9.0'):
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100719 projects = [p for p in manifest.get_projects([])
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200720 if manifest.is_active(p)]
721 else:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100722 projects = manifest.get_projects([])
Carles Cufia580b3d2022-08-09 11:13:54 +0200723 manifest_path = manifest.path
724 return {'manifest_path': manifest_path, 'projects': projects}
Martí Bolívarce2a7d92023-05-18 13:21:34 -0700725 except (ManifestImportFailed, MalformedManifest,
726 ManifestVersionError, MalformedConfig) as e:
727 sys.exit(f'ERROR: {e}')
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200728 except WestNotFound:
729 # Only accept WestNotFound, meaning we are not in a west
730 # workspace. Such setup is allowed, as west may be installed
731 # but the project is not required to use west.
732 pass
733 return None
734
735
Carles Cufia580b3d2022-08-09 11:13:54 +0200736def parse_modules(zephyr_base, manifest=None, west_projs=None, modules=None,
737 extra_modules=None):
738
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200739 if modules is None:
Carles Cufia580b3d2022-08-09 11:13:54 +0200740 west_projs = west_projs or west_projects(manifest)
741 modules = ([p.posixpath for p in west_projs['projects']]
742 if west_projs else [])
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100743
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200744 if extra_modules is None:
745 extra_modules = []
Anas Nashif286a9ed2019-12-11 10:13:23 -0500746
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200747 Module = namedtuple('Module', ['project', 'meta', 'depends'])
Damian Krolika5051482022-03-01 18:36:16 +0100748
749 all_modules_by_name = {}
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200750 # dep_modules is a list of all modules that has an unresolved dependency
751 dep_modules = []
752 # start_modules is a list modules with no depends left (no incoming edge)
753 start_modules = []
754 # sorted_modules is a topological sorted list of the modules
755 sorted_modules = []
756
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200757 for project in modules + extra_modules:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500758 # Avoid including Zephyr base project as module.
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200759 if project == zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500760 continue
761
762 meta = process_module(project)
763 if meta:
Damian Krolika5051482022-03-01 18:36:16 +0100764 depends = meta.get('build', {}).get('depends', [])
765 all_modules_by_name[meta['name']] = Module(project, meta, depends)
766
Martí Bolívar34346c42020-01-29 12:38:58 -0800767 elif project in extra_modules:
768 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
769 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500770
Damian Krolika5051482022-03-01 18:36:16 +0100771 for module in all_modules_by_name.values():
772 if not module.depends:
773 start_modules.append(module)
774 else:
775 dep_modules.append(module)
776
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200777 # This will do a topological sort to ensure the modules are ordered
778 # according to dependency settings.
779 while start_modules:
780 node = start_modules.pop(0)
781 sorted_modules.append(node)
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100782 node_name = node.meta['name']
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200783 to_remove = []
784 for module in dep_modules:
785 if node_name in module.depends:
786 module.depends.remove(node_name)
787 if not module.depends:
788 start_modules.append(module)
789 to_remove.append(module)
790 for module in to_remove:
791 dep_modules.remove(module)
792
793 if dep_modules:
794 # If there are any modules with unresolved dependencies, then the
795 # modules contains unmet or cyclic dependencies. Error out.
796 error = 'Unmet or cyclic dependencies in modules:\n'
797 for module in dep_modules:
798 error += f'{module.project} depends on: {module.depends}\n'
799 sys.exit(error)
800
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200801 return sorted_modules
802
803
804def main():
805 parser = argparse.ArgumentParser(description='''
806 Process a list of projects and create Kconfig / CMake include files for
Jamie McCraeec704442023-01-04 16:08:36 +0000807 projects which are also a Zephyr module''', allow_abbrev=False)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200808
809 parser.add_argument('--kconfig-out',
810 help="""File to write with resulting KConfig import
811 statements.""")
812 parser.add_argument('--twister-out',
813 help="""File to write with resulting twister
814 parameters.""")
815 parser.add_argument('--cmake-out',
816 help="""File to write with resulting <name>:<path>
817 values to use for including in CMake""")
Jamie McCraedf9027a2023-02-20 10:00:38 +0000818 parser.add_argument('--sysbuild-kconfig-out',
819 help="""File to write with resulting KConfig import
820 statements.""")
821 parser.add_argument('--sysbuild-cmake-out',
822 help="""File to write with resulting <name>:<path>
823 values to use for including in CMake""")
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200824 parser.add_argument('--meta-out',
825 help="""Write a build meta YaML file containing a list
826 of Zephyr modules and west projects.
827 If a module or project is also a git repository
828 the current SHA revision will also be written.""")
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100829 parser.add_argument('--meta-state-propagate', action='store_true',
830 help="""Propagate state of modules and west projects
831 to the suffix of the Zephyr SHA and if west is
832 used, to the suffix of the manifest SHA""")
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200833 parser.add_argument('--settings-out',
834 help="""File to write with resulting <name>:<value>
835 values to use for including in CMake""")
836 parser.add_argument('-m', '--modules', nargs='+',
837 help="""List of modules to parse instead of using `west
838 list`""")
839 parser.add_argument('-x', '--extra-modules', nargs='+',
840 help='List of extra modules to parse')
841 parser.add_argument('-z', '--zephyr-base',
842 help='Path to zephyr repository')
843 args = parser.parse_args()
844
845 kconfig = ""
846 cmake = ""
Jamie McCraedf9027a2023-02-20 10:00:38 +0000847 sysbuild_kconfig = ""
848 sysbuild_cmake = ""
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200849 settings = ""
850 twister = ""
851
Carles Cufia580b3d2022-08-09 11:13:54 +0200852 west_projs = west_projects()
853 modules = parse_modules(args.zephyr_base, None, west_projs,
854 args.modules, args.extra_modules)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200855
856 for module in modules:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200857 kconfig += process_kconfig(module.project, module.meta)
858 cmake += process_cmake(module.project, module.meta)
Thomas Gagneret0d053182024-01-18 17:31:46 +0100859 sysbuild_kconfig += process_sysbuildkconfig(
860 module.project, module.meta)
Jamie McCraedf9027a2023-02-20 10:00:38 +0000861 sysbuild_cmake += process_sysbuildcmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200862 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500863 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200864
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100865 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500866 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
867 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100868
869 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500870 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
871 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100872
Jamie McCraedf9027a2023-02-20 10:00:38 +0000873 if args.sysbuild_kconfig_out:
874 with open(args.sysbuild_kconfig_out, 'w', encoding="utf-8") as fp:
875 fp.write(sysbuild_kconfig)
876
877 if args.sysbuild_cmake_out:
878 with open(args.sysbuild_cmake_out, 'w', encoding="utf-8") as fp:
879 fp.write(sysbuild_cmake)
880
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200881 if args.settings_out:
882 with open(args.settings_out, 'w', encoding="utf-8") as fp:
Martí Bolívar361956b2021-08-09 10:46:17 -0700883 fp.write('''\
884# WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!
885#
886# This file contains build system settings derived from your modules.
887#
888# Modules may be set via ZEPHYR_MODULES, ZEPHYR_EXTRA_MODULES,
889# and/or the west manifest file.
890#
891# See the Modules guide for more information.
892''')
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200893 fp.write(settings)
894
Anas Nashif54713982020-12-07 14:52:10 -0500895 if args.twister_out:
896 with open(args.twister_out, 'w', encoding="utf-8") as fp:
897 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100898
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200899 if args.meta_out:
Carles Cufia580b3d2022-08-09 11:13:54 +0200900 meta = process_meta(args.zephyr_base, west_projs, modules,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100901 args.extra_modules, args.meta_state_propagate)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100902
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200903 with open(args.meta_out, 'w', encoding="utf-8") as fp:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100904 # Ignore references and insert data instead
905 yaml.Dumper.ignore_aliases = lambda self, data: True
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200906 fp.write(yaml.dump(meta))
907
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200908
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100909if __name__ == "__main__":
910 main()