Add missing prototypes.
[bertos.git] / wizard / bertos_utils.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 #
4 # This file is part of BeRTOS.
5 #
6 # Bertos is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19 #
20 # As a special exception, you may use this file as part of a free software
21 # library without restriction.  Specifically, if other files instantiate
22 # templates or use macros or inline functions from this file, or you compile
23 # this file and link it with other files to produce an executable, this
24 # file does not by itself cause the resulting executable to be covered by
25 # the GNU General Public License.  This exception does not however
26 # invalidate any other reasons why the executable file might be covered by
27 # the GNU General Public License.
28 #
29 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
30 #
31 # $Id$
32 #
33 # Author: Lorenzo Berni <duplo@develer.com>
34 #
35
36 import os
37 import fnmatch
38 import glob
39 import re
40 import shutil
41 # Use custom copytree function
42 import copytree
43 import pickle
44
45 import const
46 import plugins
47 import DefineException
48 import BProject
49
50 from LoadException import VersionException, ToolchainException
51
52 def isBertosDir(directory):
53    return os.path.exists(directory + "/VERSION")
54
55 def bertosVersion(directory):
56    return open(directory + "/VERSION").readline().strip()
57
58 def loadBertosProject(project_file, info_dict):
59     project_data = pickle.loads(open(project_file, "r").read())
60     project_info = BProject.BProject()
61     project_info.setInfo("PROJECT_PATH", os.path.dirname(project_file))
62     if "SOURCES_PATH" in info_dict:
63         project_data["SOURCES_PATH"] = info_dict["SOURCES_PATH"]
64     if os.path.exists(project_data["SOURCES_PATH"]):
65         project_info.setInfo("SOURCES_PATH", project_data["SOURCES_PATH"])
66     else:
67         raise VersionException(project_info)
68     loadSourceTree(project_info)
69     cpu_name = project_data["CPU_NAME"]
70     project_info.setInfo("CPU_NAME", cpu_name)
71     cpu_info = loadCpuInfos(project_info)
72     for cpu in cpu_info:
73         if cpu["CPU_NAME"] == cpu_name:
74             project_info.setInfo("CPU_INFOS", cpu)
75             break
76     tag_list = getTagSet(cpu_info)
77     # Create, fill and store the dict with the tags
78     tag_dict = {}
79     for element in tag_list:
80         tag_dict[element] = False
81     infos = project_info.info("CPU_INFOS")
82     for tag in tag_dict:
83         if tag in infos["CPU_TAGS"] + [infos["CPU_NAME"], infos["TOOLCHAIN"]]:
84             tag_dict[tag] = True
85         else:
86             tag_dict[tag] = False
87     project_info.setInfo("ALL_CPU_TAGS", tag_dict)
88     if "TOOLCHAIN" in info_dict:
89         project_data["TOOLCHAIN"] = info_dict["TOOLCHAIN"]
90     if os.path.exists(project_data["TOOLCHAIN"]["path"]):
91         project_info.setInfo("TOOLCHAIN", project_data["TOOLCHAIN"])
92     else:
93         raise ToolchainException(project_info)
94     project_info.setInfo("SELECTED_FREQ", project_data["SELECTED_FREQ"])
95     project_info.setInfo("OUTPUT", project_data["OUTPUT"])
96     loadModuleData(project_info, True)
97     setEnabledModules(project_info, project_data["ENABLED_MODULES"])
98     return project_info
99
100 def setEnabledModules(project_info, enabled_modules):
101     modules = project_info.info("MODULES")
102     files = {}
103     for module, information in modules.items():
104         information["enabled"] = module in enabled_modules
105         for dependency in information["depends"]:
106             if not dependency in modules:
107                 if dependency in files:
108                     files[dependency] += 1
109                 else:
110                     files[dependency] = 1
111     project_info.setInfo("MODULES", modules)
112     project_info.setInfo("FILES", files)
113
114 def enabledModules(project_info):
115     enabled_modules = []
116     for name, module in project_info.info("MODULES").items():
117         if module["enabled"]:
118             enabled_modules.append(name)
119     return enabled_modules
120
121 def mergeSources(srcdir, new_sources, old_sources):
122     # The current mergeSources function provide only a raw copy of the sources in the
123     # created project.
124     #
125     # TODO: implement the three way merge algorithm
126     #
127     shutil.rmtree(srcdir, True)
128     copytree.copytree(os.path.join(new_sources, "bertos"), srcdir, ignore_list=const.IGNORE_LIST)
129
130 def projectFileGenerator(project_info):
131     directory = project_info.info("PROJECT_PATH")
132     project_data = {}
133     enabled_modules = []
134     for module, information in project_info.info("MODULES").items():
135         if information["enabled"]:
136             enabled_modules.append(module)
137     project_data["ENABLED_MODULES"] = enabled_modules
138     project_data["SOURCES_PATH"] = project_info.info("SOURCES_PATH")
139     project_data["TOOLCHAIN"] = project_info.info("TOOLCHAIN")
140     project_data["CPU_NAME"] = project_info.info("CPU_NAME")
141     project_data["SELECTED_FREQ"] = project_info.info("SELECTED_FREQ")
142     project_data["OUTPUT"] = project_info.info("OUTPUT")
143     return pickle.dumps(project_data)
144
145 def createBertosProject(project_info, edit=False):
146     directory = project_info.info("PROJECT_PATH")
147     sources_dir = project_info.info("SOURCES_PATH")
148     old_sources_dir = project_info.info("OLD_SOURCES_PATH")
149     if not edit:
150         if os.path.isdir(directory):
151             shutil.rmtree(directory, True)        
152         os.makedirs(directory)
153     # Write the project file
154     f = open(directory + "/project.bertos", "w")
155     f.write(projectFileGenerator(project_info))
156     f.close()
157     # Destination source dir
158     srcdir = directory + "/bertos"
159     if not edit:
160         # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
161         shutil.rmtree(srcdir, True)
162         copytree.copytree(sources_dir + "/bertos", srcdir, ignore_list=const.IGNORE_LIST)
163     elif old_sources_dir:
164         # If in editing mode it merges the current bertos sources with the selected ones
165         # TODO: implement the three way merge algotihm
166         #
167         mergeSources(srcdir, sources_dir, old_sources_dir)
168     # Destination makefile
169     makefile = directory + "/Makefile"
170     makefile = open("mktemplates/Makefile").read()
171     makefile = makefileGenerator(project_info, makefile)
172     open(directory + "/Makefile", "w").write(makefile)
173     # Destination project dir
174     prjdir = directory + "/" + os.path.basename(directory)
175     if not edit:
176         shutil.rmtree(prjdir, True)
177         os.mkdir(prjdir)
178     # Destination hw files
179     hwdir = prjdir + "/hw"
180     if not edit:
181         shutil.rmtree(hwdir, True)
182         os.mkdir(hwdir)
183     # Copy all the hw files
184     for module, information in project_info.info("MODULES").items():
185         for hwfile in information["hw"]:
186             string = open(sources_dir + "/" + hwfile, "r").read()
187             hwfile_path = hwdir + "/" + os.path.basename(hwfile)
188             if not edit or not os.path.exists(hwfile_path):
189                 # If not in editing mode it copies all the hw files. If in
190                 # editing mode it copies only the files that don't exist yet
191                 open(hwdir + "/" + os.path.basename(hwfile), "w").write(string)
192     # Destination configurations files
193     cfgdir = prjdir + "/cfg"
194     if not edit:
195         shutil.rmtree(cfgdir, True)
196         os.mkdir(cfgdir)
197     # Set properly the autoenabled parameters
198     for module, information in project_info.info("MODULES").items():
199         if "configuration" in information and information["configuration"] != "":
200             configurations = project_info.info("CONFIGURATIONS")
201             configuration = configurations[information["configuration"]]
202             for start, parameter in configuration["paramlist"]:
203                 if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
204                     configuration[parameter]["value"] = "1" if information["enabled"] else "0"
205             project_info.setInfo("CONFIGURATIONS", configurations)
206     # Copy all the configuration files
207     for configuration, information in project_info.info("CONFIGURATIONS").items():
208         string = open(sources_dir + "/" + configuration, "r").read()
209         for start, parameter in information["paramlist"]:
210             infos = information[parameter]
211             value = infos["value"]
212             if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
213                 value += "U"
214             if "long" in infos["informations"] and infos["informations"]["long"]:
215                 value += "L"
216             string = sub(string, parameter, value)
217         f = open(cfgdir + "/" + os.path.basename(configuration), "w")
218         f.write(string)
219         f.close()
220     if not edit:
221         # Destination user mk file (only on project creation)
222         makefile = open("mktemplates/template.mk", "r").read()
223         makefile = mkGenerator(project_info, makefile)
224         open(prjdir + "/" + os.path.basename(prjdir) + ".mk", "w").write(makefile)
225     # Destination wizard mk file
226     makefile = open("mktemplates/template_wiz.mk", "r").read()
227     makefile = mkGenerator(project_info, makefile)
228     open(prjdir + "/" + os.path.basename(prjdir) + "_wiz.mk", "w").write(makefile)
229     # Destination main.c file
230     if not edit:
231         main = open("srctemplates/main.c", "r").read()
232         open(prjdir + "/main.c", "w").write(main)
233     # Files for selected plugins
234     relevants_files = {}
235     for plugin in project_info.info("OUTPUT"):
236         module = loadPlugin(plugin)
237         relevants_files[plugin] = module.createProject(project_info)
238     project_info.setInfo("RELEVANT_FILES", relevants_files)
239
240 def loadPlugin(plugin):
241     """
242     Returns the given plugin module.
243     """
244     return getattr(__import__("plugins", {}, {}, [plugin]), plugin)
245     
246 def mkGenerator(project_info, makefile):
247     """
248     Generates the mk file for the current project.
249     """
250     mk_data = {}
251     mk_data["$pname"] = os.path.basename(project_info.info("PROJECT_PATH"))
252     mk_data["$cpuclockfreq"] = project_info.info("SELECTED_FREQ")
253     cpu_mk_parameters = []
254     for key, value in project_info.info("CPU_INFOS").items():
255         if key.startswith(const.MK_PARAM_ID):
256             cpu_mk_parameters.append("%s = %s" %(key.replace("MK", mk_data["$pname"]), value))
257     mk_data["$cpuparameters"] = "\n".join(cpu_mk_parameters)
258     mk_data["$csrc"], mk_data["$pcsrc"], mk_data["$cppasrc"], mk_data["$cxxsrc"], mk_data["$asrc"], mk_data["$constants"] = csrcGenerator(project_info)
259     mk_data["$prefix"] = replaceSeparators(project_info.info("TOOLCHAIN")["path"].split("gcc")[0])
260     mk_data["$suffix"] = replaceSeparators(project_info.info("TOOLCHAIN")["path"].split("gcc")[1])
261     mk_data["$main"] = os.path.basename(project_info.info("PROJECT_PATH")) + "/main.c"
262     for key in mk_data:
263         while makefile.find(key) != -1:
264             makefile = makefile.replace(key, mk_data[key])
265     return makefile
266
267 def makefileGenerator(project_info, makefile):
268     """
269     Generate the Makefile for the current project.
270     """
271     # TODO write a general function that works for both the mk file and the Makefile
272     while makefile.find("$pname") != -1:
273         makefile = makefile.replace("$pname", os.path.basename(project_info.info("PROJECT_PATH")))
274     return makefile
275
276 def csrcGenerator(project_info):
277     modules = project_info.info("MODULES")
278     files = project_info.info("FILES")
279     if "harvard" in project_info.info("CPU_INFOS")["CPU_TAGS"]:
280         harvard = True
281     else:
282         harvard = False
283     # file to be included in CSRC variable
284     csrc = []
285     # file to be included in PCSRC variable
286     pcsrc = []
287     # files to be included in CPPASRC variable
288     cppasrc = []
289     # files to be included in CXXSRC variable
290     cxxsrc = []
291     # files to be included in ASRC variable
292     asrc = []
293     # constants to be included at the beginning of the makefile
294     constants = {}
295     for module, information in modules.items():
296         module_files = set([])
297         dependency_files = set([])
298         # assembly sources
299         asm_files = set([])
300         hwdir = os.path.basename(project_info.info("PROJECT_PATH")) + "/hw" 
301         if information["enabled"]:
302             if "constants" in information:
303                 constants.update(information["constants"])
304             cfiles, sfiles = findModuleFiles(module, project_info)
305             module_files |= set(cfiles)
306             asm_files |= set(sfiles)
307             for file in information["hw"]:
308                 if file.endswith(".c"):
309                     module_files |= set([hwdir + "/" + os.path.basename(file)])
310             for file_dependency in information["depends"]:
311                 if file_dependency in files:
312                     dependencyCFiles, dependencySFiles = findModuleFiles(file_dependency, project_info)
313                     dependency_files |= set(dependencyCFiles)
314                     asm_files |= set(dependencySFiles)
315             for file in module_files:
316                 if not harvard or information.get("harvard", "both") == "both":
317                     csrc.append(file)
318                 if harvard and "harvard" in information:
319                     pcsrc.append(file)
320             for file in dependency_files:
321                 csrc.append(file)
322             for file in project_info.info("CPU_INFOS")["C_SRC"]:
323                 csrc.append(file)
324             for file in project_info.info("CPU_INFOS")["PC_SRC"]:
325                 pcsrc.append(file)
326             for file in asm_files:
327                 cppasrc.append(file)
328     for file in project_info.info("CPU_INFOS")["CPPA_SRC"]:
329         cppasrc.append(file)
330     for file in project_info.info("CPU_INFOS")["CXX_SRC"]:
331         cxxsrc.append(file)
332     for file in project_info.info("CPU_INFOS")["ASRC"]:
333         asrc.append(file)
334     csrc = set(csrc)
335     csrc = " \\\n\t".join(csrc) + " \\"
336     pcsrc = set(pcsrc)
337     pcsrc = " \\\n\t".join(pcsrc) + " \\"
338     cppasrc = set(cppasrc)
339     cppasrc = " \\\n\t".join(cppasrc) + " \\"
340     cxxsrc = set(cxxsrc)
341     cxxsrc = " \\\n\t".join(cxxsrc) + " \\"
342     asrc = set(asrc)
343     asrc = " \\\n\t".join(asrc) + " \\"
344     constants = "\n".join([os.path.basename(project_info.info("PROJECT_PATH")) + "_" + key + " = " + unicode(value) for key, value in constants.items()])
345     return csrc, pcsrc, cppasrc, cxxsrc, asrc, constants
346
347 def findModuleFiles(module, project_info):
348     # Find the files related to the selected module
349     cfiles = []
350     sfiles = []
351     # .c files related to the module and the cpu architecture
352     for filename, path in findDefinitions(module + ".c", project_info) + \
353             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".c", project_info):
354         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
355         path = replaceSeparators(path)
356         cfiles.append(path + "/" + filename)
357     # .s files related to the module and the cpu architecture
358     for filename, path in findDefinitions(module + ".s", project_info) + \
359             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".s", project_info) + \
360             findDefinitions(module + ".S", project_info) + \
361             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".S", project_info):
362         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
363         path = replaceSeparators(path)
364         sfiles.append(path + "/" + filename)
365     # .c and .s files related to the module and the cpu tags
366     for tag in project_info.info("CPU_INFOS")["CPU_TAGS"]:
367         for filename, path in findDefinitions(module + "_" + tag + ".c", project_info):
368             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
369             if os.sep != "/":
370                 path = replaceSeparators(path)
371             cfiles.append(path + "/" + filename)
372         for filename, path in findDefinitions(module + "_" + tag + ".s", project_info) + \
373                 findDefinitions(module + "_" + tag + ".S", project_info):
374             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
375             path = replaceSeparators(path)
376             sfiles.append(path + "/" + filename)
377     return cfiles, sfiles
378
379 def replaceSeparators(path):
380     """
381     Replace the separators in the given path with unix standard separator.
382     """
383     if os.sep != "/":
384         while path.find(os.sep) != -1:
385             path = path.replace(os.sep, "/")
386     return path
387
388 def getSystemPath():
389     path = os.environ["PATH"]
390     if os.name == "nt":
391         path = path.split(";")
392     else:
393         path = path.split(":")
394     return path
395
396 def findToolchains(path_list):
397     toolchains = []
398     for element in path_list:
399         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
400             toolchains.append(toolchain)
401     return list(set(toolchains))
402
403 def getToolchainInfo(output):
404     info = {}
405     expr = re.compile("Target: .*")
406     target = expr.findall(output)
407     if len(target) == 1:
408         info["target"] = target[0].split("Target: ")[1]
409     expr = re.compile("gcc version [0-9,.]*")
410     version = expr.findall(output)
411     if len(version) == 1:
412         info["version"] = version[0].split("gcc version ")[1]
413     expr = re.compile("gcc version [0-9,.]* \(.*\)")
414     build = expr.findall(output)
415     if len(build) == 1:
416         build = build[0].split("gcc version ")[1]
417         build = build[build.find("(") + 1 : build.find(")")]
418         info["build"] = build
419     expr = re.compile("Configured with: .*")
420     configured = expr.findall(output)
421     if len(configured) == 1:
422         info["configured"] = configured[0].split("Configured with: ")[1]
423     expr = re.compile("Thread model: .*")
424     thread = expr.findall(output)
425     if len(thread) == 1:
426         info["thread"] = thread[0].split("Thread model: ")[1]
427     return info
428
429 def getToolchainName(toolchain_info):
430     name = "GCC " + toolchain_info["version"] + " - " + toolchain_info["target"].strip()
431     return name
432
433 def loadSourceTree(project):
434     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
435     project.setInfo("FILE_LIST", fileList)
436
437 def findDefinitions(ftype, project):
438     L = project.info("FILE_LIST")
439     definitions = []
440     for element in L:
441         for filename in element[2]:
442             if fnmatch.fnmatch(filename, ftype):
443                 definitions.append((filename, element[0]))
444     return definitions
445
446 def loadCpuInfos(project):
447     cpuInfos = []
448     for definition in findDefinitions(const.CPU_DEFINITION, project):
449         cpuInfos.append(getInfos(definition))
450     return cpuInfos
451
452 def getTagSet(cpu_info):
453     tag_set = set([])
454     for cpu in cpu_info:
455         tag_set |= set([cpu["CPU_NAME"]])
456         tag_set |= set(cpu["CPU_TAGS"])
457         tag_set |= set([cpu["TOOLCHAIN"]])
458     return tag_set
459         
460
461 def getInfos(definition):
462     D = {}
463     D.update(const.CPU_DEF)
464     def include(filename, dict = D, directory=definition[1]):
465         execfile(directory + "/" + filename, {}, D)
466     D["include"] = include
467     include(definition[0], D)
468     D["CPU_NAME"] = definition[0].split(".")[0]
469     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
470     del D["include"]
471     return D
472
473 def getCommentList(string):
474     comment_list = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
475     comment_list = [re.findall(r"^\s*\* *(.*?)$", comment, re.MULTILINE) for comment in comment_list]
476     return comment_list
477
478 def loadModuleDefinition(first_comment):
479     to_be_parsed = False
480     module_definition = {}
481     for num, line in enumerate(first_comment):
482         index = line.find("$WIZ$")
483         if index != -1:
484             to_be_parsed = True
485             try:
486                 exec line[index + len("$WIZ$ "):] in {}, module_definition
487             except:
488                 raise ParseError(num, line[index:])
489         elif line.find("\\brief") != -1:
490             module_definition["module_description"] = line[line.find("\\brief") + len("\\brief "):]
491     module_dict = {}
492     if "module_name" in module_definition:
493         module_name = module_definition[const.MODULE_DEFINITION["module_name"]]
494         del module_definition[const.MODULE_DEFINITION["module_name"]]
495         module_dict[module_name] = {}
496         if const.MODULE_DEFINITION["module_depends"] in module_definition:
497             depends = module_definition[const.MODULE_DEFINITION["module_depends"]]
498             del module_definition[const.MODULE_DEFINITION["module_depends"]]
499             if type(depends) == str:
500                 depends = (depends,)
501             module_dict[module_name]["depends"] = depends
502         else:
503             module_dict[module_name]["depends"] = ()
504         if const.MODULE_DEFINITION["module_configuration"] in module_definition:
505             module_dict[module_name]["configuration"] = module_definition[const.MODULE_DEFINITION["module_configuration"]]
506             del module_definition[const.MODULE_DEFINITION["module_configuration"]]
507         else:
508             module_dict[module_name]["configuration"] = ""
509         if "module_description" in module_definition:
510             module_dict[module_name]["description"] = module_definition["module_description"]
511             del module_definition["module_description"]
512         if const.MODULE_DEFINITION["module_harvard"] in module_definition:
513             harvard = module_definition[const.MODULE_DEFINITION["module_harvard"]]
514             module_dict[module_name]["harvard"] = harvard
515             del module_definition[const.MODULE_DEFINITION["module_harvard"]]
516         if const.MODULE_DEFINITION["module_hw"] in module_definition:
517             hw = module_definition[const.MODULE_DEFINITION["module_hw"]]
518             del module_definition[const.MODULE_DEFINITION["module_hw"]]
519             if type(hw) == str:
520                 hw = (hw, )
521             module_dict[module_name]["hw"] = hw
522         else:
523             module_dict[module_name]["hw"] = ()
524         if const.MODULE_DEFINITION["module_supports"] in module_definition:
525             supports = module_definition[const.MODULE_DEFINITION["module_supports"]]
526             del module_definition[const.MODULE_DEFINITION["module_supports"]]
527             module_dict[module_name]["supports"] = supports
528         module_dict[module_name]["constants"] = module_definition
529         module_dict[module_name]["enabled"] = False
530     return to_be_parsed, module_dict
531
532 def isSupported(project, module=None, property_id=None):
533     if not module and property_id:
534         item = project.info("CONFIGURATIONS")[property_id[0]][property_id[1]]["informations"]
535     else:
536         item = project.info("MODULES")[module]
537     tag_dict = project.info("ALL_CPU_TAGS")
538     if "supports" in item:
539         support_string = item["supports"]
540         supported = {}
541         try:
542             exec "supported = " + support_string in tag_dict, supported
543         except:
544             raise SupportedException(support_string)
545         return supported["supported"]
546     else:
547         return True
548
549 def loadDefineLists(comment_list):
550     define_list = {}
551     for comment in comment_list:
552         for num, line in enumerate(comment):
553             index = line.find("$WIZ$")
554             if index != -1:
555                 try:
556                     exec line[index + len("$WIZ$ "):] in {}, define_list
557                 except:
558                     raise ParseError(num, line[index:])
559     for key, value in define_list.items():
560         if type(value) == str:
561             define_list[key] = (value,)
562     return define_list
563
564 def getDescriptionInformations(comment):
565     """
566     Take the doxygen comment and strip the wizard informations, returning the tuple
567     (comment, wizard_information)
568     """
569     brief = ""
570     description = ""
571     information = {}
572     for num, line in enumerate(comment):
573         index = line.find("$WIZ$")
574         if index != -1:
575             if len(brief) == 0:
576                 brief += line[:index].strip()
577             else:
578                 description += " " + line[:index]
579             try:
580                 exec line[index + len("$WIZ$ "):] in {}, information
581             except:
582                 raise ParseError(num, line[index:])
583         else:
584             if len(brief) == 0:
585                 brief += line.strip()
586             else:
587                 description += " " + line
588                 description = description.strip()
589     return brief.strip(), description.strip(), information
590
591 def getDefinitionBlocks(text):
592     """
593     Take a text and return a list of tuple (description, name-value).
594     """
595     block = []
596     block_tmp = re.finditer(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
597     for match in block_tmp:
598         # Only the first element is needed
599         comment = match.group(1)
600         define = match.group(2)
601         start = match.start()
602         block.append(([re.findall(r"^\s*\* *(.*?)$", line, re.MULTILINE)[0] for line in comment.splitlines()], define, start))
603     for match in re.finditer(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
604         comment = match.group(1)
605         define = match.group(2)
606         start = match.start()
607         block.append(([comment], define, start))
608     for match in re.finditer(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
609         comment = match.group(2)
610         define = match.group(1)
611         start = match.start()
612         block.append(([comment], define, start))
613     return block
614
615 def loadModuleData(project, edit=False):
616     module_info_dict = {}
617     list_info_dict = {}
618     configuration_info_dict = {}
619     file_dict = {}
620     for filename, path in findDefinitions("*.h", project) + findDefinitions("*.c", project) + findDefinitions("*.s", project) + findDefinitions("*.S", project):
621         comment_list = getCommentList(open(path + "/" + filename, "r").read())
622         if len(comment_list) > 0:
623             module_info = {}
624             configuration_info = {}
625             try:
626                 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
627             except ParseError, err:
628                 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
629             for module, information in module_dict.items():
630                 if "depends" not in information:
631                     information["depends"] = ()
632                 information["depends"] += (filename.split(".")[0],)
633                 information["category"] = os.path.basename(path)
634                 if "configuration" in information and len(information["configuration"]):
635                     configuration = module_dict[module]["configuration"]
636                     try:
637                         configuration_info[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
638                     except ParseError, err:
639                         raise DefineException.ConfigurationDefineException(project.info("SOURCES_PATH") + "/" + configuration, err.line_number, err.line)
640                     if edit:
641                         try:
642                             path = os.path.basename(project.info("PROJECT_PATH"))
643                             directory = project.info("PROJECT_PATH")
644                             user_configuration = loadConfigurationInfos(directory + "/" + configuration.replace("bertos", path))
645                             configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], user_configuration)
646                         except ParseError, err:
647                             raise DefineException.ConfigurationDefineException(directory + "/" + configuration.replace("bertos", path))
648             module_info_dict.update(module_dict)
649             configuration_info_dict.update(configuration_info)
650             if to_be_parsed:
651                 try:
652                     list_dict = loadDefineLists(comment_list[1:])
653                     list_info_dict.update(list_dict)
654                 except ParseError, err:
655                     raise DefineException.EnumDefineException(path, err.line_number, err.line)
656     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
657         comment_list = getCommentList(open(path + "/" + filename, "r").read())
658         list_info_dict.update(loadDefineLists(comment_list))
659     for tag in project.info("CPU_INFOS")["CPU_TAGS"]:
660         for filename, path in findDefinitions("*_" + tag + ".h", project):
661             comment_list = getCommentList(open(path + "/" + filename, "r").read())
662             list_info_dict.update(loadDefineLists(comment_list))
663     project.setInfo("MODULES", module_info_dict)
664     project.setInfo("LISTS", list_info_dict)
665     project.setInfo("CONFIGURATIONS", configuration_info_dict)
666     project.setInfo("FILES", file_dict)
667
668 def formatParamNameValue(text):
669     """
670     Take the given string and return a tuple with the name of the parameter in the first position
671     and the value in the second.
672     """
673     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
674     return block[0]
675
676 def loadConfigurationInfos(path):
677     """
678     Return the module configurations found in the given file as a dict with the
679     parameter name as key and a dict containig the fields above as value:
680         "value": the value of the parameter
681         "description": the description of the parameter
682         "informations": a dict containig optional informations:
683             "type": "int" | "boolean" | "enum"
684             "min": the minimum value for integer parameters
685             "max": the maximum value for integer parameters
686             "long": boolean indicating if the num is a long
687             "unsigned": boolean indicating if the num is an unsigned
688             "value_list": the name of the enum for enum parameters
689             "conditional_deps": the list of conditional dependencies for boolean parameters
690     """
691     configuration_infos = {}
692     configuration_infos["paramlist"] = []
693     for comment, define, start in getDefinitionBlocks(open(path, "r").read()):
694         name, value = formatParamNameValue(define)
695         brief, description, informations = getDescriptionInformations(comment)
696         configuration_infos["paramlist"].append((start, name))
697         configuration_infos[name] = {}
698         configuration_infos[name]["value"] = value
699         configuration_infos[name]["informations"] = informations
700         if not "type" in configuration_infos[name]["informations"]:
701             configuration_infos[name]["informations"]["type"] = findParameterType(configuration_infos[name])
702         if ("type" in configuration_infos[name]["informations"] and
703                 configuration_infos[name]["informations"]["type"] == "int" and
704                 configuration_infos[name]["value"].find("L") != -1):
705             configuration_infos[name]["informations"]["long"] = True
706             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("L", "")
707         if ("type" in configuration_infos[name]["informations"] and
708                 configuration_infos[name]["informations"]["type"] == "int" and
709                 configuration_infos[name]["value"].find("U") != -1):
710             configuration_infos[name]["informations"]["unsigned"] = True
711             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("U", "")
712         if "conditional_deps" in configuration_infos[name]["informations"]:
713             if (type(configuration_infos[name]["informations"]["conditional_deps"]) == str or
714                     type(configuration_infos[name]["informations"]["conditional_deps"]) == unicode):
715                 configuration_infos[name]["informations"]["conditional_deps"] = (configuration_infos[name]["informations"]["conditional_deps"], )
716             elif type(configurations_infos[name]["informations"]["conditional_deps"]) == tuple:
717                 pass
718             else:
719                 configurations_infos[name]["informations"]["conditional_deps"] = ()
720         configuration_infos[name]["description"] = description
721         configuration_infos[name]["brief"] = brief
722     return configuration_infos
723
724 def updateConfigurationValues(def_conf, user_conf):
725     for param in def_conf["paramlist"]:
726         if param[1] in user_conf and "value" in user_conf[param[1]]:
727             def_conf[param[1]]["value"] = user_conf[param[1]]["value"]
728     return def_conf
729
730 def findParameterType(parameter):
731     if "value_list" in parameter["informations"]:
732         return "enum"
733     if "min" in parameter["informations"] or "max" in parameter["informations"] or re.match(r"^\d+U?L?$", parameter["value"]) != None:
734         return "int"
735
736 def sub(string, parameter, value):
737     """
738     Substitute the given value at the given parameter define in the given string
739     """
740     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
741
742 def isInt(informations):
743     """
744     Return True if the value is a simple int.
745     """
746     if ("long" not in informatios or not informations["long"]) and ("unsigned" not in informations or informations["unsigned"]):
747         return True
748     else:
749         return False
750
751 def isLong(informations):
752     """
753     Return True if the value is a long.
754     """
755     if "long" in informations and informations["long"] and "unsigned" not in informations:
756         return True
757     else:
758         return False
759
760 def isUnsigned(informations):
761     """
762     Return True if the value is an unsigned.
763     """
764     if "unsigned" in informations and informations["unsigned"] and "long" not in informations:
765         return True
766     else:
767         return False
768
769 def isUnsignedLong(informations):
770     """
771     Return True if the value is an unsigned long.
772     """
773     if "unsigned" in informations and "long" in informations and informations["unsigned"] and informations["long"]:
774         return True
775     else:
776         return False
777
778 class ParseError(Exception):
779     def __init__(self, line_number, line):
780         Exception.__init__(self)
781         self.line_number = line_number
782         self.line = line
783
784 class SupportedException(Exception):
785     def __init__(self, support_string):
786         Exception.__init__(self)
787         self.support_string = support_string