3 # Command line tool to convert simple ESP-IDF Makefile & component.mk files to
15 def get_make_variables(path, makefile="Makefile", expected_failure=False, variables={}):
17 Given the path to a Makefile of some kind, return a dictionary of all variables defined in this Makefile
19 Uses 'make' to parse the Makefile syntax, so we don't have to!
21 Overrides IDF_PATH= to avoid recursively evaluating the entire project Makefile structure.
23 variable_setters = [ ("%s=%s" % (k,v)) for (k,v) in variables.items() ]
25 cmdline = ["make", "-rpn", "-C", path, "-f", makefile ] + variable_setters
27 print("Running %s..." % (" ".join(cmdline)))
29 p = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
30 (output, stderr) = p.communicate("\n")
32 if (not expected_failure) and p.returncode != 0:
33 raise RuntimeError("Unexpected make failure, result %d" % p.returncode)
41 next_is_makefile = False # is the next line a makefile variable?
43 BUILT_IN_VARS = set(["MAKEFILE_LIST", "SHELL", "CURDIR", "MAKEFLAGS"])
45 for line in output.decode().split("\n"):
46 if line.startswith("# makefile"): # this line appears before any variable defined in the makefile itself
47 next_is_makefile = True
48 elif next_is_makefile:
49 next_is_makefile = False
50 m = re.match(r"(?P<var>[^ ]+) :?= (?P<val>.+)", line)
52 if not m.group("var") in BUILT_IN_VARS:
53 result[m.group("var")] = m.group("val").strip()
57 def get_component_variables(project_path, component_path):
58 make_vars = get_make_variables(component_path,
59 os.path.join(os.environ["IDF_PATH"],
61 "component_wrapper.mk"),
62 expected_failure=True,
64 "COMPONENT_MAKEFILE" : os.path.join(component_path, "component.mk"),
65 "COMPONENT_NAME" : os.path.basename(component_path),
66 "PROJECT_PATH": project_path,
69 if "COMPONENT_OBJS" in make_vars: # component.mk specifies list of object files
72 obj = os.path.splitext(obj)[0]
73 for ext in [ "c", "cpp", "S" ]:
74 if os.path.exists(os.path.join(component_path, obj) + "." + ext):
75 return obj + "." + ext
76 print("WARNING: Can't find source file for component %s COMPONENT_OBJS %s" % (component_path, obj))
80 for obj in make_vars["COMPONENT_OBJS"].split(" "):
84 make_vars["COMPONENT_SRCS"] = " ".join(srcs)
86 component_srcs = list()
87 for component_srcdir in make_vars.get("COMPONENT_SRCDIRS", ".").split(" "):
88 component_srcdir_path = os.path.abspath(os.path.join(component_path, component_srcdir))
91 srcs += glob.glob(os.path.join(component_srcdir_path, "*.[cS]"))
92 srcs += glob.glob(os.path.join(component_srcdir_path, "*.cpp"))
93 srcs = [('"%s"' % str(os.path.relpath(s, component_path))) for s in srcs]
95 make_vars["COMPONENT_ADD_INCLUDEDIRS"] = make_vars.get("COMPONENT_ADD_INCLUDEDIRS", "include")
96 component_srcs += srcs
97 make_vars["COMPONENT_SRCS"] = " ".join(component_srcs)
103 def convert_project(project_path):
104 if not os.path.exists(project_path):
105 raise RuntimeError("Project directory '%s' not found" % project_path)
106 if not os.path.exists(os.path.join(project_path, "Makefile")):
107 raise RuntimeError("Directory '%s' doesn't contain a project Makefile" % project_path)
109 project_cmakelists = os.path.join(project_path, "CMakeLists.txt")
110 if os.path.exists(project_cmakelists):
111 raise RuntimeError("This project already has a CMakeLists.txt file")
113 project_vars = get_make_variables(project_path, expected_failure=True)
114 if not "PROJECT_NAME" in project_vars:
115 raise RuntimeError("PROJECT_NAME does not appear to be defined in IDF project Makefile at %s" % project_path)
117 component_paths = project_vars["COMPONENT_PATHS"].split(" ")
119 # Convert components as needed
120 for p in component_paths:
121 convert_component(project_path, p)
123 project_name = project_vars["PROJECT_NAME"]
125 # Generate the project CMakeLists.txt file
126 with open(project_cmakelists, "w") as f:
128 # (Automatically converted from project Makefile by convert_to_cmake.py.)
130 # The following lines of boilerplate have to be in your project's CMakeLists
131 # in this exact order for cmake to work correctly
132 cmake_minimum_required(VERSION 3.5)
136 include($ENV{IDF_PATH}/tools/cmake/project.cmake)
138 f.write("project(%s)\n" % project_name)
140 print("Converted project %s" % project_cmakelists)
142 def convert_component(project_path, component_path):
144 print("Converting %s..." % (component_path))
145 cmakelists_path = os.path.join(component_path, "CMakeLists.txt")
146 if os.path.exists(cmakelists_path):
147 print("Skipping already-converted component %s..." % cmakelists_path)
149 v = get_component_variables(project_path, component_path)
151 # Look up all the variables before we start writing the file, so it's not
152 # created if there's an erro
153 component_srcs = v.get("COMPONENT_SRCS", None)
155 component_add_includedirs = v["COMPONENT_ADD_INCLUDEDIRS"]
156 cflags = v.get("CFLAGS", None)
158 with open(cmakelists_path, "w") as f:
159 f.write("set(COMPONENT_ADD_INCLUDEDIRS %s)\n\n" % component_add_includedirs)
161 f.write("# Edit following two lines to set component requirements (see docs)\n")
162 f.write("set(COMPONENT_REQUIRES "")\n")
163 f.write("set(COMPONENT_PRIV_REQUIRES "")\n\n")
165 if component_srcs is not None:
166 f.write("set(COMPONENT_SRCS %s)\n\n" % component_srcs)
167 f.write("register_component()\n")
169 f.write("register_config_only_component()\n")
170 if cflags is not None:
171 f.write("component_compile_options(%s)\n" % cflags)
173 print("Converted %s" % cmakelists_path)
179 parser = argparse.ArgumentParser(description='convert_to_cmake.py - ESP-IDF Project Makefile to CMakeLists.txt converter', prog='convert_to_cmake')
181 parser.add_argument('--debug', help='Display debugging output',
184 parser.add_argument('project', help='Path to project to convert (defaults to CWD)', default=os.getcwd(), metavar='project path', nargs='?')
186 args = parser.parse_args()
188 print("Converting %s..." % args.project)
189 convert_project(args.project)
192 if __name__ == "__main__":