Merge from trunk.
[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(os.path.join(const.DATA_DIR, "mktemplates/Makefile"), 'r').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(os.path.join(const.DATA_DIR, "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(os.path.join(const.DATA_DIR, "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(os.path.join(const.DATA_DIR, "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"] + tuple(files.keys()):
311                     dependencyCFiles, dependencySFiles = findModuleFiles(file_dependency, project_info)
312                     dependency_files |= set(dependencyCFiles)
313                     asm_files |= set(dependencySFiles)
314             for file in module_files:
315                 if not harvard or information.get("harvard", "both") == "both":
316                     csrc.append(file)
317                 if harvard and "harvard" in information:
318                     pcsrc.append(file)
319             for file in dependency_files:
320                 csrc.append(file)
321             for file in project_info.info("CPU_INFOS")["C_SRC"]:
322                 csrc.append(file)
323             for file in project_info.info("CPU_INFOS")["PC_SRC"]:
324                 pcsrc.append(file)
325             for file in asm_files:
326                 cppasrc.append(file)
327     for file in project_info.info("CPU_INFOS")["CPPA_SRC"]:
328         cppasrc.append(file)
329     for file in project_info.info("CPU_INFOS")["CXX_SRC"]:
330         cxxsrc.append(file)
331     for file in project_info.info("CPU_INFOS")["ASRC"]:
332         asrc.append(file)
333     csrc = set(csrc)
334     csrc = " \\\n\t".join(csrc) + " \\"
335     pcsrc = set(pcsrc)
336     pcsrc = " \\\n\t".join(pcsrc) + " \\"
337     cppasrc = set(cppasrc)
338     cppasrc = " \\\n\t".join(cppasrc) + " \\"
339     cxxsrc = set(cxxsrc)
340     cxxsrc = " \\\n\t".join(cxxsrc) + " \\"
341     asrc = set(asrc)
342     asrc = " \\\n\t".join(asrc) + " \\"
343     constants = "\n".join([os.path.basename(project_info.info("PROJECT_PATH")) + "_" + key + " = " + unicode(value) for key, value in constants.items()])
344     return csrc, pcsrc, cppasrc, cxxsrc, asrc, constants
345
346 def findModuleFiles(module, project_info):
347     # Find the files related to the selected module
348     cfiles = []
349     sfiles = []
350     # .c files related to the module and the cpu architecture
351     for filename, path in findDefinitions(module + ".c", project_info) + \
352             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".c", project_info):
353         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
354         path = replaceSeparators(path)
355         cfiles.append(path + "/" + filename)
356     # .s files related to the module and the cpu architecture
357     for filename, path in findDefinitions(module + ".s", project_info) + \
358             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".s", project_info) + \
359             findDefinitions(module + ".S", project_info) + \
360             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".S", project_info):
361         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
362         path = replaceSeparators(path)
363         sfiles.append(path + "/" + filename)
364     # .c and .s files related to the module and the cpu tags
365     for tag in project_info.info("CPU_INFOS")["CPU_TAGS"]:
366         for filename, path in findDefinitions(module + "_" + tag + ".c", project_info):
367             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
368             if os.sep != "/":
369                 path = replaceSeparators(path)
370             cfiles.append(path + "/" + filename)
371         for filename, path in findDefinitions(module + "_" + tag + ".s", project_info) + \
372                 findDefinitions(module + "_" + tag + ".S", project_info):
373             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
374             path = replaceSeparators(path)
375             sfiles.append(path + "/" + filename)
376     return cfiles, sfiles
377
378 def replaceSeparators(path):
379     """
380     Replace the separators in the given path with unix standard separator.
381     """
382     if os.sep != "/":
383         while path.find(os.sep) != -1:
384             path = path.replace(os.sep, "/")
385     return path
386
387 def getSystemPath():
388     path = os.environ["PATH"]
389     if os.name == "nt":
390         path = path.split(";")
391     else:
392         path = path.split(":")
393     return path
394
395 def findToolchains(path_list):
396     toolchains = []
397     for element in path_list:
398         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
399             toolchains.append(toolchain)
400     return list(set(toolchains))
401
402 def getToolchainInfo(output):
403     info = {}
404     expr = re.compile("Target: .*")
405     target = expr.findall(output)
406     if len(target) == 1:
407         info["target"] = target[0].split("Target: ")[1]
408     expr = re.compile("gcc version [0-9,.]*")
409     version = expr.findall(output)
410     if len(version) == 1:
411         info["version"] = version[0].split("gcc version ")[1]
412     expr = re.compile("gcc version [0-9,.]* \(.*\)")
413     build = expr.findall(output)
414     if len(build) == 1:
415         build = build[0].split("gcc version ")[1]
416         build = build[build.find("(") + 1 : build.find(")")]
417         info["build"] = build
418     expr = re.compile("Configured with: .*")
419     configured = expr.findall(output)
420     if len(configured) == 1:
421         info["configured"] = configured[0].split("Configured with: ")[1]
422     expr = re.compile("Thread model: .*")
423     thread = expr.findall(output)
424     if len(thread) == 1:
425         info["thread"] = thread[0].split("Thread model: ")[1]
426     return info
427
428 def getToolchainName(toolchain_info):
429     name = "GCC " + toolchain_info["version"] + " - " + toolchain_info["target"].strip()
430     return name
431
432 def loadSourceTree(project):
433     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
434     project.setInfo("FILE_LIST", fileList)
435
436 def findDefinitions(ftype, project):
437     L = project.info("FILE_LIST")
438     definitions = []
439     for element in L:
440         for filename in element[2]:
441             if fnmatch.fnmatch(filename, ftype):
442                 definitions.append((filename, element[0]))
443     return definitions
444
445 def loadCpuInfos(project):
446     cpuInfos = []
447     for definition in findDefinitions(const.CPU_DEFINITION, project):
448         cpuInfos.append(getInfos(definition))
449     return cpuInfos
450
451 def getTagSet(cpu_info):
452     tag_set = set([])
453     for cpu in cpu_info:
454         tag_set |= set([cpu["CPU_NAME"]])
455         tag_set |= set(cpu["CPU_TAGS"])
456         tag_set |= set([cpu["TOOLCHAIN"]])
457     return tag_set
458         
459
460 def getInfos(definition):
461     D = {}
462     D.update(const.CPU_DEF)
463     def include(filename, dict = D, directory=definition[1]):
464         execfile(directory + "/" + filename, {}, D)
465     D["include"] = include
466     include(definition[0], D)
467     D["CPU_NAME"] = definition[0].split(".")[0]
468     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
469     del D["include"]
470     return D
471
472 def getCommentList(string):
473     comment_list = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
474     comment_list = [re.findall(r"^\s*\* *(.*?)$", comment, re.MULTILINE) for comment in comment_list]
475     return comment_list
476
477 def loadModuleDefinition(first_comment):
478     to_be_parsed = False
479     module_definition = {}
480     for num, line in enumerate(first_comment):
481         index = line.find("$WIZ$")
482         if index != -1:
483             to_be_parsed = True
484             try:
485                 exec line[index + len("$WIZ$ "):] in {}, module_definition
486             except:
487                 raise ParseError(num, line[index:])
488         elif line.find("\\brief") != -1:
489             module_definition["module_description"] = line[line.find("\\brief") + len("\\brief "):]
490     module_dict = {}
491     if "module_name" in module_definition:
492         module_name = module_definition[const.MODULE_DEFINITION["module_name"]]
493         del module_definition[const.MODULE_DEFINITION["module_name"]]
494         module_dict[module_name] = {}
495         if const.MODULE_DEFINITION["module_depends"] in module_definition:
496             depends = module_definition[const.MODULE_DEFINITION["module_depends"]]
497             del module_definition[const.MODULE_DEFINITION["module_depends"]]
498             if type(depends) == str:
499                 depends = (depends,)
500             module_dict[module_name]["depends"] = depends
501         else:
502             module_dict[module_name]["depends"] = ()
503         if const.MODULE_DEFINITION["module_configuration"] in module_definition:
504             module_dict[module_name]["configuration"] = module_definition[const.MODULE_DEFINITION["module_configuration"]]
505             del module_definition[const.MODULE_DEFINITION["module_configuration"]]
506         else:
507             module_dict[module_name]["configuration"] = ""
508         if "module_description" in module_definition:
509             module_dict[module_name]["description"] = module_definition["module_description"]
510             del module_definition["module_description"]
511         if const.MODULE_DEFINITION["module_harvard"] in module_definition:
512             harvard = module_definition[const.MODULE_DEFINITION["module_harvard"]]
513             module_dict[module_name]["harvard"] = harvard
514             del module_definition[const.MODULE_DEFINITION["module_harvard"]]
515         if const.MODULE_DEFINITION["module_hw"] in module_definition:
516             hw = module_definition[const.MODULE_DEFINITION["module_hw"]]
517             del module_definition[const.MODULE_DEFINITION["module_hw"]]
518             if type(hw) == str:
519                 hw = (hw, )
520             module_dict[module_name]["hw"] = hw
521         else:
522             module_dict[module_name]["hw"] = ()
523         if const.MODULE_DEFINITION["module_supports"] in module_definition:
524             supports = module_definition[const.MODULE_DEFINITION["module_supports"]]
525             del module_definition[const.MODULE_DEFINITION["module_supports"]]
526             module_dict[module_name]["supports"] = supports
527         module_dict[module_name]["constants"] = module_definition
528         module_dict[module_name]["enabled"] = False
529     return to_be_parsed, module_dict
530
531 def isSupported(project, module=None, property_id=None):
532     if not module and property_id:
533         item = project.info("CONFIGURATIONS")[property_id[0]][property_id[1]]["informations"]
534     else:
535         item = project.info("MODULES")[module]
536     tag_dict = project.info("ALL_CPU_TAGS")
537     if "supports" in item:
538         support_string = item["supports"]
539         supported = {}
540         try:
541             exec "supported = " + support_string in tag_dict, supported
542         except:
543             raise SupportedException(support_string)
544         return supported["supported"]
545     else:
546         return True
547
548 def loadDefineLists(comment_list):
549     define_list = {}
550     for comment in comment_list:
551         for num, line in enumerate(comment):
552             index = line.find("$WIZ$")
553             if index != -1:
554                 try:
555                     exec line[index + len("$WIZ$ "):] in {}, define_list
556                 except:
557                     raise ParseError(num, line[index:])
558     for key, value in define_list.items():
559         if type(value) == str:
560             define_list[key] = (value,)
561     return define_list
562
563 def getDescriptionInformations(comment):
564     """
565     Take the doxygen comment and strip the wizard informations, returning the tuple
566     (comment, wizard_information)
567     """
568     brief = ""
569     description = ""
570     information = {}
571     for num, line in enumerate(comment):
572         index = line.find("$WIZ$")
573         if index != -1:
574             if len(brief) == 0:
575                 brief += line[:index].strip()
576             else:
577                 description += " " + line[:index]
578             try:
579                 exec line[index + len("$WIZ$ "):] in {}, information
580             except:
581                 raise ParseError(num, line[index:])
582         else:
583             if len(brief) == 0:
584                 brief += line.strip()
585             else:
586                 description += " " + line
587                 description = description.strip()
588     return brief.strip(), description.strip(), information
589
590 def getDefinitionBlocks(text):
591     """
592     Take a text and return a list of tuple (description, name-value).
593     """
594     block = []
595     block_tmp = re.finditer(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
596     for match in block_tmp:
597         # Only the first element is needed
598         comment = match.group(1)
599         define = match.group(2)
600         start = match.start()
601         block.append(([re.findall(r"^\s*\* *(.*?)$", line, re.MULTILINE)[0] for line in comment.splitlines()], define, start))
602     for match in re.finditer(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
603         comment = match.group(1)
604         define = match.group(2)
605         start = match.start()
606         block.append(([comment], define, start))
607     for match in re.finditer(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
608         comment = match.group(2)
609         define = match.group(1)
610         start = match.start()
611         block.append(([comment], define, start))
612     return block
613
614 def loadModuleData(project, edit=False):
615     module_info_dict = {}
616     list_info_dict = {}
617     configuration_info_dict = {}
618     file_dict = {}
619     for filename, path in findDefinitions("*.h", project) + findDefinitions("*.c", project) + findDefinitions("*.s", project) + findDefinitions("*.S", project):
620         comment_list = getCommentList(open(path + "/" + filename, "r").read())
621         if len(comment_list) > 0:
622             module_info = {}
623             configuration_info = {}
624             try:
625                 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
626             except ParseError, err:
627                 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
628             for module, information in module_dict.items():
629                 if "depends" not in information:
630                     information["depends"] = ()
631                 information["depends"] += (filename.split(".")[0],)
632                 information["category"] = os.path.basename(path)
633                 if "configuration" in information and len(information["configuration"]):
634                     configuration = module_dict[module]["configuration"]
635                     try:
636                         configuration_info[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
637                     except ParseError, err:
638                         raise DefineException.ConfigurationDefineException(project.info("SOURCES_PATH") + "/" + configuration, err.line_number, err.line)
639                     if edit:
640                         try:
641                             path = os.path.basename(project.info("PROJECT_PATH"))
642                             directory = project.info("PROJECT_PATH")
643                             user_configuration = loadConfigurationInfos(directory + "/" + configuration.replace("bertos", path))
644                             configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], user_configuration)
645                         except ParseError, err:
646                             raise DefineException.ConfigurationDefineException(directory + "/" + configuration.replace("bertos", path))
647             module_info_dict.update(module_dict)
648             configuration_info_dict.update(configuration_info)
649             if to_be_parsed:
650                 try:
651                     list_dict = loadDefineLists(comment_list[1:])
652                     list_info_dict.update(list_dict)
653                 except ParseError, err:
654                     raise DefineException.EnumDefineException(path, err.line_number, err.line)
655     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
656         comment_list = getCommentList(open(path + "/" + filename, "r").read())
657         list_info_dict.update(loadDefineLists(comment_list))
658     for tag in project.info("CPU_INFOS")["CPU_TAGS"]:
659         for filename, path in findDefinitions("*_" + tag + ".h", project):
660             comment_list = getCommentList(open(path + "/" + filename, "r").read())
661             list_info_dict.update(loadDefineLists(comment_list))
662     project.setInfo("MODULES", module_info_dict)
663     project.setInfo("LISTS", list_info_dict)
664     project.setInfo("CONFIGURATIONS", configuration_info_dict)
665     project.setInfo("FILES", file_dict)
666
667 def formatParamNameValue(text):
668     """
669     Take the given string and return a tuple with the name of the parameter in the first position
670     and the value in the second.
671     """
672     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
673     return block[0]
674
675 def loadConfigurationInfos(path):
676     """
677     Return the module configurations found in the given file as a dict with the
678     parameter name as key and a dict containig the fields above as value:
679         "value": the value of the parameter
680         "description": the description of the parameter
681         "informations": a dict containig optional informations:
682             "type": "int" | "boolean" | "enum"
683             "min": the minimum value for integer parameters
684             "max": the maximum value for integer parameters
685             "long": boolean indicating if the num is a long
686             "unsigned": boolean indicating if the num is an unsigned
687             "value_list": the name of the enum for enum parameters
688             "conditional_deps": the list of conditional dependencies for boolean parameters
689     """
690     configuration_infos = {}
691     configuration_infos["paramlist"] = []
692     for comment, define, start in getDefinitionBlocks(open(path, "r").read()):
693         name, value = formatParamNameValue(define)
694         brief, description, informations = getDescriptionInformations(comment)
695         configuration_infos["paramlist"].append((start, name))
696         configuration_infos[name] = {}
697         configuration_infos[name]["value"] = value
698         configuration_infos[name]["informations"] = informations
699         if not "type" in configuration_infos[name]["informations"]:
700             configuration_infos[name]["informations"]["type"] = findParameterType(configuration_infos[name])
701         if ("type" in configuration_infos[name]["informations"] and
702                 configuration_infos[name]["informations"]["type"] == "int" and
703                 configuration_infos[name]["value"].find("L") != -1):
704             configuration_infos[name]["informations"]["long"] = True
705             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("L", "")
706         if ("type" in configuration_infos[name]["informations"] and
707                 configuration_infos[name]["informations"]["type"] == "int" and
708                 configuration_infos[name]["value"].find("U") != -1):
709             configuration_infos[name]["informations"]["unsigned"] = True
710             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("U", "")
711         if "conditional_deps" in configuration_infos[name]["informations"]:
712             if (type(configuration_infos[name]["informations"]["conditional_deps"]) == str or
713                     type(configuration_infos[name]["informations"]["conditional_deps"]) == unicode):
714                 configuration_infos[name]["informations"]["conditional_deps"] = (configuration_infos[name]["informations"]["conditional_deps"], )
715             elif type(configuration_infos[name]["informations"]["conditional_deps"]) == tuple:
716                 pass
717             else:
718                 configuration_infos[name]["informations"]["conditional_deps"] = ()
719         configuration_infos[name]["description"] = description
720         configuration_infos[name]["brief"] = brief
721     return configuration_infos
722
723 def updateConfigurationValues(def_conf, user_conf):
724     for param in def_conf["paramlist"]:
725         if param[1] in user_conf and "value" in user_conf[param[1]]:
726             def_conf[param[1]]["value"] = user_conf[param[1]]["value"]
727     return def_conf
728
729 def findParameterType(parameter):
730     if "value_list" in parameter["informations"]:
731         return "enum"
732     if "min" in parameter["informations"] or "max" in parameter["informations"] or re.match(r"^\d+U?L?$", parameter["value"]) != None:
733         return "int"
734
735 def sub(string, parameter, value):
736     """
737     Substitute the given value at the given parameter define in the given string
738     """
739     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
740
741 def isInt(informations):
742     """
743     Return True if the value is a simple int.
744     """
745     if ("long" not in informatios or not informations["long"]) and ("unsigned" not in informations or informations["unsigned"]):
746         return True
747     else:
748         return False
749
750 def isLong(informations):
751     """
752     Return True if the value is a long.
753     """
754     if "long" in informations and informations["long"] and "unsigned" not in informations:
755         return True
756     else:
757         return False
758
759 def isUnsigned(informations):
760     """
761     Return True if the value is an unsigned.
762     """
763     if "unsigned" in informations and informations["unsigned"] and "long" not in informations:
764         return True
765     else:
766         return False
767
768 def isUnsignedLong(informations):
769     """
770     Return True if the value is an unsigned long.
771     """
772     if "unsigned" in informations and "long" in informations and informations["unsigned"] and informations["long"]:
773         return True
774     else:
775         return False
776
777 class ParseError(Exception):
778     def __init__(self, line_number, line):
779         Exception.__init__(self)
780         self.line_number = line_number
781         self.line = line
782
783 class SupportedException(Exception):
784     def __init__(self, support_string):
785         Exception.__init__(self)
786         self.support_string = support_string