Fixes an issue with Python 3 in MSYS where it fails while trying to join paths where one part is Unicode (default string on Python3) and the second part are bytes (returned by the subprocess call). Closes https://github.com/espressif/esp-idf/issues/5189
205 lines
8 KiB
Python
Executable file
205 lines
8 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#
|
|
# Command line tool to convert simple ESP-IDF Makefile & component.mk files to
|
|
# CMakeLists.txt files
|
|
#
|
|
import argparse
|
|
import subprocess
|
|
import re
|
|
import os.path
|
|
import glob
|
|
|
|
debug = False
|
|
|
|
|
|
def get_make_variables(path, makefile="Makefile", expected_failure=False, variables={}):
|
|
"""
|
|
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.
|
|
"""
|
|
variable_setters = [("%s=%s" % (k,v)) for (k,v) in variables.items()]
|
|
|
|
cmdline = ["make", "-rpn", "-C", path, "-f", makefile] + variable_setters
|
|
if debug:
|
|
print("Running %s..." % (" ".join(cmdline)))
|
|
|
|
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(output, stderr) = p.communicate("\n")
|
|
|
|
if (not expected_failure) and p.returncode != 0:
|
|
raise RuntimeError("Unexpected make failure, result %d" % p.returncode)
|
|
|
|
if debug:
|
|
print("Make stdout:")
|
|
print(output)
|
|
print("Make stderr:")
|
|
print(stderr)
|
|
|
|
next_is_makefile = False # is the next line a makefile variable?
|
|
result = {}
|
|
BUILT_IN_VARS = set(["MAKEFILE_LIST", "SHELL", "CURDIR", "MAKEFLAGS"])
|
|
|
|
for line in output.decode('utf-8').split("\n"):
|
|
if line.startswith("# makefile"): # this line appears before any variable defined in the makefile itself
|
|
next_is_makefile = True
|
|
elif next_is_makefile:
|
|
next_is_makefile = False
|
|
m = re.match(r"(?P<var>[^ ]+) :?= (?P<val>.+)", line)
|
|
if m is not None:
|
|
if not m.group("var") in BUILT_IN_VARS:
|
|
result[m.group("var")] = m.group("val").strip()
|
|
|
|
return result
|
|
|
|
|
|
def get_component_variables(project_path, component_path):
|
|
make_vars = get_make_variables(component_path,
|
|
os.path.join(os.environ["IDF_PATH"],
|
|
"make",
|
|
"component_wrapper.mk"),
|
|
expected_failure=True,
|
|
variables={
|
|
"COMPONENT_MAKEFILE": os.path.join(component_path, "component.mk"),
|
|
"COMPONENT_NAME": os.path.basename(component_path),
|
|
"PROJECT_PATH": project_path,
|
|
})
|
|
|
|
if "COMPONENT_OBJS" in make_vars: # component.mk specifies list of object files
|
|
# Convert to sources
|
|
def find_src(obj):
|
|
obj = os.path.splitext(obj)[0]
|
|
for ext in ["c", "cpp", "S"]:
|
|
if os.path.exists(os.path.join(component_path, obj) + "." + ext):
|
|
return obj + "." + ext
|
|
print("WARNING: Can't find source file for component %s COMPONENT_OBJS %s" % (component_path, obj))
|
|
return None
|
|
|
|
srcs = []
|
|
for obj in make_vars["COMPONENT_OBJS"].split():
|
|
src = find_src(obj)
|
|
if src is not None:
|
|
srcs.append(src)
|
|
make_vars["COMPONENT_SRCS"] = " ".join(srcs)
|
|
else:
|
|
component_srcs = list()
|
|
for component_srcdir in make_vars.get("COMPONENT_SRCDIRS", ".").split():
|
|
component_srcdir_path = os.path.abspath(os.path.join(component_path, component_srcdir))
|
|
|
|
srcs = list()
|
|
srcs += glob.glob(os.path.join(component_srcdir_path, "*.[cS]"))
|
|
srcs += glob.glob(os.path.join(component_srcdir_path, "*.cpp"))
|
|
srcs = [('"%s"' % str(os.path.relpath(s, component_path))) for s in srcs]
|
|
|
|
make_vars["COMPONENT_ADD_INCLUDEDIRS"] = make_vars.get("COMPONENT_ADD_INCLUDEDIRS", "include")
|
|
component_srcs += srcs
|
|
make_vars["COMPONENT_SRCS"] = " ".join(component_srcs)
|
|
|
|
return make_vars
|
|
|
|
|
|
def convert_project(project_path):
|
|
if not os.path.exists(project_path):
|
|
raise RuntimeError("Project directory '%s' not found" % project_path)
|
|
if not os.path.exists(os.path.join(project_path, "Makefile")):
|
|
raise RuntimeError("Directory '%s' doesn't contain a project Makefile" % project_path)
|
|
|
|
project_cmakelists = os.path.join(project_path, "CMakeLists.txt")
|
|
if os.path.exists(project_cmakelists):
|
|
raise RuntimeError("This project already has a CMakeLists.txt file")
|
|
|
|
project_vars = get_make_variables(project_path, expected_failure=True)
|
|
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)
|
|
|
|
component_paths = project_vars["COMPONENT_PATHS"].split()
|
|
|
|
converted_components = 0
|
|
|
|
# Convert components as needed
|
|
for p in component_paths:
|
|
if "MSYSTEM" in os.environ:
|
|
cmd = ["cygpath", "-w", p]
|
|
p = subprocess.check_output(cmd).decode('utf-8').strip()
|
|
|
|
converted_components += convert_component(project_path, p)
|
|
|
|
project_name = project_vars["PROJECT_NAME"]
|
|
|
|
# Generate the project CMakeLists.txt file
|
|
with open(project_cmakelists, "w") as f:
|
|
f.write("""
|
|
# (Automatically converted from project Makefile by convert_to_cmake.py.)
|
|
|
|
# The following lines of boilerplate have to be in your project's CMakeLists
|
|
# 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)
|
|
""")
|
|
f.write("project(%s)\n" % project_name)
|
|
|
|
print("Converted project %s" % project_cmakelists)
|
|
|
|
if converted_components > 0:
|
|
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 "
|
|
"components' header files. If so requirements need to be added to the components' "
|
|
"CMakeLists.txt files. See the 'Component Requirements' section of the "
|
|
"Build System docs for more details.")
|
|
|
|
|
|
def convert_component(project_path, component_path):
|
|
if debug:
|
|
print("Converting %s..." % (component_path))
|
|
cmakelists_path = os.path.join(component_path, "CMakeLists.txt")
|
|
if os.path.exists(cmakelists_path):
|
|
print("Skipping already-converted component %s..." % cmakelists_path)
|
|
return 0
|
|
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
|
|
component_srcs = v.get("COMPONENT_SRCS", None)
|
|
|
|
component_add_includedirs = v["COMPONENT_ADD_INCLUDEDIRS"]
|
|
cflags = v.get("CFLAGS", None)
|
|
|
|
with open(cmakelists_path, "w") as f:
|
|
if component_srcs is not None:
|
|
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")
|
|
else:
|
|
f.write("idf_component_register()\n")
|
|
if cflags is not None:
|
|
f.write("target_compile_options(${COMPONENT_LIB} PRIVATE %s)\n" % cflags)
|
|
|
|
print("Converted %s" % cmakelists_path)
|
|
return 1
|
|
|
|
|
|
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
|
|
print("Converting %s..." % args.project)
|
|
convert_project(args.project)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|