| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- #!/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()
|