blob: fb945bb3215991856c9958f045282fe1ed1233c2 [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.
13'''
14
15import argparse
16import os
17import sys
18import yaml
19import pykwalify.core
20import subprocess
21import re
Torsten Rasmussenad026c02019-04-09 14:06:09 +020022from pathlib import PurePath
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010023
24METADATA_SCHEMA = '''
25## A pykwalify schema for basic validation of the structure of a
26## metadata YAML file.
27##
28# The zephyr/module.yml file is a simple list of key value pairs to be used by
29# the build system.
30type: map
31mapping:
32 build:
33 required: true
34 type: map
35 mapping:
36 cmake:
37 required: false
38 type: str
39 kconfig:
40 required: false
41 type: str
42'''
43
44schema = yaml.safe_load(METADATA_SCHEMA)
45
46
47def validate_setting(setting, module_path, filename=None):
48 if setting is not None:
49 if filename is not None:
50 checkfile = os.path.join(module_path, setting, filename)
51 else:
52 checkfile = os.path.join(module_path, setting)
53 if not os.path.isfile(checkfile):
54 return False
55 return True
56
57
58def process_module(module, cmake_out=None, kconfig_out=None):
59 cmake_setting = None
60 kconfig_setting = None
61
62 module_yml = os.path.join(module, 'zephyr/module.yml')
63 if os.path.isfile(module_yml):
64 with open(module_yml, 'r') as f:
65 meta = yaml.safe_load(f.read())
66
67 try:
68 pykwalify.core.Core(source_data=meta, schema_data=schema)\
69 .validate()
70 except pykwalify.errors.SchemaError as e:
71 print('ERROR: Malformed "build" section in file: {}\n{}'
72 .format(module_yml, e), file=sys.stderr)
73 sys.exit(1)
74
75 section = meta.get('build', dict())
76 cmake_setting = section.get('cmake', None)
77 if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
78 print('ERROR: "cmake" key in {} has folder value "{}" which '
79 'does not contain a CMakeLists.txt file.'
80 .format(module_yml, cmake_setting), file=sys.stderr)
81 sys.exit(1)
82
83 kconfig_setting = section.get('kconfig', None)
84 if not validate_setting(kconfig_setting, module):
85 print('ERROR: "kconfig" key in {} has value "{}" which does not '
86 'point to a valid Kconfig file.'
87 .format(module_yml, kconfig_setting), file=sys.stderr)
88 sys.exit(1)
89
90 cmake_path = os.path.join(module, cmake_setting or 'zephyr')
91 cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
92 if os.path.isfile(cmake_file) and cmake_out is not None:
Carles Cufi766edd42019-03-31 22:29:30 +020093 cmake_out.write('\"{}\":\"{}\"\n'.format(os.path.basename(module),
Torsten Rasmussenad026c02019-04-09 14:06:09 +020094 os.path.abspath(cmake_path)))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +010095
96 kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
97 if os.path.isfile(kconfig_file) and kconfig_out is not None:
98 kconfig_out.write('osource "{}"\n\n'
Torsten Rasmussenad026c02019-04-09 14:06:09 +020099 .format(PurePath(
100 os.path.abspath(kconfig_file)).as_posix()))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100101
102
103def main():
104 kconfig_out_file = None
105 cmake_out_file = None
106
107 parser = argparse.ArgumentParser(description='''
108 Process a list of projects and create Kconfig / CMake include files for
109 projects which are also a Zephyr module''')
110
111 parser.add_argument('--kconfig-out',
112 help='File to write with resulting KConfig import'
113 'statements.')
114 parser.add_argument('--cmake-out',
115 help='File to write with resulting <name>:<path>'
116 'values to use for including in CMake')
117 parser.add_argument('-m', '--modules', nargs='+',
118 help='List of modules to parse instead of using `west'
119 'list`')
120 parser.add_argument('-x', '--extra-modules', nargs='+',
121 help='List of extra modules to parse')
Sigvart M. Hovland06365a52019-04-25 12:27:17 +0200122 parser.add_argument('-w', '--west-path', default='west',
123 help='Path to west executable')
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100124 args = parser.parse_args()
125
126 if args.modules is None:
Sigvart M. Hovland06365a52019-04-25 12:27:17 +0200127 p = subprocess.Popen([args.west_path, 'list', '--format={posixpath}'],
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100128 stdout=subprocess.PIPE,
129 stderr=subprocess.PIPE)
130 out, err = p.communicate()
131 if p.returncode == 0:
132 projects = out.decode(sys.getdefaultencoding()).splitlines()
Piotr Zierhoffera3ddc092019-08-06 12:39:34 +0200133 elif re.match((r'Error: .* is not in a west installation\.'
134 '|FATAL ERROR: no west installation found from .*'),
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100135 err.decode(sys.getdefaultencoding())):
136 # Only accept the error from bootstrapper in the event we are
137 # outside a west managed project.
138 projects = []
139 else:
Torsten Rasmussen0dd3b422019-04-25 11:03:27 +0200140 print(err.decode(sys.getdefaultencoding()))
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100141 # A real error occurred, raise an exception
142 raise subprocess.CalledProcessError(cmd=p.args,
143 returncode=p.returncode)
144 else:
145 projects = args.modules
146
147 if args.extra_modules is not None:
148 projects += args.extra_modules
149
150 if args.kconfig_out:
Carles Cufi3ad1f272019-07-18 10:38:25 +0200151 kconfig_out_file = open(args.kconfig_out, 'w', encoding="utf-8")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100152
153 if args.cmake_out:
Carles Cufi3ad1f272019-07-18 10:38:25 +0200154 cmake_out_file = open(args.cmake_out, 'w', encoding="utf-8")
Torsten Rasmussenbd7569f2019-03-19 10:38:18 +0100155
156 try:
157 for project in projects:
158 # Avoid including Zephyr base project as module.
159 if project != os.environ.get('ZEPHYR_BASE'):
160 process_module(project, cmake_out_file, kconfig_out_file)
161 finally:
162 if args.kconfig_out:
163 kconfig_out_file.close()
164 if args.cmake_out:
165 cmake_out_file.close()
166
167
168if __name__ == "__main__":
169 main()