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