blob: 8725095ae3014773b846716ebe6aa303de71d47a [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
21import os
22import sys
23import yaml
24import pykwalify.core
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +010025from pathlib import Path, PurePath
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020026from collections import namedtuple
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010027
28METADATA_SCHEMA = '''
29## A pykwalify schema for basic validation of the structure of a
30## metadata YAML file.
31##
32# The zephyr/module.yml file is a simple list of key value pairs to be used by
33# the build system.
34type: map
35mapping:
Torsten Rasmussenf24f8832021-01-07 15:27:53 +010036 name:
37 required: false
38 type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010039 build:
Anas Nashif286a9ed2019-12-11 10:13:23 -050040 required: false
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010041 type: map
42 mapping:
43 cmake:
44 required: false
45 type: str
46 kconfig:
47 required: false
48 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +010049 cmake-ext:
50 required: false
51 type: bool
52 default: false
53 kconfig-ext:
54 required: false
55 type: bool
56 default: false
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020057 depends:
58 required: false
59 type: seq
60 sequence:
61 - type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020062 settings:
63 required: false
64 type: map
65 mapping:
66 board_root:
67 required: false
68 type: str
69 dts_root:
70 required: false
71 type: str
72 soc_root:
73 required: false
74 type: str
75 arch_root:
76 required: false
77 type: str
Torsten Rasmussen3673e282020-12-17 11:27:42 +010078 module_ext_root:
79 required: false
80 type: str
Anas Nashif286a9ed2019-12-11 10:13:23 -050081 tests:
82 required: false
83 type: seq
84 sequence:
85 - type: str
86 samples:
87 required: false
88 type: seq
89 sequence:
90 - type: str
91 boards:
92 required: false
93 type: seq
94 sequence:
95 - type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010096'''
97
98schema = yaml.safe_load(METADATA_SCHEMA)
99
100
101def validate_setting(setting, module_path, filename=None):
102 if setting is not None:
103 if filename is not None:
104 checkfile = os.path.join(module_path, setting, filename)
105 else:
106 checkfile = os.path.join(module_path, setting)
107 if not os.path.isfile(checkfile):
108 return False
109 return True
110
111
Anas Nashif286a9ed2019-12-11 10:13:23 -0500112def process_module(module):
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100113 module_path = PurePath(module)
114 module_yml = module_path.joinpath('zephyr/module.yml')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500115
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100116 # The input is a module if zephyr/module.yml is a valid yaml file
117 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present.
118
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100119 if Path(module_yml).is_file():
120 with Path(module_yml).open('r') as f:
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100121 meta = yaml.safe_load(f.read())
122
123 try:
124 pykwalify.core.Core(source_data=meta, schema_data=schema)\
125 .validate()
126 except pykwalify.errors.SchemaError as e:
Ulf Magnusson50b9b122019-09-07 14:41:01 +0200127 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100128 .format(module_yml.as_posix(), e))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100129
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100130 meta['name'] = meta.get('name', module_path.name)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500131 return meta
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100132
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100133 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \
134 Path(module_path.joinpath('zephyr/Kconfig')).is_file():
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100135 return {'name': module_path.name,
136 'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}}
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100137
Anas Nashif286a9ed2019-12-11 10:13:23 -0500138 return None
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100139
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100140
Anas Nashif286a9ed2019-12-11 10:13:23 -0500141def process_cmake(module, meta):
142 section = meta.get('build', dict())
143 module_path = PurePath(module)
144 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100145
146 cmake_extern = section.get('cmake-ext', False)
147 if cmake_extern:
148 return('\"{}\":\"{}\":\"{}\"\n'
Andrzej Głąbek02819482021-01-19 07:39:47 +0100149 .format(meta['name'],
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100150 module_path.as_posix(),
Andrzej Głąbek02819482021-01-19 07:39:47 +0100151 "${ZEPHYR_" + meta['name'].upper() + "_CMAKE_DIR}"))
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100152
Anas Nashif286a9ed2019-12-11 10:13:23 -0500153 cmake_setting = section.get('cmake', None)
154 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
155 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200156 'does not contain a CMakeLists.txt file.'
157 .format(module_yml.as_posix(), cmake_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500158
159 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
160 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
161 if os.path.isfile(cmake_file):
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200162 return('\"{}\":\"{}\":\"{}\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100163 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200164 module_path.as_posix(),
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200165 Path(cmake_path).resolve().as_posix()))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500166 else:
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200167 return('\"{}\":\"{}\":\"\"\n'
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100168 .format(meta['name'],
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200169 module_path.as_posix()))
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200170
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100171
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200172def process_settings(module, meta):
173 section = meta.get('build', dict())
174 build_settings = section.get('settings', None)
175 out_text = ""
176
177 if build_settings is not None:
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100178 for root in ['board', 'dts', 'soc', 'arch', 'module_ext']:
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200179 setting = build_settings.get(root+'_root', None)
180 if setting is not None:
181 root_path = PurePath(module) / setting
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100182 out_text += f'"{root.upper()}_ROOT":'
183 out_text += f'"{root_path.as_posix()}"\n'
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200184
185 return out_text
186
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200187
Andrzej Głąbek02819482021-01-19 07:39:47 +0100188def kconfig_snippet(name, path, kconfig_file=None):
189 snippet = (f'menu "{name} ({path})"',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100190 f'osource "{kconfig_file.resolve().as_posix()}"' if kconfig_file
Andrzej Głąbek02819482021-01-19 07:39:47 +0100191 else f'osource "$(ZEPHYR_{name.upper()}_KCONFIG)"',
192 f'config ZEPHYR_{name.upper()}_MODULE',
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100193 ' bool',
194 ' default y',
195 'endmenu\n')
196 return '\n'.join(snippet)
197
198
Anas Nashif286a9ed2019-12-11 10:13:23 -0500199def process_kconfig(module, meta):
200 section = meta.get('build', dict())
201 module_path = PurePath(module)
202 module_yml = module_path.joinpath('zephyr/module.yml')
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100203 kconfig_extern = section.get('kconfig-ext', False)
204 if kconfig_extern:
Andrzej Głąbek02819482021-01-19 07:39:47 +0100205 return kconfig_snippet(meta['name'], module_path)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500206
207 kconfig_setting = section.get('kconfig', None)
208 if not validate_setting(kconfig_setting, module):
209 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200210 'not point to a valid Kconfig file.'
211 .format(module_yml, kconfig_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500212
213 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
214 if os.path.isfile(kconfig_file):
Andrzej Głąbek02819482021-01-19 07:39:47 +0100215 return kconfig_snippet(meta['name'], module_path, Path(kconfig_file))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500216 else:
217 return ""
218
Torsten Rasmussen3673e282020-12-17 11:27:42 +0100219
Anas Nashif54713982020-12-07 14:52:10 -0500220def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500221
222 out = ""
223 tests = meta.get('tests', [])
224 samples = meta.get('samples', [])
225 boards = meta.get('boards', [])
226
227 for pth in tests + samples:
228 if pth:
229 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200230 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
231 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500232
233 for pth in boards:
234 if pth:
235 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200236 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
237 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500238
239 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100240
241
242def main():
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100243 parser = argparse.ArgumentParser(description='''
244 Process a list of projects and create Kconfig / CMake include files for
245 projects which are also a Zephyr module''')
246
247 parser.add_argument('--kconfig-out',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500248 help="""File to write with resulting KConfig import
249 statements.""")
Anas Nashif54713982020-12-07 14:52:10 -0500250 parser.add_argument('--twister-out',
251 help="""File to write with resulting twister
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200252 parameters.""")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100253 parser.add_argument('--cmake-out',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500254 help="""File to write with resulting <name>:<path>
255 values to use for including in CMake""")
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200256 parser.add_argument('--settings-out',
257 help="""File to write with resulting <name>:<value>
258 values to use for including in CMake""")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100259 parser.add_argument('-m', '--modules', nargs='+',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500260 help="""List of modules to parse instead of using `west
261 list`""")
Martí Bolívar34346c42020-01-29 12:38:58 -0800262 parser.add_argument('-x', '--extra-modules', nargs='+', default=[],
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100263 help='List of extra modules to parse')
Torsten Rasmussen39cd4c82020-02-19 12:01:39 +0100264 parser.add_argument('-z', '--zephyr-base',
265 help='Path to zephyr repository')
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100266 args = parser.parse_args()
267
268 if args.modules is None:
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200269 # West is imported here, as it is optional
270 # (and thus maybe not installed)
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200271 # if user is providing a specific modules list.
272 from west.manifest import Manifest
273 from west.util import WestNotFound
274 try:
275 manifest = Manifest.from_file()
276 projects = [p.posixpath for p in manifest.get_projects([])]
277 except WestNotFound:
278 # Only accept WestNotFound, meaning we are not in a west
279 # workspace. Such setup is allowed, as west may be installed
280 # but the project is not required to use west.
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100281 projects = []
Carles Cufi02dea922020-07-20 14:09:12 +0200282 else:
283 projects = args.modules.copy()
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100284
Martí Bolívar34346c42020-01-29 12:38:58 -0800285 projects += args.extra_modules
286 extra_modules = set(args.extra_modules)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500287
288 kconfig = ""
289 cmake = ""
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200290 settings = ""
Anas Nashif54713982020-12-07 14:52:10 -0500291 twister = ""
Anas Nashif286a9ed2019-12-11 10:13:23 -0500292
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200293 Module = namedtuple('Module', ['project', 'meta', 'depends'])
294 # dep_modules is a list of all modules that has an unresolved dependency
295 dep_modules = []
296 # start_modules is a list modules with no depends left (no incoming edge)
297 start_modules = []
298 # sorted_modules is a topological sorted list of the modules
299 sorted_modules = []
300
Anas Nashif286a9ed2019-12-11 10:13:23 -0500301 for project in projects:
302 # Avoid including Zephyr base project as module.
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200303 if project == args.zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500304 continue
305
306 meta = process_module(project)
307 if meta:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200308 section = meta.get('build', dict())
309 deps = section.get('depends', [])
310 if not deps:
311 start_modules.append(Module(project, meta, []))
312 else:
313 dep_modules.append(Module(project, meta, deps))
Martí Bolívar34346c42020-01-29 12:38:58 -0800314 elif project in extra_modules:
315 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
316 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500317
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200318 # This will do a topological sort to ensure the modules are ordered
319 # according to dependency settings.
320 while start_modules:
321 node = start_modules.pop(0)
322 sorted_modules.append(node)
Torsten Rasmussenf24f8832021-01-07 15:27:53 +0100323 node_name = node.meta['name']
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200324 to_remove = []
325 for module in dep_modules:
326 if node_name in module.depends:
327 module.depends.remove(node_name)
328 if not module.depends:
329 start_modules.append(module)
330 to_remove.append(module)
331 for module in to_remove:
332 dep_modules.remove(module)
333
334 if dep_modules:
335 # If there are any modules with unresolved dependencies, then the
336 # modules contains unmet or cyclic dependencies. Error out.
337 error = 'Unmet or cyclic dependencies in modules:\n'
338 for module in dep_modules:
339 error += f'{module.project} depends on: {module.depends}\n'
340 sys.exit(error)
341
342 for module in sorted_modules:
343 kconfig += process_kconfig(module.project, module.meta)
344 cmake += process_cmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200345 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500346 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200347
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100348 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500349 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
350 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100351
352 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500353 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
354 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100355
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200356 if args.settings_out:
357 with open(args.settings_out, 'w', encoding="utf-8") as fp:
358 fp.write(settings)
359
Anas Nashif54713982020-12-07 14:52:10 -0500360 if args.twister_out:
361 with open(args.twister_out, 'w', encoding="utf-8") as fp:
362 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100363
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200364
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100365if __name__ == "__main__":
366 main()