blob: 883ef895f3795f28cac4cd5c03603e95165cd834 [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:
36 build:
Anas Nashif286a9ed2019-12-11 10:13:23 -050037 required: false
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010038 type: map
39 mapping:
40 cmake:
41 required: false
42 type: str
43 kconfig:
44 required: false
45 type: str
Torsten Rasmussen3917ee52020-05-18 22:34:49 +020046 depends:
47 required: false
48 type: seq
49 sequence:
50 - type: str
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +020051 settings:
52 required: false
53 type: map
54 mapping:
55 board_root:
56 required: false
57 type: str
58 dts_root:
59 required: false
60 type: str
61 soc_root:
62 required: false
63 type: str
64 arch_root:
65 required: false
66 type: str
Anas Nashif286a9ed2019-12-11 10:13:23 -050067 tests:
68 required: false
69 type: seq
70 sequence:
71 - type: str
72 samples:
73 required: false
74 type: seq
75 sequence:
76 - type: str
77 boards:
78 required: false
79 type: seq
80 sequence:
81 - type: str
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010082'''
83
84schema = yaml.safe_load(METADATA_SCHEMA)
85
86
87def validate_setting(setting, module_path, filename=None):
88 if setting is not None:
89 if filename is not None:
90 checkfile = os.path.join(module_path, setting, filename)
91 else:
92 checkfile = os.path.join(module_path, setting)
93 if not os.path.isfile(checkfile):
94 return False
95 return True
96
97
Anas Nashif286a9ed2019-12-11 10:13:23 -050098def process_module(module):
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +010099 module_path = PurePath(module)
100 module_yml = module_path.joinpath('zephyr/module.yml')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500101
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100102 # The input is a module if zephyr/module.yml is a valid yaml file
103 # or if both zephyr/CMakeLists.txt and zephyr/Kconfig are present.
104
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100105 if Path(module_yml).is_file():
106 with Path(module_yml).open('r') as f:
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100107 meta = yaml.safe_load(f.read())
108
109 try:
110 pykwalify.core.Core(source_data=meta, schema_data=schema)\
111 .validate()
112 except pykwalify.errors.SchemaError as e:
Ulf Magnusson50b9b122019-09-07 14:41:01 +0200113 sys.exit('ERROR: Malformed "build" section in file: {}\n{}'
Torsten Rasmussenb3da9ef2019-12-13 09:42:13 +0100114 .format(module_yml.as_posix(), e))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100115
Anas Nashif286a9ed2019-12-11 10:13:23 -0500116 return meta
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100117
Sebastian Bøebb95dce2019-12-19 15:49:22 +0100118 if Path(module_path.joinpath('zephyr/CMakeLists.txt')).is_file() and \
119 Path(module_path.joinpath('zephyr/Kconfig')).is_file():
120 return {'build': {'cmake': 'zephyr', 'kconfig': 'zephyr/Kconfig'}}
121
Anas Nashif286a9ed2019-12-11 10:13:23 -0500122 return None
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100123
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100124
Anas Nashif286a9ed2019-12-11 10:13:23 -0500125def process_cmake(module, meta):
126 section = meta.get('build', dict())
127 module_path = PurePath(module)
128 module_yml = module_path.joinpath('zephyr/module.yml')
129 cmake_setting = section.get('cmake', None)
130 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
131 sys.exit('ERROR: "cmake" key in {} has folder value "{}" which '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200132 'does not contain a CMakeLists.txt file.'
133 .format(module_yml.as_posix(), cmake_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500134
135 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
136 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
137 if os.path.isfile(cmake_file):
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200138 return('\"{}\":\"{}\":\"{}\"\n'
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200139 .format(module_path.name,
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200140 module_path.as_posix(),
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200141 Path(cmake_path).resolve().as_posix()))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500142 else:
Torsten Rasmussenab7ec172020-08-25 13:32:33 +0200143 return('\"{}\":\"{}\":\"\"\n'
144 .format(module_path.name,
145 module_path.as_posix()))
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200146
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200147def process_settings(module, meta):
148 section = meta.get('build', dict())
149 build_settings = section.get('settings', None)
150 out_text = ""
151
152 if build_settings is not None:
153 for root in ['board', 'dts', 'soc', 'arch']:
154 setting = build_settings.get(root+'_root', None)
155 if setting is not None:
156 root_path = PurePath(module) / setting
Torsten Rasmussenfef78792020-10-15 22:16:41 +0200157 out_text += f'"{root.upper()}_ROOT":"{root_path.as_posix()}"\n'
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200158
159 return out_text
160
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200161
Anas Nashif286a9ed2019-12-11 10:13:23 -0500162def process_kconfig(module, meta):
163 section = meta.get('build', dict())
164 module_path = PurePath(module)
165 module_yml = module_path.joinpath('zephyr/module.yml')
166
167 kconfig_setting = section.get('kconfig', None)
168 if not validate_setting(kconfig_setting, module):
169 sys.exit('ERROR: "kconfig" key in {} has value "{}" which does '
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200170 'not point to a valid Kconfig file.'
171 .format(module_yml, kconfig_setting))
Anas Nashif286a9ed2019-12-11 10:13:23 -0500172
173 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
174 if os.path.isfile(kconfig_file):
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200175 return 'osource "{}"\n\n'.format(Path(kconfig_file)
176 .resolve().as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500177 else:
178 return ""
179
Anas Nashif54713982020-12-07 14:52:10 -0500180def process_twister(module, meta):
Anas Nashif286a9ed2019-12-11 10:13:23 -0500181
182 out = ""
183 tests = meta.get('tests', [])
184 samples = meta.get('samples', [])
185 boards = meta.get('boards', [])
186
187 for pth in tests + samples:
188 if pth:
189 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200190 out += '-T\n{}\n'.format(PurePath(os.path.abspath(dir))
191 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500192
193 for pth in boards:
194 if pth:
195 dir = os.path.join(module, pth)
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200196 out += '--board-root\n{}\n'.format(PurePath(os.path.abspath(dir))
197 .as_posix())
Anas Nashif286a9ed2019-12-11 10:13:23 -0500198
199 return out
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100200
201
202def main():
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100203 parser = argparse.ArgumentParser(description='''
204 Process a list of projects and create Kconfig / CMake include files for
205 projects which are also a Zephyr module''')
206
207 parser.add_argument('--kconfig-out',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500208 help="""File to write with resulting KConfig import
209 statements.""")
Anas Nashif54713982020-12-07 14:52:10 -0500210 parser.add_argument('--twister-out',
211 help="""File to write with resulting twister
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200212 parameters.""")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100213 parser.add_argument('--cmake-out',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500214 help="""File to write with resulting <name>:<path>
215 values to use for including in CMake""")
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200216 parser.add_argument('--settings-out',
217 help="""File to write with resulting <name>:<value>
218 values to use for including in CMake""")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100219 parser.add_argument('-m', '--modules', nargs='+',
Anas Nashif286a9ed2019-12-11 10:13:23 -0500220 help="""List of modules to parse instead of using `west
221 list`""")
Martí Bolívar34346c42020-01-29 12:38:58 -0800222 parser.add_argument('-x', '--extra-modules', nargs='+', default=[],
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100223 help='List of extra modules to parse')
Torsten Rasmussen39cd4c82020-02-19 12:01:39 +0100224 parser.add_argument('-z', '--zephyr-base',
225 help='Path to zephyr repository')
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100226 args = parser.parse_args()
227
228 if args.modules is None:
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200229 # West is imported here, as it is optional
230 # (and thus maybe not installed)
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200231 # if user is providing a specific modules list.
232 from west.manifest import Manifest
233 from west.util import WestNotFound
234 try:
235 manifest = Manifest.from_file()
236 projects = [p.posixpath for p in manifest.get_projects([])]
237 except WestNotFound:
238 # Only accept WestNotFound, meaning we are not in a west
239 # workspace. Such setup is allowed, as west may be installed
240 # but the project is not required to use west.
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100241 projects = []
Carles Cufi02dea922020-07-20 14:09:12 +0200242 else:
243 projects = args.modules.copy()
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100244
Martí Bolívar34346c42020-01-29 12:38:58 -0800245 projects += args.extra_modules
246 extra_modules = set(args.extra_modules)
Anas Nashif286a9ed2019-12-11 10:13:23 -0500247
248 kconfig = ""
249 cmake = ""
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200250 settings = ""
Anas Nashif54713982020-12-07 14:52:10 -0500251 twister = ""
Anas Nashif286a9ed2019-12-11 10:13:23 -0500252
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200253 Module = namedtuple('Module', ['project', 'meta', 'depends'])
254 # dep_modules is a list of all modules that has an unresolved dependency
255 dep_modules = []
256 # start_modules is a list modules with no depends left (no incoming edge)
257 start_modules = []
258 # sorted_modules is a topological sorted list of the modules
259 sorted_modules = []
260
Anas Nashif286a9ed2019-12-11 10:13:23 -0500261 for project in projects:
262 # Avoid including Zephyr base project as module.
Torsten Rasmussenef3c5e52020-06-08 21:09:15 +0200263 if project == args.zephyr_base:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500264 continue
265
266 meta = process_module(project)
267 if meta:
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200268 section = meta.get('build', dict())
269 deps = section.get('depends', [])
270 if not deps:
271 start_modules.append(Module(project, meta, []))
272 else:
273 dep_modules.append(Module(project, meta, deps))
Martí Bolívar34346c42020-01-29 12:38:58 -0800274 elif project in extra_modules:
275 sys.exit(f'{project}, given in ZEPHYR_EXTRA_MODULES, '
276 'is not a valid zephyr module')
Anas Nashif286a9ed2019-12-11 10:13:23 -0500277
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200278 # This will do a topological sort to ensure the modules are ordered
279 # according to dependency settings.
280 while start_modules:
281 node = start_modules.pop(0)
282 sorted_modules.append(node)
283 node_name = PurePath(node.project).name
284 to_remove = []
285 for module in dep_modules:
286 if node_name in module.depends:
287 module.depends.remove(node_name)
288 if not module.depends:
289 start_modules.append(module)
290 to_remove.append(module)
291 for module in to_remove:
292 dep_modules.remove(module)
293
294 if dep_modules:
295 # If there are any modules with unresolved dependencies, then the
296 # modules contains unmet or cyclic dependencies. Error out.
297 error = 'Unmet or cyclic dependencies in modules:\n'
298 for module in dep_modules:
299 error += f'{module.project} depends on: {module.depends}\n'
300 sys.exit(error)
301
302 for module in sorted_modules:
303 kconfig += process_kconfig(module.project, module.meta)
304 cmake += process_cmake(module.project, module.meta)
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200305 settings += process_settings(module.project, module.meta)
Anas Nashif54713982020-12-07 14:52:10 -0500306 twister += process_twister(module.project, module.meta)
Torsten Rasmussen3917ee52020-05-18 22:34:49 +0200307
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100308 if args.kconfig_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500309 with open(args.kconfig_out, 'w', encoding="utf-8") as fp:
310 fp.write(kconfig)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100311
312 if args.cmake_out:
Anas Nashif286a9ed2019-12-11 10:13:23 -0500313 with open(args.cmake_out, 'w', encoding="utf-8") as fp:
314 fp.write(cmake)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100315
Torsten Rasmussen25d57ba2020-07-07 17:29:56 +0200316 if args.settings_out:
317 with open(args.settings_out, 'w', encoding="utf-8") as fp:
318 fp.write(settings)
319
Anas Nashif54713982020-12-07 14:52:10 -0500320 if args.twister_out:
321 with open(args.twister_out, 'w', encoding="utf-8") as fp:
322 fp.write(twister)
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100323
Torsten Rasmussen25c3f8e2020-08-25 14:02:04 +0200324
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100325if __name__ == "__main__":
326 main()