blob: ab27eaa57063dfc10fe1607739be1fab300d97d4 [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
373 if kconfig_setting is None:
374 return ""
375
376 kconfig_file = os.path.join(module, kconfig_setting)
377 if os.path.isfile(kconfig_file):
378 return kconfig_snippet(meta, module_path, Path(kconfig_file))
379 else:
380 return ""
381
382
Anas Nashif54713982020-12-07 14:52:10 -0500383def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500384
385 out = ""
386 tests = meta.get('tests', [])
387 samples = meta.get('samples', [])
388 boards = meta.get('boards', [])
389
390 for pth in tests + samples:
391 if pth:
392 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200393 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
394 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500395
396 for pth in boards:
397 if pth:
398 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200399 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
400 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500401
402 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100403
404
Carles Cufia580b3d2022-08-09 11:13:54 +0200405def process_meta(zephyr_base, west_projs, modules, extra_modules=None,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100406 propagate_state=False):
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200407 # Process zephyr_base, projects, and modules and create a dictionary
408 # with meta information for each input.
409 #
410 # The dictionary will contain meta info in the following lists:
411 # - zephyr: path and revision
412 # - modules: name, path, and revision
413 # - west-projects: path and revision
414 #
415 # returns the dictionary with said lists
416
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100417 meta = {'zephyr': None, 'modules': None, 'workspace': None}
418
419 workspace_dirty = False
420 workspace_extra = extra_modules is not None
421 workspace_off = False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200422
423 def git_revision(path):
424 rc = subprocess.Popen(['git', 'rev-parse', '--is-inside-work-tree'],
425 stdout=subprocess.PIPE,
426 stderr=subprocess.PIPE,
427 cwd=path).wait()
428 if rc == 0:
429 # A git repo.
430 popen = subprocess.Popen(['git', 'rev-parse', 'HEAD'],
431 stdout=subprocess.PIPE,
432 stderr=subprocess.PIPE,
433 cwd=path)
434 stdout, stderr = popen.communicate()
435 stdout = stdout.decode('utf-8')
436
437 if not (popen.returncode or stderr):
438 revision = stdout.rstrip()
439
440 rc = subprocess.Popen(['git', 'diff-index', '--quiet', 'HEAD',
441 '--'],
442 stdout=None,
443 stderr=None,
444 cwd=path).wait()
445 if rc:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100446 return revision + '-dirty', True
447 return revision, False
448 return None, False
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200449
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100450 zephyr_revision, zephyr_dirty = git_revision(zephyr_base)
451 zephyr_project = {'path': zephyr_base,
452 'revision': zephyr_revision}
453 meta['zephyr'] = zephyr_project
454 meta['workspace'] = {}
455 workspace_dirty |= zephyr_dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200456
Carles Cufia580b3d2022-08-09 11:13:54 +0200457 if west_projs is not None:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100458 from west.manifest import MANIFEST_REV_BRANCH
Carles Cufia580b3d2022-08-09 11:13:54 +0200459 projects = west_projs['projects']
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200460 meta_projects = []
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100461
462 # Special treatment of manifest project.
Carles Cufia580b3d2022-08-09 11:13:54 +0200463 manifest_proj_path = PurePath(projects[0].posixpath).as_posix()
464 manifest_revision, manifest_dirty = git_revision(manifest_proj_path)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100465 workspace_dirty |= manifest_dirty
Carles Cufia580b3d2022-08-09 11:13:54 +0200466 manifest_project = {'path': manifest_proj_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100467 'revision': manifest_revision}
468 meta_projects.append(manifest_project)
469
470 for project in projects[1:]:
471 project_path = PurePath(project.posixpath).as_posix()
472 revision, dirty = git_revision(project_path)
473 workspace_dirty |= dirty
474 if project.sha(MANIFEST_REV_BRANCH) != revision:
475 revision += '-off'
476 workspace_off = True
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200477 meta_project = {'path': project_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100478 'revision': revision}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200479 meta_projects.append(meta_project)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100480
Carles Cufia580b3d2022-08-09 11:13:54 +0200481 meta.update({'west': {'manifest': west_projs['manifest_path'],
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100482 'projects': meta_projects}})
483 meta['workspace'].update({'off': workspace_off})
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200484
485 meta_projects = []
486 for module in modules:
487 module_path = PurePath(module.project).as_posix()
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100488 revision, dirty = git_revision(module_path)
489 workspace_dirty |= dirty
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200490 meta_project = {'name': module.meta['name'],
491 'path': module_path,
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100492 'revision': revision}
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200493 meta_projects.append(meta_project)
494 meta['modules'] = meta_projects
495
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100496 meta['workspace'].update({'dirty': workspace_dirty,
497 'extra': workspace_extra})
498
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100499 if propagate_state:
500 if workspace_dirty and not zephyr_dirty:
501 zephyr_revision += '-dirty'
502 if workspace_extra:
503 zephyr_revision += '-extra'
504 if workspace_off:
505 zephyr_revision += '-off'
506 zephyr_project.update({'revision': zephyr_revision})
507
Carles Cufia580b3d2022-08-09 11:13:54 +0200508 if west_projs is not None:
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100509 if workspace_dirty and not manifest_dirty:
510 manifest_revision += '-dirty'
511 if workspace_extra:
512 manifest_revision += '-extra'
513 if workspace_off:
514 manifest_revision += '-off'
515 manifest_project.update({'revision': manifest_revision})
516
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200517 return meta
518
519
Carles Cufia580b3d2022-08-09 11:13:54 +0200520def west_projects(manifest = None):
521 manifest_path = None
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200522 projects = []
523 # West is imported here, as it is optional
524 # (and thus maybe not installed)
525 # if user is providing a specific modules list.
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600526 try:
Martí Bolívar251f2692023-05-19 11:22:01 -0700527 from west.manifest import Manifest
Tristan Honscheiddbbe8022022-05-06 11:47:47 -0600528 except ImportError:
529 # West is not installed, so don't return any projects.
530 return None
531
Martí Bolívar251f2692023-05-19 11:22:01 -0700532 # If west *is* installed, we need all of the following imports to
533 # work. West versions that are excessively old may fail here:
534 # west.configuration.MalformedConfig was
535 # west.manifest.MalformedConfig until west v0.14.0, for example.
536 # These should be hard errors.
537 from west.manifest import \
538 ManifestImportFailed, MalformedManifest, ManifestVersionError
539 from west.configuration import MalformedConfig
540 from west.util import WestNotFound
541 from west.version import __version__ as WestVersion
542
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200543 from packaging import version
544 try:
Carles Cufia580b3d2022-08-09 11:13:54 +0200545 if not manifest:
546 manifest = Manifest.from_file()
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200547 if version.parse(WestVersion) >= version.parse('0.9.0'):
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100548 projects = [p for p in manifest.get_projects([])
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200549 if manifest.is_active(p)]
550 else:
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100551 projects = manifest.get_projects([])
Carles Cufia580b3d2022-08-09 11:13:54 +0200552 manifest_path = manifest.path
553 return {'manifest_path': manifest_path, 'projects': projects}
Martí Bolívarce2a7d92023-05-18 13:21:34 -0700554 except (ManifestImportFailed, MalformedManifest,
555 ManifestVersionError, MalformedConfig) as e:
556 sys.exit(f'ERROR: {e}')
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200557 except WestNotFound:
558 # Only accept WestNotFound, meaning we are not in a west
559 # workspace. Such setup is allowed, as west may be installed
560 # but the project is not required to use west.
561 pass
562 return None
563
564
Carles Cufia580b3d2022-08-09 11:13:54 +0200565def parse_modules(zephyr_base, manifest=None, west_projs=None, modules=None,
566 extra_modules=None):
567
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200568 if modules is None:
Carles Cufia580b3d2022-08-09 11:13:54 +0200569 west_projs = west_projs or west_projects(manifest)
570 modules = ([p.posixpath for p in west_projs['projects']]
571 if west_projs else [])
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100572
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200573 if extra_modules is None:
574 extra_modules = []
Anas Nashif286a9ed2019-12-11 10:13:23 -0500575
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200576 Module = namedtuple('Module', ['project', 'meta', 'depends'])
Damian Krolika5051482022-03-01 18:36:16 +0100577
578 all_modules_by_name = {}
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200579 # dep_modules is a list of all modules that has an unresolved dependency
580 dep_modules = []
581 # start_modules is a list modules with no depends left (no incoming edge)
582 start_modules = []
583 # sorted_modules is a topological sorted list of the modules
584 sorted_modules = []
585
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200586 for project in modules + extra_modules:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500587 # Avoid including Zephyr base project as module.
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200588 if project == zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500589 continue
590
591 meta = process_module(project)
592 if meta:
Damian Krolika5051482022-03-01 18:36:16 +0100593 depends = meta.get('build', {}).get('depends', [])
594 all_modules_by_name[meta['name']] = Module(project, meta, depends)
595
Martí Bolívar34346c42020-01-29 12:38:58 -0800596 elif project in extra_modules:
597 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
598 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500599
Damian Krolika5051482022-03-01 18:36:16 +0100600 for module in all_modules_by_name.values():
601 if not module.depends:
602 start_modules.append(module)
603 else:
604 dep_modules.append(module)
605
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200606 # This will do a topological sort to ensure the modules are ordered
607 # according to dependency settings.
608 while start_modules:
609 node = start_modules.pop(0)
610 sorted_modules.append(node)
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100611 node_name = node.meta['name']
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200612 to_remove = []
613 for module in dep_modules:
614 if node_name in module.depends:
615 module.depends.remove(node_name)
616 if not module.depends:
617 start_modules.append(module)
618 to_remove.append(module)
619 for module in to_remove:
620 dep_modules.remove(module)
621
622 if dep_modules:
623 # If there are any modules with unresolved dependencies, then the
624 # modules contains unmet or cyclic dependencies. Error out.
625 error = 'Unmet or cyclic dependencies in modules:\n'
626 for module in dep_modules:
627 error += f'{module.project} depends on: {module.depends}\n'
628 sys.exit(error)
629
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200630 return sorted_modules
631
632
633def main():
634 parser = argparse.ArgumentParser(description='''
635 Process a list of projects and create Kconfig / CMake include files for
Jamie McCraeec704442023-01-04 16:08:36 +0000636 projects which are also a Zephyr module''', allow_abbrev=False)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200637
638 parser.add_argument('--kconfig-out',
639 help="""File to write with resulting KConfig import
640 statements.""")
641 parser.add_argument('--twister-out',
642 help="""File to write with resulting twister
643 parameters.""")
644 parser.add_argument('--cmake-out',
645 help="""File to write with resulting <name>:<path>
646 values to use for including in CMake""")
Jamie McCraedf9027a2023-02-20 10:00:38 +0000647 parser.add_argument('--sysbuild-kconfig-out',
648 help="""File to write with resulting KConfig import
649 statements.""")
650 parser.add_argument('--sysbuild-cmake-out',
651 help="""File to write with resulting <name>:<path>
652 values to use for including in CMake""")
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200653 parser.add_argument('--meta-out',
654 help="""Write a build meta YaML file containing a list
655 of Zephyr modules and west projects.
656 If a module or project is also a git repository
657 the current SHA revision will also be written.""")
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100658 parser.add_argument('--meta-state-propagate', action='store_true',
659 help="""Propagate state of modules and west projects
660 to the suffix of the Zephyr SHA and if west is
661 used, to the suffix of the manifest SHA""")
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200662 parser.add_argument('--settings-out',
663 help="""File to write with resulting <name>:<value>
664 values to use for including in CMake""")
665 parser.add_argument('-m', '--modules', nargs='+',
666 help="""List of modules to parse instead of using `west
667 list`""")
668 parser.add_argument('-x', '--extra-modules', nargs='+',
669 help='List of extra modules to parse')
670 parser.add_argument('-z', '--zephyr-base',
671 help='Path to zephyr repository')
672 args = parser.parse_args()
673
674 kconfig = ""
675 cmake = ""
Jamie McCraedf9027a2023-02-20 10:00:38 +0000676 sysbuild_kconfig = ""
677 sysbuild_cmake = ""
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200678 settings = ""
679 twister = ""
680
Carles Cufia580b3d2022-08-09 11:13:54 +0200681 west_projs = west_projects()
682 modules = parse_modules(args.zephyr_base, None, west_projs,
683 args.modules, args.extra_modules)
Marcin Niestroj248d25d2021-05-08 16:56:34 +0200684
685 for module in modules:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200686 kconfig += process_kconfig(module.project, module.meta)
687 cmake += process_cmake(module.project, module.meta)
Jamie McCraedf9027a2023-02-20 10:00:38 +0000688 sysbuild_kconfig += process_sysbuildkconfig(module.project, module.meta)
689 sysbuild_cmake += process_sysbuildcmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200690 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500691 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200692
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100693 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500694 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
695 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100696
697 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500698 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
699 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100700
Jamie McCraedf9027a2023-02-20 10:00:38 +0000701 if args.sysbuild_kconfig_out:
702 with open(args.sysbuild_kconfig_out, 'w', encoding="utf-8") as fp:
703 fp.write(sysbuild_kconfig)
704
705 if args.sysbuild_cmake_out:
706 with open(args.sysbuild_cmake_out, 'w', encoding="utf-8") as fp:
707 fp.write(sysbuild_cmake)
708
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200709 if args.settings_out:
710 with open(args.settings_out, 'w', encoding="utf-8") as fp:
Martí Bolívar361956b2021-08-09 10:46:17 -0700711 fp.write('''\
712# WARNING. THIS FILE IS AUTO-GENERATED. DO NOT MODIFY!
713#
714# This file contains build system settings derived from your modules.
715#
716# Modules may be set via ZEPHYR_MODULES, ZEPHYR_EXTRA_MODULES,
717# and/or the west manifest file.
718#
719# See the Modules guide for more information.
720''')
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200721 fp.write(settings)
722
Anas Nashif54713982020-12-07 14:52:10 -0500723 if args.twister_out:
724 with open(args.twister_out, 'w', encoding="utf-8") as fp:
725 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100726
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200727 if args.meta_out:
Carles Cufia580b3d2022-08-09 11:13:54 +0200728 meta = process_meta(args.zephyr_base, west_projs, modules,
Torsten Rasmussen1a519932021-11-04 18:35:50 +0100729 args.extra_modules, args.meta_state_propagate)
Torsten Rasmussen64ec6ee2021-11-04 14:28:21 +0100730
Torsten Rasmussenfffaf052021-10-12 23:08:36 +0200731 with open(args.meta_out, 'w', encoding="utf-8") as fp:
732 fp.write(yaml.dump(meta))
733
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200734
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100735if __name__ == "__main__":
736 main()