Add VERSION file into the project directory.
[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     # VERSION file
158     version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
159     open(directory + "/VERSION", "w").write(versionFileGenerator(project_info, version_file))
160     # Destination source dir
161     srcdir = directory + "/bertos"
162     if not edit:
163         # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
164         shutil.rmtree(srcdir, True)
165         copytree.copytree(sources_dir + "/bertos", srcdir, ignore_list=const.IGNORE_LIST)
166     elif old_sources_dir:
167         # If in editing mode it merges the current bertos sources with the selected ones
168         # TODO: implement the three way merge algotihm
169         #
170         mergeSources(srcdir, sources_dir, old_sources_dir)
171     # Destination makefile
172     makefile = directory + "/Makefile"
173     makefile = open(os.path.join(const.DATA_DIR, "mktemplates/Makefile"), 'r').read()
174     makefile = makefileGenerator(project_info, makefile)
175     open(directory + "/Makefile", "w").write(makefile)
176     # Destination project dir
177     prjdir = directory + "/" + os.path.basename(directory)
178     if not edit:
179         shutil.rmtree(prjdir, True)
180         os.mkdir(prjdir)
181     # Destination hw files
182     hwdir = prjdir + "/hw"
183     if not edit:
184         shutil.rmtree(hwdir, True)
185         os.mkdir(hwdir)
186     # Copy all the hw files
187     for module, information in project_info.info("MODULES").items():
188         for hwfile in information["hw"]:
189             string = open(sources_dir + "/" + hwfile, "r").read()
190             hwfile_path = hwdir + "/" + os.path.basename(hwfile)
191             if not edit or not os.path.exists(hwfile_path):
192                 # If not in editing mode it copies all the hw files. If in
193                 # editing mode it copies only the files that don't exist yet
194                 open(hwdir + "/" + os.path.basename(hwfile), "w").write(string)
195     # Destination configurations files
196     cfgdir = prjdir + "/cfg"
197     if not edit:
198         shutil.rmtree(cfgdir, True)
199         os.mkdir(cfgdir)
200     # Set properly the autoenabled parameters
201     for module, information in project_info.info("MODULES").items():
202         if "configuration" in information and information["configuration"] != "":
203             configurations = project_info.info("CONFIGURATIONS")
204             configuration = configurations[information["configuration"]]
205             for start, parameter in configuration["paramlist"]:
206                 if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
207                     configuration[parameter]["value"] = "1" if information["enabled"] else "0"
208             project_info.setInfo("CONFIGURATIONS", configurations)
209     # Copy all the configuration files
210     for configuration, information in project_info.info("CONFIGURATIONS").items():
211         string = open(sources_dir + "/" + configuration, "r").read()
212         for start, parameter in information["paramlist"]:
213             infos = information[parameter]
214             value = infos["value"]
215             if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
216                 value += "U"
217             if "long" in infos["informations"] and infos["informations"]["long"]:
218                 value += "L"
219             string = sub(string, parameter, value)
220         f = open(cfgdir + "/" + os.path.basename(configuration), "w")
221         f.write(string)
222         f.close()
223     if not edit:
224         # Destination user mk file (only on project creation)
225         makefile = open(os.path.join(const.DATA_DIR, "mktemplates/template.mk"), "r").read()
226         makefile = mkGenerator(project_info, makefile)
227         open(prjdir + "/" + os.path.basename(prjdir) + ".mk", "w").write(makefile)
228     # Destination wizard mk file
229     makefile = open(os.path.join(const.DATA_DIR, "mktemplates/template_wiz.mk"), "r").read()
230     makefile = mkGenerator(project_info, makefile)
231     open(prjdir + "/" + os.path.basename(prjdir) + "_wiz.mk", "w").write(makefile)
232     # Destination main.c file
233     if not edit:
234         main = open(os.path.join(const.DATA_DIR, "srctemplates/main.c"), "r").read()
235         open(prjdir + "/main.c", "w").write(main)
236     # Files for selected plugins
237     relevants_files = {}
238     for plugin in project_info.info("OUTPUT"):
239         module = loadPlugin(plugin)
240         relevants_files[plugin] = module.createProject(project_info)
241     project_info.setInfo("RELEVANT_FILES", relevants_files)
242
243 def loadPlugin(plugin):
244     """
245     Returns the given plugin module.
246     """
247     return getattr(__import__("plugins", {}, {}, [plugin]), plugin)
248
249 def versionFileGenerator(project_info, version_file):
250     version = bertosVersion(project_info.info("SOURCES_PATH"))
251     return version_file.replace('$version', version)
252
253 def mkGenerator(project_info, makefile):
254     """
255     Generates the mk file for the current project.
256     """
257     mk_data = {}
258     mk_data["$pname"] = os.path.basename(project_info.info("PROJECT_PATH"))
259     mk_data["$cpuclockfreq"] = project_info.info("SELECTED_FREQ")
260     cpu_mk_parameters = []
261     for key, value in project_info.info("CPU_INFOS").items():
262         if key.startswith(const.MK_PARAM_ID):
263             cpu_mk_parameters.append("%s = %s" %(key.replace("MK", mk_data["$pname"]), value))
264     mk_data["$cpuparameters"] = "\n".join(cpu_mk_parameters)
265     mk_data["$csrc"], mk_data["$pcsrc"], mk_data["$cppasrc"], mk_data["$cxxsrc"], mk_data["$asrc"], mk_data["$constants"] = csrcGenerator(project_info)
266     mk_data["$prefix"] = replaceSeparators(project_info.info("TOOLCHAIN")["path"].split("gcc")[0])
267     mk_data["$suffix"] = replaceSeparators(project_info.info("TOOLCHAIN")["path"].split("gcc")[1])
268     mk_data["$main"] = os.path.basename(project_info.info("PROJECT_PATH")) + "/main.c"
269     for key in mk_data:
270         while makefile.find(key) != -1:
271             makefile = makefile.replace(key, mk_data[key])
272     return makefile
273
274 def makefileGenerator(project_info, makefile):
275     """
276     Generate the Makefile for the current project.
277     """
278     # TODO write a general function that works for both the mk file and the Makefile
279     while makefile.find("$pname") != -1:
280         makefile = makefile.replace("$pname", os.path.basename(project_info.info("PROJECT_PATH")))
281     return makefile
282
283 def csrcGenerator(project_info):
284     modules = project_info.info("MODULES")
285     files = project_info.info("FILES")
286     if "harvard" in project_info.info("CPU_INFOS")["CPU_TAGS"]:
287         harvard = True
288     else:
289         harvard = False
290     # file to be included in CSRC variable
291     csrc = []
292     # file to be included in PCSRC variable
293     pcsrc = []
294     # files to be included in CPPASRC variable
295     cppasrc = []
296     # files to be included in CXXSRC variable
297     cxxsrc = []
298     # files to be included in ASRC variable
299     asrc = []
300     # constants to be included at the beginning of the makefile
301     constants = {}
302     for module, information in modules.items():
303         module_files = set([])
304         dependency_files = set([])
305         # assembly sources
306         asm_files = set([])
307         hwdir = os.path.basename(project_info.info("PROJECT_PATH")) + "/hw" 
308         if information["enabled"]:
309             if "constants" in information:
310                 constants.update(information["constants"])
311             cfiles, sfiles = findModuleFiles(module, project_info)
312             module_files |= set(cfiles)
313             asm_files |= set(sfiles)
314             for file in information["hw"]:
315                 if file.endswith(".c"):
316                     module_files |= set([hwdir + "/" + os.path.basename(file)])
317             for file_dependency in information["depends"] + tuple(files.keys()):
318                     dependencyCFiles, dependencySFiles = findModuleFiles(file_dependency, project_info)
319                     dependency_files |= set(dependencyCFiles)
320                     asm_files |= set(dependencySFiles)
321             for file in module_files:
322                 if not harvard or information.get("harvard", "both") == "both":
323                     csrc.append(file)
324                 if harvard and "harvard" in information:
325                     pcsrc.append(file)
326             for file in dependency_files:
327                 csrc.append(file)
328             for file in project_info.info("CPU_INFOS")["C_SRC"]:
329                 csrc.append(file)
330             for file in project_info.info("CPU_INFOS")["PC_SRC"]:
331                 pcsrc.append(file)
332             for file in asm_files:
333                 cppasrc.append(file)
334     for file in project_info.info("CPU_INFOS")["CPPA_SRC"]:
335         cppasrc.append(file)
336     for file in project_info.info("CPU_INFOS")["CXX_SRC"]:
337         cxxsrc.append(file)
338     for file in project_info.info("CPU_INFOS")["ASRC"]:
339         asrc.append(file)
340     csrc = set(csrc)
341     csrc = " \\\n\t".join(csrc) + " \\"
342     pcsrc = set(pcsrc)
343     pcsrc = " \\\n\t".join(pcsrc) + " \\"
344     cppasrc = set(cppasrc)
345     cppasrc = " \\\n\t".join(cppasrc) + " \\"
346     cxxsrc = set(cxxsrc)
347     cxxsrc = " \\\n\t".join(cxxsrc) + " \\"
348     asrc = set(asrc)
349     asrc = " \\\n\t".join(asrc) + " \\"
350     constants = "\n".join([os.path.basename(project_info.info("PROJECT_PATH")) + "_" + key + " = " + unicode(value) for key, value in constants.items()])
351     return csrc, pcsrc, cppasrc, cxxsrc, asrc, constants
352
353 def findModuleFiles(module, project_info):
354     # Find the files related to the selected module
355     cfiles = []
356     sfiles = []
357     # .c files related to the module and the cpu architecture
358     for filename, path in findDefinitions(module + ".c", project_info) + \
359             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".c", project_info):
360         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
361         path = replaceSeparators(path)
362         cfiles.append(path + "/" + filename)
363     # .s files related to the module and the cpu architecture
364     for filename, path in findDefinitions(module + ".s", project_info) + \
365             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".s", project_info) + \
366             findDefinitions(module + ".S", project_info) + \
367             findDefinitions(module + "_" + project_info.info("CPU_INFOS")["TOOLCHAIN"] + ".S", project_info):
368         path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
369         path = replaceSeparators(path)
370         sfiles.append(path + "/" + filename)
371     # .c and .s files related to the module and the cpu tags
372     for tag in project_info.info("CPU_INFOS")["CPU_TAGS"]:
373         for filename, path in findDefinitions(module + "_" + tag + ".c", project_info):
374             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
375             if os.sep != "/":
376                 path = replaceSeparators(path)
377             cfiles.append(path + "/" + filename)
378         for filename, path in findDefinitions(module + "_" + tag + ".s", project_info) + \
379                 findDefinitions(module + "_" + tag + ".S", project_info):
380             path = path.replace(project_info.info("SOURCES_PATH") + os.sep, "")
381             path = replaceSeparators(path)
382             sfiles.append(path + "/" + filename)
383     return cfiles, sfiles
384
385 def replaceSeparators(path):
386     """
387     Replace the separators in the given path with unix standard separator.
388     """
389     if os.sep != "/":
390         while path.find(os.sep) != -1:
391             path = path.replace(os.sep, "/")
392     return path
393
394 def getSystemPath():
395     path = os.environ["PATH"]
396     if os.name == "nt":
397         path = path.split(";")
398     else:
399         path = path.split(":")
400     return path
401
402 def findToolchains(path_list):
403     toolchains = []
404     for element in path_list:
405         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
406             toolchains.append(toolchain)
407     return list(set(toolchains))
408
409 def getToolchainInfo(output):
410     info = {}
411     expr = re.compile("Target: .*")
412     target = expr.findall(output)
413     if len(target) == 1:
414         info["target"] = target[0].split("Target: ")[1]
415     expr = re.compile("gcc version [0-9,.]*")
416     version = expr.findall(output)
417     if len(version) == 1:
418         info["version"] = version[0].split("gcc version ")[1]
419     expr = re.compile("gcc version [0-9,.]* \(.*\)")
420     build = expr.findall(output)
421     if len(build) == 1:
422         build = build[0].split("gcc version ")[1]
423         build = build[build.find("(") + 1 : build.find(")")]
424         info["build"] = build
425     expr = re.compile("Configured with: .*")
426     configured = expr.findall(output)
427     if len(configured) == 1:
428         info["configured"] = configured[0].split("Configured with: ")[1]
429     expr = re.compile("Thread model: .*")
430     thread = expr.findall(output)
431     if len(thread) == 1:
432         info["thread"] = thread[0].split("Thread model: ")[1]
433     return info
434
435 def getToolchainName(toolchain_info):
436     name = "GCC " + toolchain_info["version"] + " - " + toolchain_info["target"].strip()
437     return name
438
439 def loadSourceTree(project):
440     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
441     project.setInfo("FILE_LIST", fileList)
442
443 def findDefinitions(ftype, project):
444     L = project.info("FILE_LIST")
445     definitions = []
446     for element in L:
447         for filename in element[2]:
448             if fnmatch.fnmatch(filename, ftype):
449                 definitions.append((filename, element[0]))
450     return definitions
451
452 def loadCpuInfos(project):
453     cpuInfos = []
454     for definition in findDefinitions(const.CPU_DEFINITION, project):
455         cpuInfos.append(getInfos(definition))
456     return cpuInfos
457
458 def getTagSet(cpu_info):
459     tag_set = set([])
460     for cpu in cpu_info:
461         tag_set |= set([cpu["CPU_NAME"]])
462         tag_set |= set(cpu["CPU_TAGS"])
463         tag_set |= set([cpu["TOOLCHAIN"]])
464     return tag_set
465         
466
467 def getInfos(definition):
468     D = {}
469     D.update(const.CPU_DEF)
470     def include(filename, dict = D, directory=definition[1]):
471         execfile(directory + "/" + filename, {}, D)
472     D["include"] = include
473     include(definition[0], D)
474     D["CPU_NAME"] = definition[0].split(".")[0]
475     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
476     del D["include"]
477     return D
478
479 def getCommentList(string):
480     comment_list = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
481     comment_list = [re.findall(r"^\s*\* *(.*?)$", comment, re.MULTILINE) for comment in comment_list]
482     return comment_list
483
484 def loadModuleDefinition(first_comment):
485     to_be_parsed = False
486     module_definition = {}
487     for num, line in enumerate(first_comment):
488         index = line.find("$WIZ$")
489         if index != -1:
490             to_be_parsed = True
491             try:
492                 exec line[index + len("$WIZ$ "):] in {}, module_definition
493             except:
494                 raise ParseError(num, line[index:])
495         elif line.find("\\brief") != -1:
496             module_definition["module_description"] = line[line.find("\\brief") + len("\\brief "):]
497     module_dict = {}
498     if "module_name" in module_definition:
499         module_name = module_definition[const.MODULE_DEFINITION["module_name"]]
500         del module_definition[const.MODULE_DEFINITION["module_name"]]
501         module_dict[module_name] = {}
502         if const.MODULE_DEFINITION["module_depends"] in module_definition:
503             depends = module_definition[const.MODULE_DEFINITION["module_depends"]]
504             del module_definition[const.MODULE_DEFINITION["module_depends"]]
505             if type(depends) == str:
506                 depends = (depends,)
507             module_dict[module_name]["depends"] = depends
508         else:
509             module_dict[module_name]["depends"] = ()
510         if const.MODULE_DEFINITION["module_configuration"] in module_definition:
511             module_dict[module_name]["configuration"] = module_definition[const.MODULE_DEFINITION["module_configuration"]]
512             del module_definition[const.MODULE_DEFINITION["module_configuration"]]
513         else:
514             module_dict[module_name]["configuration"] = ""
515         if "module_description" in module_definition:
516             module_dict[module_name]["description"] = module_definition["module_description"]
517             del module_definition["module_description"]
518         if const.MODULE_DEFINITION["module_harvard"] in module_definition:
519             harvard = module_definition[const.MODULE_DEFINITION["module_harvard"]]
520             module_dict[module_name]["harvard"] = harvard
521             del module_definition[const.MODULE_DEFINITION["module_harvard"]]
522         if const.MODULE_DEFINITION["module_hw"] in module_definition:
523             hw = module_definition[const.MODULE_DEFINITION["module_hw"]]
524             del module_definition[const.MODULE_DEFINITION["module_hw"]]
525             if type(hw) == str:
526                 hw = (hw, )
527             module_dict[module_name]["hw"] = hw
528         else:
529             module_dict[module_name]["hw"] = ()
530         if const.MODULE_DEFINITION["module_supports"] in module_definition:
531             supports = module_definition[const.MODULE_DEFINITION["module_supports"]]
532             del module_definition[const.MODULE_DEFINITION["module_supports"]]
533             module_dict[module_name]["supports"] = supports
534         module_dict[module_name]["constants"] = module_definition
535         module_dict[module_name]["enabled"] = False
536     return to_be_parsed, module_dict
537
538 def isSupported(project, module=None, property_id=None):
539     if not module and property_id:
540         item = project.info("CONFIGURATIONS")[property_id[0]][property_id[1]]["informations"]
541     else:
542         item = project.info("MODULES")[module]
543     tag_dict = project.info("ALL_CPU_TAGS")
544     if "supports" in item:
545         support_string = item["supports"]
546         supported = {}
547         try:
548             exec "supported = " + support_string in tag_dict, supported
549         except:
550             raise SupportedException(support_string)
551         return supported["supported"]
552     else:
553         return True
554
555 def loadDefineLists(comment_list):
556     define_list = {}
557     for comment in comment_list:
558         for num, line in enumerate(comment):
559             index = line.find("$WIZ$")
560             if index != -1:
561                 try:
562                     exec line[index + len("$WIZ$ "):] in {}, define_list
563                 except:
564                     raise ParseError(num, line[index:])
565     for key, value in define_list.items():
566         if type(value) == str:
567             define_list[key] = (value,)
568     return define_list
569
570 def getDescriptionInformations(comment):
571     """
572     Take the doxygen comment and strip the wizard informations, returning the tuple
573     (comment, wizard_information)
574     """
575     brief = ""
576     description = ""
577     information = {}
578     for num, line in enumerate(comment):
579         index = line.find("$WIZ$")
580         if index != -1:
581             if len(brief) == 0:
582                 brief += line[:index].strip()
583             else:
584                 description += " " + line[:index]
585             try:
586                 exec line[index + len("$WIZ$ "):] in {}, information
587             except:
588                 raise ParseError(num, line[index:])
589         else:
590             if len(brief) == 0:
591                 brief += line.strip()
592             else:
593                 description += " " + line
594                 description = description.strip()
595     return brief.strip(), description.strip(), information
596
597 def getDefinitionBlocks(text):
598     """
599     Take a text and return a list of tuple (description, name-value).
600     """
601     block = []
602     block_tmp = re.finditer(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
603     for match in block_tmp:
604         # Only the first element is needed
605         comment = match.group(1)
606         define = match.group(2)
607         start = match.start()
608         block.append(([re.findall(r"^\s*\* *(.*?)$", line, re.MULTILINE)[0] for line in comment.splitlines()], define, start))
609     for match in re.finditer(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
610         comment = match.group(1)
611         define = match.group(2)
612         start = match.start()
613         block.append(([comment], define, start))
614     for match in re.finditer(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE):
615         comment = match.group(2)
616         define = match.group(1)
617         start = match.start()
618         block.append(([comment], define, start))
619     return block
620
621 def loadModuleData(project, edit=False):
622     module_info_dict = {}
623     list_info_dict = {}
624     configuration_info_dict = {}
625     file_dict = {}
626     for filename, path in findDefinitions("*.h", project) + findDefinitions("*.c", project) + findDefinitions("*.s", project) + findDefinitions("*.S", project):
627         comment_list = getCommentList(open(path + "/" + filename, "r").read())
628         if len(comment_list) > 0:
629             module_info = {}
630             configuration_info = {}
631             try:
632                 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
633             except ParseError, err:
634                 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
635             for module, information in module_dict.items():
636                 if "depends" not in information:
637                     information["depends"] = ()
638                 information["depends"] += (filename.split(".")[0],)
639                 information["category"] = os.path.basename(path)
640                 if "configuration" in information and len(information["configuration"]):
641                     configuration = module_dict[module]["configuration"]
642                     try:
643                         configuration_info[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
644                     except ParseError, err:
645                         raise DefineException.ConfigurationDefineException(project.info("SOURCES_PATH") + "/" + configuration, err.line_number, err.line)
646                     if edit:
647                         try:
648                             path = os.path.basename(project.info("PROJECT_PATH"))
649                             directory = project.info("PROJECT_PATH")
650                             user_configuration = loadConfigurationInfos(directory + "/" + configuration.replace("bertos", path))
651                             configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], user_configuration)
652                         except ParseError, err:
653                             raise DefineException.ConfigurationDefineException(directory + "/" + configuration.replace("bertos", path))
654             module_info_dict.update(module_dict)
655             configuration_info_dict.update(configuration_info)
656             if to_be_parsed:
657                 try:
658                     list_dict = loadDefineLists(comment_list[1:])
659                     list_info_dict.update(list_dict)
660                 except ParseError, err:
661                     raise DefineException.EnumDefineException(path, err.line_number, err.line)
662     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
663         comment_list = getCommentList(open(path + "/" + filename, "r").read())
664         list_info_dict.update(loadDefineLists(comment_list))
665     for tag in project.info("CPU_INFOS")["CPU_TAGS"]:
666         for filename, path in findDefinitions("*_" + tag + ".h", project):
667             comment_list = getCommentList(open(path + "/" + filename, "r").read())
668             list_info_dict.update(loadDefineLists(comment_list))
669     project.setInfo("MODULES", module_info_dict)
670     project.setInfo("LISTS", list_info_dict)
671     project.setInfo("CONFIGURATIONS", configuration_info_dict)
672     project.setInfo("FILES", file_dict)
673
674 def formatParamNameValue(text):
675     """
676     Take the given string and return a tuple with the name of the parameter in the first position
677     and the value in the second.
678     """
679     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
680     return block[0]
681
682 def loadConfigurationInfos(path):
683     """
684     Return the module configurations found in the given file as a dict with the
685     parameter name as key and a dict containig the fields above as value:
686         "value": the value of the parameter
687         "description": the description of the parameter
688         "informations": a dict containig optional informations:
689             "type": "int" | "boolean" | "enum"
690             "min": the minimum value for integer parameters
691             "max": the maximum value for integer parameters
692             "long": boolean indicating if the num is a long
693             "unsigned": boolean indicating if the num is an unsigned
694             "value_list": the name of the enum for enum parameters
695             "conditional_deps": the list of conditional dependencies for boolean parameters
696     """
697     configuration_infos = {}
698     configuration_infos["paramlist"] = []
699     for comment, define, start in getDefinitionBlocks(open(path, "r").read()):
700         name, value = formatParamNameValue(define)
701         brief, description, informations = getDescriptionInformations(comment)
702         configuration_infos["paramlist"].append((start, name))
703         configuration_infos[name] = {}
704         configuration_infos[name]["value"] = value
705         configuration_infos[name]["informations"] = informations
706         if not "type" in configuration_infos[name]["informations"]:
707             configuration_infos[name]["informations"]["type"] = findParameterType(configuration_infos[name])
708         if ("type" in configuration_infos[name]["informations"] and
709                 configuration_infos[name]["informations"]["type"] == "int" and
710                 configuration_infos[name]["value"].find("L") != -1):
711             configuration_infos[name]["informations"]["long"] = True
712             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("L", "")
713         if ("type" in configuration_infos[name]["informations"] and
714                 configuration_infos[name]["informations"]["type"] == "int" and
715                 configuration_infos[name]["value"].find("U") != -1):
716             configuration_infos[name]["informations"]["unsigned"] = True
717             configuration_infos[name]["value"] = configuration_infos[name]["value"].replace("U", "")
718         if "conditional_deps" in configuration_infos[name]["informations"]:
719             if (type(configuration_infos[name]["informations"]["conditional_deps"]) == str or
720                     type(configuration_infos[name]["informations"]["conditional_deps"]) == unicode):
721                 configuration_infos[name]["informations"]["conditional_deps"] = (configuration_infos[name]["informations"]["conditional_deps"], )
722             elif type(configuration_infos[name]["informations"]["conditional_deps"]) == tuple:
723                 pass
724             else:
725                 configuration_infos[name]["informations"]["conditional_deps"] = ()
726         configuration_infos[name]["description"] = description
727         configuration_infos[name]["brief"] = brief
728     return configuration_infos
729
730 def updateConfigurationValues(def_conf, user_conf):
731     for param in def_conf["paramlist"]:
732         if param[1] in user_conf and "value" in user_conf[param[1]]:
733             def_conf[param[1]]["value"] = user_conf[param[1]]["value"]
734     return def_conf
735
736 def findParameterType(parameter):
737     if "value_list" in parameter["informations"]:
738         return "enum"
739     if "min" in parameter["informations"] or "max" in parameter["informations"] or re.match(r"^\d+U?L?$", parameter["value"]) != None:
740         return "int"
741
742 def sub(string, parameter, value):
743     """
744     Substitute the given value at the given parameter define in the given string
745     """
746     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
747
748 def isInt(informations):
749     """
750     Return True if the value is a simple int.
751     """
752     if ("long" not in informatios or not informations["long"]) and ("unsigned" not in informations or informations["unsigned"]):
753         return True
754     else:
755         return False
756
757 def isLong(informations):
758     """
759     Return True if the value is a long.
760     """
761     if "long" in informations and informations["long"] and "unsigned" not in informations:
762         return True
763     else:
764         return False
765
766 def isUnsigned(informations):
767     """
768     Return True if the value is an unsigned.
769     """
770     if "unsigned" in informations and informations["unsigned"] and "long" not in informations:
771         return True
772     else:
773         return False
774
775 def isUnsignedLong(informations):
776     """
777     Return True if the value is an unsigned long.
778     """
779     if "unsigned" in informations and "long" in informations and informations["unsigned"] and informations["long"]:
780         return True
781     else:
782         return False
783
784 class ParseError(Exception):
785     def __init__(self, line_number, line):
786         Exception.__init__(self)
787         self.line_number = line_number
788         self.line = line
789
790 class SupportedException(Exception):
791     def __init__(self, support_string):
792         Exception.__init__(self)
793         self.support_string = support_string