2018-03-01 02:41:40 -05:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
|
|
|
# Command line tool to convert simple ESP-IDF Makefile & component.mk files to
|
|
|
|
# CMakeLists.txt files
|
|
|
|
#
|
|
|
|
import argparse
|
|
|
|
import glob
|
2021-01-25 21:49:01 -05:00
|
|
|
import os.path
|
|
|
|
import re
|
|
|
|
import subprocess
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
debug = False
|
|
|
|
|
2018-12-04 07:46:48 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
def get_make_variables(path, makefile='Makefile', expected_failure=False, variables={}):
|
2018-03-01 02:41:40 -05:00
|
|
|
"""
|
|
|
|
Given the path to a Makefile of some kind, return a dictionary of all variables defined in this Makefile
|
|
|
|
|
|
|
|
Uses 'make' to parse the Makefile syntax, so we don't have to!
|
|
|
|
|
|
|
|
Overrides IDF_PATH= to avoid recursively evaluating the entire project Makefile structure.
|
|
|
|
"""
|
2021-01-25 21:49:01 -05:00
|
|
|
variable_setters = [('%s=%s' % (k,v)) for (k,v) in variables.items()]
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
cmdline = ['make', '-rpn', '-C', path, '-f', makefile] + variable_setters
|
2018-03-01 02:41:40 -05:00
|
|
|
if debug:
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Running %s...' % (' '.join(cmdline)))
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
2021-01-25 21:49:01 -05:00
|
|
|
(output, stderr) = p.communicate('\n')
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
if (not expected_failure) and p.returncode != 0:
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('Unexpected make failure, result %d' % p.returncode)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
if debug:
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Make stdout:')
|
2018-03-01 02:41:40 -05:00
|
|
|
print(output)
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Make stderr:')
|
2018-03-01 02:41:40 -05:00
|
|
|
print(stderr)
|
|
|
|
|
|
|
|
next_is_makefile = False # is the next line a makefile variable?
|
|
|
|
result = {}
|
2021-01-25 21:49:01 -05:00
|
|
|
BUILT_IN_VARS = set(['MAKEFILE_LIST', 'SHELL', 'CURDIR', 'MAKEFLAGS'])
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
for line in output.decode('utf-8').split('\n'):
|
|
|
|
if line.startswith('# makefile'): # this line appears before any variable defined in the makefile itself
|
2018-03-01 02:41:40 -05:00
|
|
|
next_is_makefile = True
|
|
|
|
elif next_is_makefile:
|
|
|
|
next_is_makefile = False
|
2021-01-25 21:49:01 -05:00
|
|
|
m = re.match(r'(?P<var>[^ ]+) :?= (?P<val>.+)', line)
|
2018-03-01 02:41:40 -05:00
|
|
|
if m is not None:
|
2021-01-25 21:49:01 -05:00
|
|
|
if not m.group('var') in BUILT_IN_VARS:
|
|
|
|
result[m.group('var')] = m.group('val').strip()
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
2018-12-04 07:46:48 -05:00
|
|
|
|
2018-03-01 02:41:40 -05:00
|
|
|
def get_component_variables(project_path, component_path):
|
|
|
|
make_vars = get_make_variables(component_path,
|
2021-01-25 21:49:01 -05:00
|
|
|
os.path.join(os.environ['IDF_PATH'],
|
|
|
|
'make',
|
|
|
|
'component_wrapper.mk'),
|
2018-03-01 02:41:40 -05:00
|
|
|
expected_failure=True,
|
2018-12-04 07:46:48 -05:00
|
|
|
variables={
|
2021-01-25 21:49:01 -05:00
|
|
|
'COMPONENT_MAKEFILE': os.path.join(component_path, 'component.mk'),
|
|
|
|
'COMPONENT_NAME': os.path.basename(component_path),
|
|
|
|
'PROJECT_PATH': project_path,
|
2018-03-01 02:41:40 -05:00
|
|
|
})
|
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'COMPONENT_OBJS' in make_vars: # component.mk specifies list of object files
|
2018-03-01 02:41:40 -05:00
|
|
|
# Convert to sources
|
|
|
|
def find_src(obj):
|
|
|
|
obj = os.path.splitext(obj)[0]
|
2021-01-25 21:49:01 -05:00
|
|
|
for ext in ['c', 'cpp', 'S']:
|
|
|
|
if os.path.exists(os.path.join(component_path, obj) + '.' + ext):
|
|
|
|
return obj + '.' + ext
|
2018-03-01 02:41:40 -05:00
|
|
|
print("WARNING: Can't find source file for component %s COMPONENT_OBJS %s" % (component_path, obj))
|
|
|
|
return None
|
|
|
|
|
|
|
|
srcs = []
|
2021-01-25 21:49:01 -05:00
|
|
|
for obj in make_vars['COMPONENT_OBJS'].split():
|
2018-03-01 02:41:40 -05:00
|
|
|
src = find_src(obj)
|
|
|
|
if src is not None:
|
|
|
|
srcs.append(src)
|
2021-01-25 21:49:01 -05:00
|
|
|
make_vars['COMPONENT_SRCS'] = ' '.join(srcs)
|
2018-09-10 21:44:12 -04:00
|
|
|
else:
|
|
|
|
component_srcs = list()
|
2021-01-25 21:49:01 -05:00
|
|
|
for component_srcdir in make_vars.get('COMPONENT_SRCDIRS', '.').split():
|
2018-09-10 21:44:12 -04:00
|
|
|
component_srcdir_path = os.path.abspath(os.path.join(component_path, component_srcdir))
|
2018-12-04 07:46:48 -05:00
|
|
|
|
2018-09-10 21:44:12 -04:00
|
|
|
srcs = list()
|
2021-01-25 21:49:01 -05:00
|
|
|
srcs += glob.glob(os.path.join(component_srcdir_path, '*.[cS]'))
|
|
|
|
srcs += glob.glob(os.path.join(component_srcdir_path, '*.cpp'))
|
2018-09-10 21:44:12 -04:00
|
|
|
srcs = [('"%s"' % str(os.path.relpath(s, component_path))) for s in srcs]
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
make_vars['COMPONENT_ADD_INCLUDEDIRS'] = make_vars.get('COMPONENT_ADD_INCLUDEDIRS', 'include')
|
2018-11-08 07:02:03 -05:00
|
|
|
component_srcs += srcs
|
2021-01-25 21:49:01 -05:00
|
|
|
make_vars['COMPONENT_SRCS'] = ' '.join(component_srcs)
|
2018-09-10 21:44:12 -04:00
|
|
|
|
2018-03-01 02:41:40 -05:00
|
|
|
return make_vars
|
|
|
|
|
|
|
|
|
|
|
|
def convert_project(project_path):
|
|
|
|
if not os.path.exists(project_path):
|
|
|
|
raise RuntimeError("Project directory '%s' not found" % project_path)
|
2021-01-25 21:49:01 -05:00
|
|
|
if not os.path.exists(os.path.join(project_path, 'Makefile')):
|
2018-03-01 02:41:40 -05:00
|
|
|
raise RuntimeError("Directory '%s' doesn't contain a project Makefile" % project_path)
|
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
project_cmakelists = os.path.join(project_path, 'CMakeLists.txt')
|
2018-03-01 02:41:40 -05:00
|
|
|
if os.path.exists(project_cmakelists):
|
2021-01-25 21:49:01 -05:00
|
|
|
raise RuntimeError('This project already has a CMakeLists.txt file')
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
project_vars = get_make_variables(project_path, expected_failure=True)
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'PROJECT_NAME' not in project_vars:
|
|
|
|
raise RuntimeError('PROJECT_NAME does not appear to be defined in IDF project Makefile at %s' % project_path)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
component_paths = project_vars['COMPONENT_PATHS'].split()
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2019-12-19 18:28:41 -05:00
|
|
|
converted_components = 0
|
|
|
|
|
2018-03-01 02:41:40 -05:00
|
|
|
# Convert components as needed
|
|
|
|
for p in component_paths:
|
2021-01-25 21:49:01 -05:00
|
|
|
if 'MSYSTEM' in os.environ:
|
|
|
|
cmd = ['cygpath', '-w', p]
|
2020-04-27 10:15:52 -04:00
|
|
|
p = subprocess.check_output(cmd).decode('utf-8').strip()
|
2018-12-11 07:07:18 -05:00
|
|
|
|
2019-12-19 18:28:41 -05:00
|
|
|
converted_components += convert_component(project_path, p)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
project_name = project_vars['PROJECT_NAME']
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
# Generate the project CMakeLists.txt file
|
2021-01-25 21:49:01 -05:00
|
|
|
with open(project_cmakelists, 'w') as f:
|
2018-03-01 02:41:40 -05:00
|
|
|
f.write("""
|
|
|
|
# (Automatically converted from project Makefile by convert_to_cmake.py.)
|
|
|
|
|
2018-09-10 21:44:12 -04:00
|
|
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
2018-03-01 02:41:40 -05:00
|
|
|
# in this exact order for cmake to work correctly
|
|
|
|
cmake_minimum_required(VERSION 3.5)
|
|
|
|
|
|
|
|
""")
|
|
|
|
f.write("""
|
|
|
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
|
|
|
""")
|
2021-01-25 21:49:01 -05:00
|
|
|
f.write('project(%s)\n' % project_name)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Converted project %s' % project_cmakelists)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2019-12-19 18:28:41 -05:00
|
|
|
if converted_components > 0:
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Note: Newly created component CMakeLists.txt do not have any REQUIRES or PRIV_REQUIRES '
|
|
|
|
'lists to declare their component requirements. Builds may fail to include other '
|
2019-12-19 18:28:41 -05:00
|
|
|
"components' header files. If so requirements need to be added to the components' "
|
|
|
|
"CMakeLists.txt files. See the 'Component Requirements' section of the "
|
2021-01-25 21:49:01 -05:00
|
|
|
'Build System docs for more details.')
|
2019-12-19 18:28:41 -05:00
|
|
|
|
2018-12-04 07:46:48 -05:00
|
|
|
|
2018-03-01 02:41:40 -05:00
|
|
|
def convert_component(project_path, component_path):
|
|
|
|
if debug:
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Converting %s...' % (component_path))
|
|
|
|
cmakelists_path = os.path.join(component_path, 'CMakeLists.txt')
|
2018-03-01 02:41:40 -05:00
|
|
|
if os.path.exists(cmakelists_path):
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Skipping already-converted component %s...' % cmakelists_path)
|
2019-12-19 18:28:41 -05:00
|
|
|
return 0
|
2018-03-01 02:41:40 -05:00
|
|
|
v = get_component_variables(project_path, component_path)
|
|
|
|
|
|
|
|
# Look up all the variables before we start writing the file, so it's not
|
|
|
|
# created if there's an erro
|
2021-01-25 21:49:01 -05:00
|
|
|
component_srcs = v.get('COMPONENT_SRCS', None)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
component_add_includedirs = v['COMPONENT_ADD_INCLUDEDIRS']
|
|
|
|
cflags = v.get('CFLAGS', None)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
with open(cmakelists_path, 'w') as f:
|
2018-09-10 21:44:12 -04:00
|
|
|
if component_srcs is not None:
|
2021-01-25 21:49:01 -05:00
|
|
|
f.write('idf_component_register(SRCS %s)\n' % component_srcs)
|
|
|
|
f.write(' INCLUDE_DIRS %s' % component_add_includedirs)
|
|
|
|
f.write(' # Edit following two lines to set component requirements (see docs)\n')
|
|
|
|
f.write(' REQUIRES '')\n')
|
|
|
|
f.write(' PRIV_REQUIRES '')\n\n')
|
2018-03-01 02:41:40 -05:00
|
|
|
else:
|
2021-01-25 21:49:01 -05:00
|
|
|
f.write('idf_component_register()\n')
|
2018-03-01 02:41:40 -05:00
|
|
|
if cflags is not None:
|
2021-01-25 21:49:01 -05:00
|
|
|
f.write('target_compile_options(${COMPONENT_LIB} PRIVATE %s)\n' % cflags)
|
2018-03-01 02:41:40 -05:00
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Converted %s' % cmakelists_path)
|
2019-12-19 18:28:41 -05:00
|
|
|
return 1
|
2018-03-01 02:41:40 -05:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
global debug
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='convert_to_cmake.py - ESP-IDF Project Makefile to CMakeLists.txt converter', prog='convert_to_cmake')
|
|
|
|
|
|
|
|
parser.add_argument('--debug', help='Display debugging output',
|
|
|
|
action='store_true')
|
|
|
|
|
|
|
|
parser.add_argument('project', help='Path to project to convert (defaults to CWD)', default=os.getcwd(), metavar='project path', nargs='?')
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
debug = args.debug
|
2021-01-25 21:49:01 -05:00
|
|
|
print('Converting %s...' % args.project)
|
2018-03-01 02:41:40 -05:00
|
|
|
convert_project(args.project)
|
|
|
|
|
|
|
|
|
2021-01-25 21:49:01 -05:00
|
|
|
if __name__ == '__main__':
|
2018-03-01 02:41:40 -05:00
|
|
|
main()
|