cmake: Extracted Zephyr module processing into python script
Fixes: #14513 This commit move the functionality of extracting zephyr modules into generated CMake and Kconfig include files from CMake into python. This allows other tools, especially CI to re-use the zephyr module functionality. Signed-off-by: Torsten Rasmussen <torsten.rasmussen@nordicsemi.no>
This commit is contained in:
parent
dfd779c380
commit
bd7569f272
5 changed files with 194 additions and 151 deletions
|
@ -575,16 +575,17 @@ add_subdirectory(ext)
|
|||
add_subdirectory(subsys)
|
||||
add_subdirectory(drivers)
|
||||
|
||||
# Add all zephyr modules subdirectories.
|
||||
if(ZEPHYR_MODULES_NAME)
|
||||
message("Including module(s): ${ZEPHYR_MODULES_NAME}")
|
||||
# Include zephyr modules generated CMake file.
|
||||
if(EXISTS ${CMAKE_BINARY_DIR}/zephyr_modules.txt)
|
||||
file(STRINGS ${CMAKE_BINARY_DIR}/zephyr_modules.txt ZEPHYR_MODULES_TXT)
|
||||
|
||||
foreach(module ${ZEPHYR_MODULES_TXT})
|
||||
string(REGEX REPLACE "(.*):.*" "\\1" module_name ${module})
|
||||
string(REGEX REPLACE ".*:(.*)" "\\1" module_path ${module})
|
||||
message("Including module: ${module_name}")
|
||||
add_subdirectory(${module_path} ${CMAKE_BINARY_DIR}/${module_name})
|
||||
endforeach()
|
||||
endif()
|
||||
set(index 0)
|
||||
foreach(module_name ${ZEPHYR_MODULES_NAME})
|
||||
list(GET ZEPHYR_MODULES_DIR ${index} module_dir)
|
||||
math(EXPR index "${index} + 1")
|
||||
add_subdirectory(${module_dir} ${CMAKE_BINARY_DIR}/${module_name})
|
||||
endforeach()
|
||||
|
||||
set(syscall_macros_h ${ZEPHYR_BINARY_DIR}/include/generated/syscall_macros.h)
|
||||
|
||||
|
|
|
@ -253,6 +253,7 @@
|
|||
/scripts/series-push-hook.sh @erwango
|
||||
/scripts/west_commands/ @mbolivar
|
||||
/scripts/west-commands.yml @mbolivar
|
||||
/scripts/zephyr_module.py @tejlmand
|
||||
/subsys/bluetooth/ @sjanc @jhedberg @Vudentz
|
||||
/subsys/bluetooth/controller/ @carlescufi @cvinayak @thoh-ot
|
||||
/subsys/fs/ @nashif
|
||||
|
|
|
@ -9,86 +9,30 @@
|
|||
# search for zephyr/module.yml
|
||||
|
||||
if(ZEPHYR_MODULES)
|
||||
set(west_project_list ${ZEPHYR_MODULES})
|
||||
elseif(WEST)
|
||||
## Use `west list` to fetch all west handled projects.
|
||||
execute_process(
|
||||
COMMAND
|
||||
${WEST} list --format={posixpath}
|
||||
OUTPUT_VARIABLE
|
||||
west_project_output
|
||||
ERROR_VARIABLE
|
||||
west_project_error
|
||||
RESULT_VARIABLE
|
||||
west_list_result
|
||||
)
|
||||
if(NOT ${west_list_result})
|
||||
string(REGEX REPLACE "[\r\n]+" ";" west_project_list "${west_project_output}")
|
||||
elseif(NOT ("${west_project_error}" MATCHES
|
||||
"^Error: .* is not in a west installation\..*"))
|
||||
message(FATAL_ERROR "${west_project_error}")
|
||||
endif()
|
||||
set(ZEPHYR_MODULES_ARG "--modules" ${ZEPHYR_MODULES})
|
||||
endif()
|
||||
|
||||
if(ZEPHYR_EXTRA_MODULES)
|
||||
list(APPEND west_project_list ${ZEPHYR_EXTRA_MODULES})
|
||||
set(ZEPHYR_EXTRA_MODULES_ARG "--extra-modules" ${ZEPHYR_EXTRA_MODULES})
|
||||
endif()
|
||||
|
||||
# Clear the Kconfig.modules generated file in case modules has been removed.
|
||||
# The Kconfig.modules contains a list of additional Kconfig files to be sourced
|
||||
# based upon <module>/zephyr/module.yml files.
|
||||
set(KCONFIG_MODULES_FILE ${CMAKE_BINARY_DIR}/Kconfig.modules)
|
||||
file(WRITE ${KCONFIG_MODULES_FILE}
|
||||
"# NOTE: THIS FILE IS AUTOGENERATED BY CMAKE\n"
|
||||
if(WEST OR ZEPHYR_MODULES)
|
||||
# Zephyr module uses west, so only call it if west is installed or
|
||||
# ZEPHYR_MODULES was provided as argument to CMake.
|
||||
execute_process(
|
||||
COMMAND
|
||||
${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/zephyr_module.py
|
||||
${ZEPHYR_MODULES_ARG}
|
||||
${ZEPHYR_EXTRA_MODULES_ARG}
|
||||
--kconfig-out ${CMAKE_BINARY_DIR}/Kconfig.modules
|
||||
--cmake-out ${CMAKE_BINARY_DIR}/zephyr_modules.txt
|
||||
ERROR_VARIABLE
|
||||
zephyr_module_error_text
|
||||
RESULT_VARIABLE
|
||||
zephyr_module_return
|
||||
)
|
||||
# For each west managed project, determine if the project is a zephyr module.
|
||||
foreach(module ${west_project_list})
|
||||
set(cmake_subdir "zephyr")
|
||||
if(${ZEPHYR_BASE} STREQUAL ${module})
|
||||
# Ignore Zephyr project to avoid potential invalid looping
|
||||
elseif(EXISTS "${module}/zephyr/module.yml")
|
||||
set(kconfig_osource "osource \"${module}/zephyr/Kconfig\"\n")
|
||||
get_filename_component(module_name ${module} NAME)
|
||||
execute_process(
|
||||
COMMAND
|
||||
${PYTHON_EXECUTABLE}
|
||||
${ZEPHYR_BASE}/scripts/yaml_to_cmake.py
|
||||
-i "${module}/zephyr/module.yml"
|
||||
-o "${CMAKE_CURRENT_BINARY_DIR}/zephyr_module_${module_name}.txt"
|
||||
-s "build"
|
||||
)
|
||||
file(
|
||||
STRINGS
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/zephyr_module_${module_name}.txt"
|
||||
zephyr_module
|
||||
)
|
||||
|
||||
foreach(key_value ${zephyr_module})
|
||||
if(${key_value} MATCHES "^cmake=")
|
||||
string(REGEX REPLACE "^cmake=" "" cmake_subdir ${key_value})
|
||||
elseif(${key_value} MATCHES "^kconfig=")
|
||||
string(
|
||||
REGEX REPLACE
|
||||
"^kconfig="
|
||||
"osource \"${module}/"
|
||||
kconfig_osource
|
||||
${key_value} "\"\n"
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
list(APPEND ZEPHYR_MODULES_NAME ${module_name})
|
||||
list(APPEND ZEPHYR_MODULES_DIR ${module}/${cmake_subdir})
|
||||
file(APPEND ${KCONFIG_MODULES_FILE} ${kconfig_osource})
|
||||
elseif(EXISTS "${module}/${cmake_subdir}/CMakeLists.txt")
|
||||
set(kconfig_osource "osource \"${module}/zephyr/Kconfig\"\n")
|
||||
get_filename_component(module_name ${module} NAME)
|
||||
list(APPEND ZEPHYR_MODULES_NAME ${module_name})
|
||||
list(APPEND ZEPHYR_MODULES_DIR ${module}/${cmake_subdir})
|
||||
file(APPEND ${KCONFIG_MODULES_FILE} ${kconfig_osource})
|
||||
else()
|
||||
# Not a Zephyr module, ignore.
|
||||
if(${zephyr_module_return})
|
||||
message(FATAL_ERROR "${zephyr_module_error_text}")
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
|
||||
endif()
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2019, Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
'''Tool for a simple parsing of YAML files and return a ;-list of <key>=<value>
|
||||
pairs to use within a CMake build file.
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import yaml
|
||||
import pykwalify.core
|
||||
|
||||
|
||||
METADATA_SCHEMA = '''\
|
||||
## A pykwalify schema for basic validation of the structure of a
|
||||
## metadata YAML file.
|
||||
##
|
||||
# The zephyr/module.yml file is a simple list of key value pairs to be used by
|
||||
# the build system.
|
||||
type: map
|
||||
mapping:
|
||||
build:
|
||||
required: true
|
||||
type: map
|
||||
mapping:
|
||||
cmake:
|
||||
required: false
|
||||
type: str
|
||||
kconfig:
|
||||
required: false
|
||||
type: str
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='''
|
||||
Converts YAML to a CMake list''')
|
||||
|
||||
parser.add_argument('-i', '--input', required=True,
|
||||
help='YAML file with data')
|
||||
parser.add_argument('-o', '--output', required=True,
|
||||
help='File to write with CMake data')
|
||||
parser.add_argument('-s', '--section', required=True,
|
||||
help='Section in YAML file to parse')
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.input, 'r') as f:
|
||||
meta = yaml.safe_load(f.read())
|
||||
|
||||
pykwalify.core.Core(source_data=meta,
|
||||
schema_data=yaml.safe_load(METADATA_SCHEMA)).validate()
|
||||
|
||||
val_str = ''
|
||||
|
||||
section = meta.get(args.section)
|
||||
if section is not None:
|
||||
for key in section:
|
||||
val_str += '{}={}\n'.format(key, section[key])
|
||||
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(val_str)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
164
scripts/zephyr_module.py
Executable file
164
scripts/zephyr_module.py
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2019, Nordic Semiconductor ASA
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
'''Tool for parsing a list of projects to determine if they are Zephyr
|
||||
projects. If no projects are given then the output from `west list` will be
|
||||
used as project list.
|
||||
|
||||
Include file is generated for Kconfig using --kconfig-out.
|
||||
A <name>:<path> text file is generated for use with CMake using --cmake-out.
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import pykwalify.core
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
|
||||
METADATA_SCHEMA = '''
|
||||
## A pykwalify schema for basic validation of the structure of a
|
||||
## metadata YAML file.
|
||||
##
|
||||
# The zephyr/module.yml file is a simple list of key value pairs to be used by
|
||||
# the build system.
|
||||
type: map
|
||||
mapping:
|
||||
build:
|
||||
required: true
|
||||
type: map
|
||||
mapping:
|
||||
cmake:
|
||||
required: false
|
||||
type: str
|
||||
kconfig:
|
||||
required: false
|
||||
type: str
|
||||
'''
|
||||
|
||||
schema = yaml.safe_load(METADATA_SCHEMA)
|
||||
|
||||
|
||||
def validate_setting(setting, module_path, filename=None):
|
||||
if setting is not None:
|
||||
if filename is not None:
|
||||
checkfile = os.path.join(module_path, setting, filename)
|
||||
else:
|
||||
checkfile = os.path.join(module_path, setting)
|
||||
if not os.path.isfile(checkfile):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def process_module(module, cmake_out=None, kconfig_out=None):
|
||||
cmake_setting = None
|
||||
kconfig_setting = None
|
||||
|
||||
module_yml = os.path.join(module, 'zephyr/module.yml')
|
||||
if os.path.isfile(module_yml):
|
||||
with open(module_yml, 'r') as f:
|
||||
meta = yaml.safe_load(f.read())
|
||||
|
||||
try:
|
||||
pykwalify.core.Core(source_data=meta, schema_data=schema)\
|
||||
.validate()
|
||||
except pykwalify.errors.SchemaError as e:
|
||||
print('ERROR: Malformed "build" section in file: {}\n{}'
|
||||
.format(module_yml, e), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
section = meta.get('build', dict())
|
||||
cmake_setting = section.get('cmake', None)
|
||||
if not validate_setting(cmake_setting, module, 'CMakeLists.txt'):
|
||||
print('ERROR: "cmake" key in {} has folder value "{}" which '
|
||||
'does not contain a CMakeLists.txt file.'
|
||||
.format(module_yml, cmake_setting), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
kconfig_setting = section.get('kconfig', None)
|
||||
if not validate_setting(kconfig_setting, module):
|
||||
print('ERROR: "kconfig" key in {} has value "{}" which does not '
|
||||
'point to a valid Kconfig file.'
|
||||
.format(module_yml, kconfig_setting), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
cmake_path = os.path.join(module, cmake_setting or 'zephyr')
|
||||
cmake_file = os.path.join(cmake_path, 'CMakeLists.txt')
|
||||
if os.path.isfile(cmake_file) and cmake_out is not None:
|
||||
cmake_out.write('{}:{}\n'.format(os.path.basename(module),
|
||||
os.path.abspath(cmake_path)))
|
||||
|
||||
kconfig_file = os.path.join(module, kconfig_setting or 'zephyr/Kconfig')
|
||||
if os.path.isfile(kconfig_file) and kconfig_out is not None:
|
||||
kconfig_out.write('osource "{}"\n\n'
|
||||
.format(os.path.abspath(kconfig_file)))
|
||||
|
||||
|
||||
def main():
|
||||
kconfig_out_file = None
|
||||
cmake_out_file = None
|
||||
|
||||
parser = argparse.ArgumentParser(description='''
|
||||
Process a list of projects and create Kconfig / CMake include files for
|
||||
projects which are also a Zephyr module''')
|
||||
|
||||
parser.add_argument('--kconfig-out',
|
||||
help='File to write with resulting KConfig import'
|
||||
'statements.')
|
||||
parser.add_argument('--cmake-out',
|
||||
help='File to write with resulting <name>:<path>'
|
||||
'values to use for including in CMake')
|
||||
parser.add_argument('-m', '--modules', nargs='+',
|
||||
help='List of modules to parse instead of using `west'
|
||||
'list`')
|
||||
parser.add_argument('-x', '--extra-modules', nargs='+',
|
||||
help='List of extra modules to parse')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.modules is None:
|
||||
p = subprocess.Popen(['west', 'list', '--format={posixpath}'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
out, err = p.communicate()
|
||||
if p.returncode == 0:
|
||||
projects = out.decode(sys.getdefaultencoding()).splitlines()
|
||||
elif re.match(r'Error: .* is not in a west installation\..*',
|
||||
err.decode(sys.getdefaultencoding())):
|
||||
# Only accept the error from bootstrapper in the event we are
|
||||
# outside a west managed project.
|
||||
projects = []
|
||||
else:
|
||||
# A real error occurred, raise an exception
|
||||
raise subprocess.CalledProcessError(cmd=p.args,
|
||||
returncode=p.returncode)
|
||||
else:
|
||||
projects = args.modules
|
||||
|
||||
if args.extra_modules is not None:
|
||||
projects += args.extra_modules
|
||||
|
||||
if args.kconfig_out:
|
||||
kconfig_out_file = open(args.kconfig_out, 'w')
|
||||
|
||||
if args.cmake_out:
|
||||
cmake_out_file = open(args.cmake_out, 'w')
|
||||
|
||||
try:
|
||||
for project in projects:
|
||||
# Avoid including Zephyr base project as module.
|
||||
if project != os.environ.get('ZEPHYR_BASE'):
|
||||
process_module(project, cmake_out_file, kconfig_out_file)
|
||||
finally:
|
||||
if args.kconfig_out:
|
||||
kconfig_out_file.close()
|
||||
if args.cmake_out:
|
||||
cmake_out_file.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue