Remove CROSS from the mk template
[bertos.git] / wizard / bertos_utils.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 #
4 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
5 # All rights reserved.
6 #
7 # $Id:$
8 #
9 # Author: Lorenzo Berni <duplo@develer.com>
10 #
11
12 import os
13 import fnmatch
14 import glob
15 import re
16 import shutil
17 import pickle
18
19 import const
20 from plugins import codelite_project
21 import DefineException
22
23 def isBertosDir(directory):
24    return os.path.exists(directory + "/VERSION")
25
26 def bertosVersion(directory):
27    return open(directory + "/VERSION").readline().strip()
28
29 def createBertosProject(project_info):
30     directory = project_info.info("PROJECT_PATH")
31     sources_dir = project_info.info("SOURCES_PATH")
32     if not os.path.isdir(directory):
33         os.mkdir(directory)
34     f = open(directory + "/project.bertos", "w")
35     f.write(pickle.dumps(project_info))
36     f.close()
37     # Destination source dir
38     srcdir = directory + "/bertos"
39     shutil.rmtree(srcdir, True)
40     shutil.copytree(sources_dir + "/bertos", srcdir)
41     # Destination makefile
42     makefile = directory + "/Makefile"
43     if os.path.exists(makefile):
44         os.remove(makefile)
45     makefile = open("mktemplates/Makefile").read()
46     makefile = makefileGenerator(project_info, makefile)
47     open(directory + "/Makefile", "w").write(makefile)
48     # Destination project dir
49     prjdir = directory + "/" + os.path.basename(directory)
50     shutil.rmtree(prjdir, True)
51     os.mkdir(prjdir)
52     # Destination configurations files
53     cfgdir = prjdir + "/cfg"
54     shutil.rmtree(cfgdir, True)
55     os.mkdir(cfgdir)
56     for configuration, information in project_info.info("CONFIGURATIONS").items():
57         string = open(sources_dir + "/" + configuration, "r").read()
58         for start, parameter in information["paramlist"]:
59             infos = information[parameter]
60             value = infos["value"]
61             if "type" in infos["informations"] and infos["informations"]["type"] == "autoenabled":
62                 value = "1"
63             if "unsigned" in infos["informations"].keys() and infos["informations"]["unsigned"]:
64                 value += "U"
65             if "long" in infos["informations"].keys() and infos["informations"]["long"]:
66                 value += "L"
67             string = sub(string, parameter, value)
68         f = open(cfgdir + "/" + os.path.basename(configuration), "w")
69         f.write(string)
70         f.close()
71     # Destinatio mk file
72     makefile = open("mktemplates/template.mk", "r").read()
73     makefile = mkGenerator(project_info, makefile)
74     open(prjdir + "/" + os.path.basename(prjdir) + ".mk", "w").write(makefile)
75     # Destination main.c file
76     main = open("srctemplates/main.c", "r").read()
77     open(prjdir + "/main.c", "w").write(main)
78     # Codelite project files
79     if "codelite" in project_info.info("OUTPUT"):
80         codelite_project.createProject(project_info)
81
82 def mkGenerator(project_info, makefile):
83     """
84     Generates the mk file for the current project.
85     """
86     mk_data = {}
87     mk_data["$pname"] = os.path.basename(project_info.info("PROJECT_PATH"))
88     mk_data["$cpuname"] = project_info.info("CPU_INFOS")["CORE_CPU"]
89     mk_data["$cflags"] = " ".join(project_info.info("CPU_INFOS")["C_FLAGS"])
90     mk_data["$ldflags"] = " ".join(project_info.info("CPU_INFOS")["LD_FLAGS"])
91     mk_data["$csrc"], mk_data["$pcsrc"], mk_data["$asrc"], mk_data["$constants"] = csrcGenerator(project_info)
92     mk_data["$prefix"] = project_info.info("TOOLCHAIN")["path"].split("gcc")[0]
93     mk_data["$suffix"] = project_info.info("TOOLCHAIN")["path"].split("gcc")[1]
94     mk_data["$main"] = os.path.basename(project_info.info("PROJECT_PATH")) + "/main.c"
95     for key in mk_data:
96         while makefile.find(key) != -1:
97             makefile = makefile.replace(key, mk_data[key])
98     return makefile
99
100 def makefileGenerator(project_info, makefile):
101     """
102     Generate the Makefile for the current project.
103     """
104     # TODO write a general function that works for both the mk file and the Makefile
105     while makefile.find("project_name") != -1:
106         makefile = makefile.replace("project_name", os.path.basename(project_info.info("PROJECT_PATH")))
107     return makefile
108
109 def csrcGenerator(project_info):
110     modules = project_info.info("MODULES")
111     files = project_info.info("FILES")
112     if "harvard" in project_info.info("CPU_INFOS")["CPU_TAGS"]:
113         harvard = True
114     else:
115         harvard = False
116     # file to be included in CSRC variable
117     csrc = []
118     # file to be included in PCSRC variable
119     pcsrc = []
120     # files to be included in CPPASRC variable
121     asrc = []
122     # constants to be included at the beginning of the makefile
123     constants = {}
124     for module, information in modules.items():
125         module_files = set([])
126         dependency_files = set([])
127         # assembly sources
128         asm_files = set([])
129         if information["enabled"]:
130             if "constants" in information:
131                 constants.update(information["constants"])
132             cfiles, sfiles = findModuleFiles(module, project_info)
133             module_files |= set(cfiles)
134             asm_files |= set(sfiles)
135             for file_dependency in information["depends"]:
136                 if file_dependency in files:
137                     dependencyCFiles, dependencySFiles = findModuleFiles(file_dependency, project_info)
138                     dependency_files |= set(dependencyCFiles)
139                     asm_files |= set(dependencySFiles)
140             for file in module_files:
141                 if not harvard or "harvard" not in information or information["harvard"] == "both":
142                     csrc.append(file)
143                 if harvard and "harvard" in information:
144                     pcsrc.append(file)
145             for file in dependency_files:
146                 csrc.append(file)
147             for file in asm_files:
148                 asrc.append(file)
149     csrc = " \\\n\t".join(csrc) + " \\"
150     pcsrc = " \\\n\t".join(pcsrc) + " \\"
151     asrc = " \\\n\t".join(asrc) + " \\"
152     constants = "\n".join([os.path.basename(project_info.info("PROJECT_PATH")) + "_" + key + " = " + str(value) for key, value in constants.items()])
153     return csrc, pcsrc, asrc, constants
154
155 def findModuleFiles(module, project_info):
156     # Find the files related to the selected module
157     cfiles = []
158     sfiles = []
159     # .c files related to the module and the cpu architecture
160     for filename, path in findDefinitions(module + ".c", project_info) + \
161             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".c", project_info):
162         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
163         if os.sep != "/":
164             path = replaceSeparators(path)
165         cfiles.append(path + "/" + filename)
166     # .s files related to the module and the cpu architecture
167     for filename, path in findDefinitions(module + ".s", project_info) + \
168             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".s", project_info) + \
169             findDefinitions(module + ".S", project_info) + \
170             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".S", project_info):
171         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
172         if os.sep != "/":
173             path = replaceSeparators(path)
174         sfiles.append(path + "/" + filename)
175     # .c and .s files related to the module and the cpu tags
176     for tag in project_info.info("CPU_INFOS")["CPU_TAGS"]:
177         for filename, path in findDefinitions(module + "_" + tag + ".c", project_info):
178             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
179             if os.sep != "/":
180                 path = replaceSeparators(path)
181             cfiles.append(path + "/" + filename)
182         for filename, path in findDefinitions(module + "_" + tag + ".s", project_info) + \
183                 findDefinitions(module + "_" + tag + ".S", project_info):
184             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
185             if os.sep != "/":
186                 path = replaceSeparators(path)
187             sfiles.append(path + "/" + filename)
188     return cfiles, sfiles
189
190 def replaceSeparators(path):
191     """
192     Replace the separators in the given path with unix standard separator.
193     """
194     while path.find(os.sep) != -1:
195         path = path.replace(os.sep, "/")
196     return path
197
198 def getSystemPath():
199     path = os.environ["PATH"]
200     if os.name == "nt":
201         path = path.split(";")
202     else:
203         path = path.split(":")
204     return path
205
206 def findToolchains(path_list):
207     toolchains = []
208     for element in path_list:
209         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
210             toolchains.append(toolchain)
211     return list(set(toolchains))
212
213 def getToolchainInfo(output):
214     info = {}
215     expr = re.compile("Target: .*")
216     target = expr.findall(output)
217     if len(target) == 1:
218         info["target"] = target[0].split("Target: ")[1]
219     expr = re.compile("gcc version [0-9,.]*")
220     version = expr.findall(output)
221     if len(version) == 1:
222         info["version"] = version[0].split("gcc version ")[1]
223     expr = re.compile("gcc version [0-9,.]* \(.*\)")
224     build = expr.findall(output)
225     if len(build) == 1:
226         build = build[0].split("gcc version ")[1]
227         build = build[build.find("(") + 1 : build.find(")")]
228         info["build"] = build
229     expr = re.compile("Configured with: .*")
230     configured = expr.findall(output)
231     if len(configured) == 1:
232         info["configured"] = configured[0].split("Configured with: ")[1]
233     expr = re.compile("Thread model: .*")
234     thread = expr.findall(output)
235     if len(thread) == 1:
236         info["thread"] = thread[0].split("Thread model: ")[1]
237     return info
238
239 def loadSourceTree(project):
240     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
241     project.setInfo("FILE_LIST", fileList)
242
243 def findDefinitions(ftype, project):
244     L = project.info("FILE_LIST")
245     definitions = []
246     for element in L:
247         for filename in element[2]:
248             if fnmatch.fnmatch(filename, ftype):
249                 definitions.append((filename, element[0]))
250     return definitions
251
252 def loadCpuInfos(project):
253     cpuInfos = []
254     for definition in findDefinitions(const.CPU_DEFINITION, project):
255         cpuInfos.append(getInfos(definition))
256     return cpuInfos
257
258 def getInfos(definition):
259     D = {}
260     D.update(const.CPU_DEF)
261     def include(filename, dict = D, directory=definition[1]):
262         execfile(directory + "/" + filename, {}, D)
263     D["include"] = include
264     include(definition[0], D)
265     D["CPU_NAME"] = definition[0].split(".")[0]
266     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
267     del D["include"]
268     return D
269
270 def getCommentList(string):
271     comment_list = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
272     comment_list = [re.findall(r"^\s*\* *(.*?)$", comment, re.MULTILINE) for comment in comment_list]
273     return comment_list
274
275 def loadModuleDefinition(first_comment):
276     to_be_parsed = False
277     module_definition = {}
278     for num, line in enumerate(first_comment):
279         index = line.find("$WIZ$")
280         if index != -1:
281             to_be_parsed = True
282             try:
283                 exec line[index + len("$WIZ$ "):] in {}, module_definition
284             except:
285                 raise ParseError(num, line[index:])
286         elif line.find("\\brief") != -1:
287             module_definition["module_description"] = line[line.find("\\brief") + len("\\brief "):]
288     module_dict = {}
289     if "module_name" in module_definition.keys():
290         module_name = module_definition[const.MODULE_DEFINITION["module_name"]]
291         del module_definition[const.MODULE_DEFINITION["module_name"]]
292         module_dict[module_name] = {}
293         if const.MODULE_DEFINITION["module_depends"] in module_definition.keys():
294             if type(module_definition[const.MODULE_DEFINITION["module_depends"]]) == str:
295                 module_definition[const.MODULE_DEFINITION["module_depends"]] = (module_definition[const.MODULE_DEFINITION["module_depends"]],)
296             module_dict[module_name]["depends"] = module_definition[const.MODULE_DEFINITION["module_depends"]]
297             del module_definition[const.MODULE_DEFINITION["module_depends"]]
298         else:
299             module_dict[module_name]["depends"] = ()
300         if const.MODULE_DEFINITION["module_configuration"] in module_definition.keys():
301             module_dict[module_name]["configuration"] = module_definition[const.MODULE_DEFINITION["module_configuration"]]
302             del module_definition[const.MODULE_DEFINITION["module_configuration"]]
303         else:
304             module_dict[module_name]["configuration"] = ""
305         if "module_description" in module_definition.keys():
306             module_dict[module_name]["description"] = module_definition["module_description"]
307             del module_definition["module_description"]
308         if const.MODULE_DEFINITION["module_harvard"] in module_definition.keys():
309             harvard = module_definition[const.MODULE_DEFINITION["module_harvard"]]
310             if harvard == "both" or harvard == "pgm_memory":
311                 module_dict[module_name]["harvard"] = harvard
312             del module_definition[const.MODULE_DEFINITION["module_harvard"]]
313         module_dict[module_name]["constants"] = module_definition
314         module_dict[module_name]["enabled"] = False
315     return to_be_parsed, module_dict
316
317 def loadDefineLists(comment_list):
318     define_list = {}
319     for comment in comment_list:
320         for num, line in enumerate(comment):
321             index = line.find("$WIZ$")
322             if index != -1:
323                 try:
324                     exec line[index + len("$WIZ$ "):] in {}, define_list
325                 except:
326                     raise ParseError(num, line[index:])
327     for key, value in define_list.items():
328         if type(value) == str:
329             define_list[key] = (value,)
330     return define_list
331
332 def getDescriptionInformations(comment):
333     """
334     Take the doxygen comment and strip the wizard informations, returning the tuple
335     (comment, wizard_information)
336     """
337     brief = ""
338     description = ""
339     information = {}
340     for num, line in enumerate(comment):
341         index = line.find("$WIZ$")
342         if index != -1:
343             if len(brief) == 0:
344                 brief += line[:index].strip()
345             else:
346                 description += " " + line[:index]
347             try:
348                 exec line[index + len("$WIZ$ "):] in {}, information
349             except:
350                 raise ParseError(num, line[index:])
351         else:
352             if len(brief) == 0:
353                 brief += line.strip()
354             else:
355                 description += " " + line
356                 description = description.strip()
357     return brief.strip(), description.strip(), information
358
359 def getDefinitionBlocks(text):
360     """
361     Take a text and return a list of tuple (description, name-value).
362     """
363     block = []
364     block_tmp = re.finditer(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
365     for match in block_tmp:
366         # Only the first element is needed
367         comment = match.group(1)
368         define = match.group(2)
369         start = match.start()
370         block.append(([re.findall(r"^\s*\* *(.*?)$", line, re.MULTILINE)[0] for line in comment.splitlines()], define, start))
371     for match in re.finditer(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
372         comment = match.group(1)
373         define = match.group(2)
374         start = match.start()
375         block.append(([comment], define, start))
376     for match in re.finditer(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
377         comment = match.group(2)
378         define = match.group(1)
379         start = match.start()
380         block.append(([comment], define, start))
381     return block
382
383 def loadModuleData(project):
384     module_info_dict = {}
385     list_info_dict = {}
386     configuration_info_dict = {}
387     file_dict = {}
388     for filename, path in findDefinitions("*.h", project) + findDefinitions("*.c", project) + findDefinitions("*.s", project) + findDefinitions("*.S", project):
389         comment_list = getCommentList(open(path + "/" + filename, "r").read())
390         if len(comment_list) > 0:
391             module_info = {}
392             configuration_info = {}
393             try:
394                 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
395             except ParseError, err:
396                 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
397             for module, information in module_dict.items():
398                 if "depends" not in information:
399                     information["depends"] = ()
400                 information["depends"] += (filename.split(".")[0],)
401                 information["category"] = os.path.basename(path)
402                 if "configuration" in information.keys() and len(information["configuration"]):
403                     configuration = module_dict[module]["configuration"]
404                     try:
405                         configuration_info[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
406                     except ParseError, err:
407                         raise DefineException.ConfigurationDefineException(project.info("SOURCES_PATH") + "/" + configuration, err.line_number, err.line)
408             module_info_dict.update(module_dict)
409             configuration_info_dict.update(configuration_info)
410             if to_be_parsed:
411                 try:
412                     list_dict = loadDefineLists(comment_list[1:])
413                     list_info_dict.update(list_dict)
414                 except ParseError, err:
415                     raise DefineException.EnumDefineException(path, err.line_number, err.line)
416     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
417         comment_list = getCommentList(open(path + "/" + filename, "r").read())
418         list_info_dict.update(loadDefineLists(comment_list))
419     for tag in project.info("CPU_INFOS")["CPU_TAGS"]:
420         for filename, path in findDefinitions("*_" + tag + ".h", project):
421             comment_list = getCommentList(open(path + "/" + filename, "r").read())
422             list_info_dict.update(loadDefineLists(comment_list))
423     project.setInfo("MODULES", module_info_dict)
424     project.setInfo("LISTS", list_info_dict)
425     project.setInfo("CONFIGURATIONS", configuration_info_dict)
426     project.setInfo("FILES", file_dict)
427
428 def formatParamNameValue(text):
429     """
430     Take the given string and return a tuple with the name of the parameter in the first position
431     and the value in the second.
432     """
433     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
434     return block[0]
435
436 def loadConfigurationInfos(path):
437     """
438     Return the module configurations found in the given file as a dict with the
439     parameter name as key and a dict containig the fields above as value:
440         "value": the value of the parameter
441         "description": the description of the parameter
442         "informations": a dict containig optional informations:
443             "type": "int" | "boolean" | "enum"
444             "min": the minimum value for integer parameters
445             "max": the maximum value for integer parameters
446             "long": boolean indicating if the num is a long
447             "unsigned": boolean indicating if the num is an unsigned
448             "value_list": the name of the enum for enum parameters
449     """
450     configuration_infos = {}
451     configuration_infos["paramlist"] = []
452     for comment, define, start in getDefinitionBlocks(open(path, "r").read()):
453         name, value = formatParamNameValue(define)
454         brief, description, informations = getDescriptionInformations(comment)
455         configuration_infos["paramlist"].append((start, name))
456         configuration_infos[name] = {}
457         configuration_infos[name]["value"] = value
458         configuration_infos[name]["informations"] = informations
459         if not "type" in configuration_infos[name]["informations"]:
460             configuration_infos[name]["informations"]["type"] = findParameterType(configuration_infos[name])
461         if ("type" in configuration_infos[name]["informations"].keys() and
462                 configuration_infos[name]["informations"]["type"] == "int" and
463                 configuration_infos[name]["value"].find("L") != -1):
464             configuration_infos[name]["informations"]["long"] = True
465             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("L", "")
466         if ("type" in configuration_infos[name]["informations"].keys() and
467                 configuration_infos[name]["informations"]["type"] == "int" and
468                 configuration_infos[name]["value"].find("U") != -1):
469             configuration_infos[name]["informations"]["unsigned"] = True
470             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("U", "")
471         configuration_infos[name]["description"] = description
472         configuration_infos[name]["brief"] = brief
473     return configuration_infos
474
475 def findParameterType(parameter):
476     if "value_list" in parameter["informations"]:
477         return "enum"
478     if "min" in parameter["informations"] or "max" in parameter["informations"] or re.match(r"^\d+U?L?$", parameter["value"]) != None:
479         return "int"
480
481 def sub(string, parameter, value):
482     """
483     Substitute the given value at the given parameter define in the given string
484     """
485     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
486
487 def isInt(informations):
488     """
489     Return True if the value is a simple int.
490     """
491     if ("long" not in informatios.keys() or not informations["long"]) and ("unsigned" not in informations.keys() or informations["unsigned"]):
492         return True
493     else:
494         return False
495
496 def isLong(informations):
497     """
498     Return True if the value is a long.
499     """
500     if "long" in informations.keys() and informations["long"] and "unsigned" not in informations.keys():
501         return True
502     else:
503         return False
504
505 def isUnsigned(informations):
506     """
507     Return True if the value is an unsigned.
508     """
509     if "unsigned" in informations.keys() and informations["unsigned"] and "long" not in informations.keys():
510         return True
511     else:
512         return False
513
514 def isUnsignedLong(informations):
515     """
516     Return True if the value is an unsigned long.
517     """
518     if "unsigned" in informations.keys() and "long" in informations.keys() and informations["unsigned"] and informations["long"]:
519         return True
520     else:
521         return False
522
523 class ParseError(Exception):
524     def __init__(self, line_number, line):
525         Exception.__init__(self)
526         self.line_number = line_number
527         self.line = line