blob: 93928f65a1b8291ee8fcbf5679430bed53e61b0b [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
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100164'''
165
Carles Cufi57e5cd42022-08-11 17:37:34 +0200166MODULE_YML_PATH = PurePath('zephyr/module.yml')
Carles Cufi336aa9d2022-08-04 19:08:22 +0200167# Path to the blobs folder
168MODULE_BLOBS_PATH = PurePath('zephyr/blobs')
Carles Cufib662bc92022-08-24 15:42:55 +0200169BLOB_PRESENT = 'A'
170BLOB_NOT_PRESENT = 'D'
171BLOB_OUTDATED = 'M'
Carles Cufi57e5cd42022-08-11 17:37:34 +0200172
Lukasz Mrugalab2f43212024-04-19 10:37:20 +0000173schema = yaml.load(METADATA_SCHEMA, Loader=SafeLoader)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100174
175
176def validate_setting(setting, module_path, filename=None):
177 if setting is not None:
178 if filename is not None:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600179 checkfile = Path(module_path) / setting / filename
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100180 else:
Jack Rosenthalf9593092023-08-09 16:24:14 -0600181 checkfile = Path(module_path) / setting
182 if not checkfile.resolve().is_file():
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100183 return False
184 return True
185
186
Anas Nashif286a9ed2019-12-11 10:13:23 -0500187def process_module(module):
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100188 module_path = PurePath(module)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500189
Carles Cufi57e5cd42022-08-11 17:37:34 +0200190 # The input is a module if zephyr/module.{yml,yaml} is a valid yaml file
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100191 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present.
192
Carles Cufi57e5cd42022-08-11 17:37:34 +0200193 for module_yml in [module_path / MODULE_YML_PATH,
194 module_path / MODULE_YML_PATH.with_suffix('.yaml')]:
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200195 if Path(module_yml).is_file():
196 with Path(module_yml).open('r') as f:
Lukasz Mrugalab2f43212024-04-19 10:37:20 +0000197 meta = yaml.load(f.read(), Loader=SafeLoader)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100198
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200199 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))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100205
Alexander Mihajlovic099c1262022-03-31 08:04:45 +0200206 meta['name'] = meta.get('name', module_path.name)
207 meta['name-sanitized'] = re.sub('[^a-zA-Z0-9]', '_', meta['name'])
208 return meta
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100209
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100210 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \
211 Path(module_path.joinpath('zephyr/Kconfig')).is_file():
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100212 return {'name': module_path.name,
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100213 'name-sanitized': re.sub('[^a-zA-Z0-9]', '_', module_path.name),
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100214 'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}}
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100215
Anas Nashif286a9ed2019-12-11 10:13:23 -0500216 return None
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100217
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100218
Anas Nashif286a9ed2019-12-11 10:13:23 -0500219def process_cmake(module, meta):
220 section = meta.get('build', dict())
221 module_path = PurePath(module)
222 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100223
224 cmake_extern = section.get('cmake-ext', False)
225 if cmake_extern:
226 return('\"{}\":\"{}\":\"{}\"\n'
Andrzej Głąbek02819482021-01-19 07:39:47 +0100227 .format(meta['name'],
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100228 module_path.as_posix(),
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100229 "${ZEPHYR_" + meta['name-sanitized'].upper() + "_CMAKE_DIR}"))
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100230
Anas Nashif286a9ed2019-12-11 10:13:23 -0500231 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 '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200234 'does not contain a CMakeLists.txt file.'
235 .format(module_yml.as_posix(), cmake_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500236
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):
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200240 return('\"{}\":\"{}\":\"{}\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100241 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200242 module_path.as_posix(),
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200243 Path(cmake_path).resolve().as_posix()))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500244 else:
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200245 return('\"{}\":\"{}\":\"\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100246 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200247 module_path.as_posix()))
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200248
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100249
Jamie McCraedf9027a2023-02-20 10:00:38 +0000250def 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
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200284def 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:
Marti Bolivar3f282da02023-01-07 15:25:10 -0800290 for root in ['board', 'dts', 'snippet', 'soc', 'arch', 'module_ext', 'sca']:
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200291 setting = build_settings.get(root+'_root', None)
292 if setting is not None:
293 root_path = PurePath(module) / setting
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100294 out_text += f'"{root.upper()}_ROOT":'
295 out_text += f'"{root_path.as_posix()}"\n'
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200296
297 return out_text
298
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200299
Carles Cufib662bc92022-08-24 15:42:55 +0200300def 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
Jamie McCraedf9027a2023-02-20 10:00:38 +0000328def kconfig_snippet(meta, path, kconfig_file=None, blobs=False, sysbuild=False):
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100329 name = meta['name']
330 name_sanitized = meta['name-sanitized']
331
Carles Cufi035dffc2022-08-24 16:56:01 +0200332 snippet = [f'menu "{name} ({path.as_posix()})"',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100333 f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file
Jamie McCraedf9027a2023-02-20 10:00:38 +0000334 else f'osource "$(SYSBUILD_{name_sanitized.upper()}_KCONFIG)"' if sysbuild is True
335 else f'osource "$(ZEPHYR_{name_sanitized.upper()}_KCONFIG)"',
Torsten Rasmussen3d880832021-01-19 12:01:38 +0100336 f'config ZEPHYR_{name_sanitized.upper()}_MODULE',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100337 ' bool',
338 ' default y',
Carles Cufi035dffc2022-08-24 16:56:01 +0200339 'endmenu\n']
340
341 if blobs:
342 snippet.insert(-1, ' select TAINT_BLOBS')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100343 return '\n'.join(snippet)
344
345
Anas Nashif286a9ed2019-12-11 10:13:23 -0500346def process_kconfig(module, meta):
Carles Cufi035dffc2022-08-24 16:56:01 +0200347 blobs = process_blobs(module, meta)
Christophe Dufazaae9326c2024-07-15 15:48:13 +0200348 taint_blobs = any(b['status'] != BLOB_NOT_PRESENT for b in blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500349 section = meta.get('build', dict())
350 module_path = PurePath(module)
351 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100352 kconfig_extern = section.get('kconfig-ext', False)
353 if kconfig_extern:
Carles Cufi035dffc2022-08-24 16:56:01 +0200354 return kconfig_snippet(meta, module_path, blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500355
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 '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200359 'not point to a valid Kconfig file.'
360 .format(module_yml, kconfig_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500361
362 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
363 if os.path.isfile(kconfig_file):
Carles Cufi035dffc2022-08-24 16:56:01 +0200364 return kconfig_snippet(meta, module_path, Path(kconfig_file),
365 blobs=taint_blobs)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500366 else:
Flavio Ceolin2e5c3712023-05-10 21:40:04 +0000367 name_sanitized = meta['name-sanitized']
368 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n'
369 f' bool\n'
370 f' default y\n')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500371
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100372
Jamie McCraedf9027a2023-02-20 10:00:38 +0000373def 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
Jamie McCraeacd14f82024-04-29 09:40:24 +0100387 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))
Jamie McCraedf9027a2023-02-20 10:00:38 +0000391
Jamie McCraeacd14f82024-04-29 09:40:24 +0100392 name_sanitized = meta['name-sanitized']
393 return (f'config ZEPHYR_{name_sanitized.upper()}_MODULE\n'
394 f' bool\n'
395 f' default y\n')
Jamie McCraedf9027a2023-02-20 10:00:38 +0000396
397
Anas Nashif54713982020-12-07 14:52:10 -0500398def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500399
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)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200408 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
409 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500410
411 for pth in boards:
412 if pth:
413 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200414 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
415 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500416
417 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100418
419
Thomas Gagneret0d053182024-01-18 17:31:46 +0100420def _create_meta_project(project_path):
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200421 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:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100444 return revision + '-dirty', True
445 return revision, False
446 return None, False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200447
Thomas Gagneret0d053182024-01-18 17:31:46 +0100448 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
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100544 meta['zephyr'] = zephyr_project
545 meta['workspace'] = {}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200546
Carles Cufia580b3d2022-08-09 11:13:54 +0200547 if west_projs is not None:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100548 from west.manifest import MANIFEST_REV_BRANCH
Carles Cufia580b3d2022-08-09 11:13:54 +0200549 projects = west_projs['projects']
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200550 meta_projects = []
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100551
Thomas Gagneret0d053182024-01-18 17:31:46 +0100552 manifest_path = projects[0].posixpath
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100553
Thomas Gagneret0d053182024-01-18 17:31:46 +0100554 # 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)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100582 for project in projects[1:]:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100583 meta_project, dirty = _create_meta_project(project.posixpath)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100584 workspace_dirty |= dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200585 meta_projects.append(meta_project)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100586
Thomas Gagneret0d053182024-01-18 17:31:46 +0100587 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
Carles Cufia580b3d2022-08-09 11:13:54 +0200605 meta.update({'west': {'manifest': west_projs['manifest_path'],
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100606 'projects': meta_projects}})
607 meta['workspace'].update({'off': workspace_off})
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200608
Thomas Gagneret0d053182024-01-18 17:31:46 +0100609 # Iterates on all modules
610 meta_modules = []
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200611 for module in modules:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100612 # 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
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200627
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100628 meta['workspace'].update({'dirty': workspace_dirty,
629 'extra': workspace_extra})
630
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100631 if propagate_state:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100632 zephyr_revision = zephyr_project['revision']
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100633 if workspace_dirty and not zephyr_dirty:
634 zephyr_revision += '-dirty'
635 if workspace_extra:
636 zephyr_revision += '-extra'
Thomas Gagneret0d053182024-01-18 17:31:46 +0100637 if workspace_off and not zephyr_off:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100638 zephyr_revision += '-off'
639 zephyr_project.update({'revision': zephyr_revision})
640
Carles Cufia580b3d2022-08-09 11:13:54 +0200641 if west_projs is not None:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100642 manifest_revision = manifest_project['revision']
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100643 if workspace_dirty and not manifest_dirty:
644 manifest_revision += '-dirty'
645 if workspace_extra:
646 manifest_revision += '-extra'
Thomas Gagneret0d053182024-01-18 17:31:46 +0100647 if workspace_off and not manifest_off:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100648 manifest_revision += '-off'
649 manifest_project.update({'revision': manifest_revision})
650
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200651 return meta
652
653
Thomas Gagneret0d053182024-01-18 17:31:46 +0100654def west_projects(manifest=None):
Carles Cufia580b3d2022-08-09 11:13:54 +0200655 manifest_path = None
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200656 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.
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600660 try:
Martí Bolívar251f2692023-05-19 11:22:01 -0700661 from west.manifest import Manifest
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600662 except ImportError:
663 # West is not installed, so don't return any projects.
664 return None
665
Martí Bolívar251f2692023-05-19 11:22:01 -0700666 # 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
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200677 from packaging import version
678 try:
Carles Cufia580b3d2022-08-09 11:13:54 +0200679 if not manifest:
680 manifest = Manifest.from_file()
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200681 if version.parse(WestVersion) >= version.parse('0.9.0'):
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100682 projects = [p for p in manifest.get_projects([])
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200683 if manifest.is_active(p)]
684 else:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100685 projects = manifest.get_projects([])
Carles Cufia580b3d2022-08-09 11:13:54 +0200686 manifest_path = manifest.path
687 return {'manifest_path': manifest_path, 'projects': projects}
Martí Bolívarce2a7d92023-05-18 13:21:34 -0700688 except (ManifestImportFailed, MalformedManifest,
689 ManifestVersionError, MalformedConfig) as e:
690 sys.exit(f'ERROR: {e}')
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200691 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
Carles Cufia580b3d2022-08-09 11:13:54 +0200699def parse_modules(zephyr_base, manifest=None, west_projs=None, modules=None,
700 extra_modules=None):
701
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200702 if modules is None:
Carles Cufia580b3d2022-08-09 11:13:54 +0200703 west_projs = west_projs or west_projects(manifest)
704 modules = ([p.posixpath for p in west_projs['projects']]
705 if west_projs else [])
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100706
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200707 if extra_modules is None:
708 extra_modules = []
Anas Nashif286a9ed2019-12-11 10:13:23 -0500709
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200710 Module = namedtuple('Module', ['project', 'meta', 'depends'])
Damian Krolika5051482022-03-01 18:36:16 +0100711
712 all_modules_by_name = {}
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200713 # 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
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200720 for project in modules + extra_modules:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500721 # Avoid including Zephyr base project as module.
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200722 if project == zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500723 continue
724
725 meta = process_module(project)
726 if meta:
Damian Krolika5051482022-03-01 18:36:16 +0100727 depends = meta.get('build', {}).get('depends', [])
728 all_modules_by_name[meta['name']] = Module(project, meta, depends)
729
Martí Bolívar34346c42020-01-29 12:38:58 -0800730 elif project in extra_modules:
731 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
732 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500733
Damian Krolika5051482022-03-01 18:36:16 +0100734 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
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200740 # 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)
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100745 node_name = node.meta['name']
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200746 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
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200764 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
Jamie McCraeec704442023-01-04 16:08:36 +0000770 projects which are also a Zephyr module''', allow_abbrev=False)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200771
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""")
Jamie McCraedf9027a2023-02-20 10:00:38 +0000781 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""")
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200787 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.""")
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100792 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""")
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200796 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 = ""
Jamie McCraedf9027a2023-02-20 10:00:38 +0000810 sysbuild_kconfig = ""
811 sysbuild_cmake = ""
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200812 settings = ""
813 twister = ""
814
Carles Cufia580b3d2022-08-09 11:13:54 +0200815 west_projs = west_projects()
816 modules = parse_modules(args.zephyr_base, None, west_projs,
817 args.modules, args.extra_modules)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200818
819 for module in modules:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200820 kconfig += process_kconfig(module.project, module.meta)
821 cmake += process_cmake(module.project, module.meta)
Thomas Gagneret0d053182024-01-18 17:31:46 +0100822 sysbuild_kconfig += process_sysbuildkconfig(
823 module.project, module.meta)
Jamie McCraedf9027a2023-02-20 10:00:38 +0000824 sysbuild_cmake += process_sysbuildcmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200825 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500826 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200827
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100828 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500829 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
830 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100831
832 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500833 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
834 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100835
Jamie McCraedf9027a2023-02-20 10:00:38 +0000836 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
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200844 if args.settings_out:
845 with open(args.settings_out, 'w', encoding="utf-8") as fp:
Martí Bolívar361956b2021-08-09 10:46:17 -0700846 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''')
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200856 fp.write(settings)
857
Anas Nashif54713982020-12-07 14:52:10 -0500858 if args.twister_out:
859 with open(args.twister_out, 'w', encoding="utf-8") as fp:
860 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100861
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200862 if args.meta_out:
Carles Cufia580b3d2022-08-09 11:13:54 +0200863 meta = process_meta(args.zephyr_base, west_projs, modules,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100864 args.extra_modules, args.meta_state_propagate)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100865
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200866 with open(args.meta_out, 'w', encoding="utf-8") as fp:
Thomas Gagneret0d053182024-01-18 17:31:46 +0100867 # Ignore references and insert data instead
868 yaml.Dumper.ignore_aliases = lambda self, data: True
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200869 fp.write(yaml.dump(meta))
870
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200871
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100872if __name__ == "__main__":
873 main()