Change the syntax of the wizard tag also in the configuration files.
[bertos.git] / wizard / bertos_utils.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 #
4 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
5 # All rights reserved.
6 #
7 # $Id:$
8 #
9 # Author: Lorenzo Berni <duplo@develer.com>
10 #
11
12 import os
13 import fnmatch
14 import glob
15 import re
16 import shutil
17
18 import const
19 import DefineException
20
21 # Try to use the new parsing module for the module information and the define lists
22 import newParser
23
24 def isBertosDir(directory):
25    return os.path.exists(directory + "/VERSION")
26
27 def bertosVersion(directory):
28    return open(directory + "/VERSION").readline().strip()
29
30 def createBertosProject(projectInfos):
31     directory = projectInfos.info("PROJECT_PATH")
32     sourcesDir = projectInfos.info("SOURCES_PATH")
33     if not os.path.isdir(directory):
34         os.mkdir(directory)
35     f = open(directory + "/project.bertos", "w")
36     f.write(repr(projectInfos))
37     f.close()
38     ## Destination source dir
39     srcdir = directory + "/bertos"
40     shutil.rmtree(srcdir, True)
41     shutil.copytree(sourcesDir + "/bertos", srcdir)
42     ## Destination makefile
43     makefile = directory + "/Makefile"
44     if os.path.exists(makefile):
45         os.remove(makefile)
46     makefile = open("mktemplates/Makefile").read()
47     makefile = makefileGenerator(projectInfos, makefile)
48     open(directory + "/Makefile", "w").write(makefile)
49     ## Destination project dir
50     prjdir = directory + "/" + os.path.basename(directory)
51     shutil.rmtree(prjdir, True)
52     os.mkdir(prjdir)
53     ## Destination configurations files
54     cfgdir = prjdir + "/cfg"
55     shutil.rmtree(cfgdir, True)
56     os.mkdir(cfgdir)
57     for key, value in projectInfos.info("CONFIGURATIONS").items():
58         string = open(sourcesDir + "/" + key, "r").read()
59         for parameter, infos in value.items():
60             value = infos["value"]
61             if "unsigned" in infos["informations"].keys() and infos["informations"]["unsigned"]:
62                 value += "U"
63             if "long" in infos["informations"].keys() and infos["informations"]["long"]:
64                 value += "L"
65             string = sub(string, parameter, value)
66         f = open(cfgdir + "/" + os.path.basename(key), "w")
67         f.write(string)
68         f.close()
69     ## Destinatio mk file
70     makefile = open("mktemplates/template.mk", "r").read()
71     makefile = mkGenerator(projectInfos, makefile)
72     open(prjdir + "/" + os.path.basename(prjdir) + ".mk", "w").write(makefile)
73
74 def mkGenerator(projectInfos, makefile):
75     """
76     Generates the mk file for the current project.
77     """
78     mkData = {}
79     mkData["pname"] = os.path.basename(projectInfos.info("PROJECT_PATH"))
80     mkData["cpuname"] = projectInfos.info("CPU_INFOS")["CPU_NAME"]
81     mkData["cflags"] = " ".join(projectInfos.info("CPU_INFOS")["C_FLAGS"])
82     mkData["ldflags"] = " ".join(projectInfos.info("CPU_INFOS")["LD_FLAGS"])
83     for key in mkData:
84         while makefile.find(key) != -1:
85             makefile = makefile.replace(key, mkData[key])
86     return makefile
87
88 def makefileGenerator(projectInfos, makefile):
89     """
90     Generate the Makefile for the current project.
91     """
92     # TODO: write a general function that works for both the mk file and the Makefile
93     while makefile.find("project_name") != -1:
94         makefile = makefile.replace("project_name", os.path.basename(projectInfos.info("PROJECT_PATH")))
95     return makefile
96
97 def getSystemPath():
98     path = os.environ["PATH"]
99     if os.name == "nt":
100         path = path.split(";")
101     else:
102         path = path.split(":")
103     return path
104
105 def findToolchains(pathList):
106     toolchains = []
107     for element in pathList:
108         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
109             if not os.path.islink(toolchain):
110                 toolchains.append(toolchain)
111     return list(set(toolchains))
112
113 def getToolchainInfo(output):
114     info = {}
115     expr = re.compile("Target: .*")
116     target = expr.findall(output)
117     if len(target) == 1:
118         info["target"] = target[0].split("Target: ")[1]
119     expr = re.compile("gcc version [0-9,.]*")
120     version = expr.findall(output)
121     if len(version) == 1:
122         info["version"] = version[0].split("gcc version ")[1]
123     expr = re.compile("gcc version [0-9,.]* \(.*\)")
124     build = expr.findall(output)
125     if len(build) == 1:
126         build = build[0].split("gcc version ")[1]
127         build = build[build.find("(") + 1 : build.find(")")]
128         info["build"] = build
129     expr = re.compile("Configured with: .*")
130     configured = expr.findall(output)
131     if len(configured) == 1:
132         info["configured"] = configured[0].split("Configured with: ")[1]
133     expr = re.compile("Thread model: .*")
134     thread = expr.findall(output)
135     if len(thread) == 1:
136         info["thread"] = thread[0].split("Thread model: ")[1]
137     return info
138
139 def loadSourceTree(project):
140     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
141     project.setInfo("FILE_LIST", fileList)
142
143 def findDefinitions(ftype, project):
144     L = project.info("FILE_LIST")
145     definitions = []
146     for element in L:
147         for filename in element[2]:
148             if fnmatch.fnmatch(filename, ftype):
149                 definitions.append((filename, element[0]))
150     return definitions
151
152 def loadCpuInfos(project):
153     cpuInfos = []
154     for definition in findDefinitions(const.CPU_DEFINITION, project):
155         cpuInfos.append(getInfos(definition))
156     return cpuInfos
157
158 def getInfos(definition):
159     D = {}
160     D.update(const.CPU_DEF)
161     def include(filename, dict = D, directory=definition[1]):
162         execfile(directory + "/" + filename, {}, D)
163     D["include"] = include
164     include(definition[0], D)
165     D["CPU_NAME"] = definition[0].split(".")[0]
166     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
167     del D["include"]
168     return D
169
170 def loadModuleData(project):
171     moduleInfoDict = {}
172     listInfoDict = {}
173     configurationInfoDict = {}
174     for filename, path in findDefinitions("*.h", project):
175         commentList = newParser.getCommentList(open(path + "/" + filename, "r").read())
176         if len(commentList) > 0:
177             moduleInfo = {}
178             configurationInfo = {}
179             try:
180                 toBeParsed, moduleDict = newParser.loadModuleDefinition(commentList[0])
181             except newParser.ParseError, err:
182                 print "error in file %s. line: %d - statement %s" % (path + "/" + filename, err.line_number, err.line)
183                 print err.args
184                 print err.message
185                 raise Exception
186             for module, information in moduleDict.items():
187                 if "configuration" in information.keys() and len(information["configuration"]):
188                     configuration = moduleDict[module]["configuration"]
189                     configurationInfo[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
190             moduleInfoDict.update(moduleDict)
191             configurationInfoDict.update(configurationInfo)
192             if toBeParsed:
193                 try:
194                     listDict = newParser.loadDefineLists(commentList[1:])
195                     listInfoDict.update(listDict)
196                 except newParser.ParseError, err:
197                     print "error in file %s. line: %d - statement %s" % (path + "/" + filename, err.line_number, err.line)
198                     print err.args
199                     print err.message
200                     raise Exception
201     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
202         commentList = newParser.getCommentList(open(path + "/" + filename, "r").read())
203         listInfoDict.update(newParser.loadDefineLists(commentList))
204     project.setInfo("MODULES", moduleInfoDict)
205     project.setInfo("LISTS", listInfoDict)
206     project.setInfo("CONFIGURATIONS", configurationInfoDict)
207     
208
209 def loadModuleData_old(project):
210     """
211     Loads all the module data, like module definition, list definition, and module configurations
212     int the given BProject, using the SOURCES_PATH information from this as the base for find the
213     header files.
214     """
215     moduleInfosDict = {}
216     listInfosDict = {}
217     configurationsInfoDict = {}
218     for filename, path in findDefinitions("*.h", project):
219         moduleInfos, listInfos, configurationInfos= loadModuleInfos(path + "/" + filename)
220         moduleInfosDict.update(moduleInfos)
221         listInfosDict.update(listInfos)
222         for configuration in configurationInfos.keys():
223             configurationsInfoDict[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
224     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
225         listInfosDict.update(loadDefineLists(path + "/" + filename))
226     project.setInfo("MODULES", moduleInfosDict)
227     project.setInfo("LISTS", listInfosDict)
228     project.setInfo("CONFIGURATIONS", configurationsInfoDict)
229
230 def getDefinitionBlocks(text):
231     """
232     Take a text and return a list of tuple (description, name-value).
233     """
234     block = []
235     block_tmp = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
236     for comment, define in block_tmp:
237         block.append((" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip(), define))
238     block += re.findall(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
239     block += [(comment, define) for define, comment in re.findall(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)]
240     return block
241
242 def formatParamNameValue(text):
243     """
244     Take the given string and return a tuple with the name of the parameter in the first position
245     and the value in the second.
246     """
247     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
248     return block[0]
249
250 def getDescriptionInformations(text): 
251     """ 
252     Take the doxygen comment and strip the wizard informations, returning the tuple 
253     (comment, wizard_informations) 
254     """ 
255     index = text.find("$WIZARD") 
256     if index != -1: 
257         exec(text[index + 1:]) 
258         informations = WIZARD 
259         return text[:index].strip(), informations
260     else:
261         return text.strip(), {}
262
263 def loadConfigurationInfos(path):
264     """
265     Return the module configurations found in the given file as a dict with the
266     parameter name as key and a dict containig the fields above as value:
267         "value": the value of the parameter
268         "description": the description of the parameter
269         "informations": a dict containig optional informations:
270             "type": "int" | "boolean" | "enum"
271             "min": the minimum value for integer parameters
272             "max": the maximum value for integer parameters
273             "long": boolean indicating if the num is a long
274             "value_list": the name of the enum for enum parameters
275     """
276     try:
277         configurationInfos = {}
278         for comment, define in newParser.getDefinitionBlocks(open(path, "r").read()):
279             name, value = formatParamNameValue(define)
280             description, informations = newParser.getDescriptionInformations(comment)
281             configurationInfos[name] = {}
282             configurationInfos[name]["value"] = value
283             configurationInfos[name]["informations"] = informations
284             if ("type" in configurationInfos[name]["informations"].keys() and
285                     configurationInfos[name]["informations"]["type"] == "int" and
286                     configurationInfos[name]["value"].find("L") != -1):
287                 configurationInfos[name]["informations"]["long"] = True
288                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("L", "")
289             if ("type" in configurationInfos[name]["informations"].keys() and
290                     configurationInfos[name]["informations"]["type"] == "int" and
291                     configurationInfos[name]["value"].find("U") != -1):
292                 configurationInfos[name]["informations"]["unsigned"] = True
293                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("U", "")
294             configurationInfos[name]["description"] = description
295         return configurationInfos
296     except newParser.ParseError, err:
297         print "error in file %s. line: %d - statement %s" % (path, err.line_number, err.line)
298         print err.args
299         print err.message
300         raise Exception
301
302 def loadConfigurationInfos_old(path):
303     """
304     Return the module configurations found in the given file as a dict with the
305     parameter name as key and a dict containig the fields above as value:
306         "value": the value of the parameter
307         "description": the description of the parameter
308         "informations": a dict containig optional informations:
309             "type": "int" | "boolean" | "enum"
310             "min": the minimum value for integer parameters
311             "max": the maximum value for integer parameters
312             "long": boolean indicating if the num is a long
313             "value_list": the name of the enum for enum parameters
314     """
315     try:
316         configurationInfos = {}
317         for comment, define in getDefinitionBlocks(open(path, "r").read()):
318             name, value = formatParamNameValue(define)
319             description, informations = getDescriptionInformations(comment)
320             configurationInfos[name] = {}
321             configurationInfos[name]["value"] = value
322             configurationInfos[name]["informations"] = informations
323             if ("type" in configurationInfos[name]["informations"].keys() and
324                     configurationInfos[name]["informations"]["type"] == "int" and
325                     configurationInfos[name]["value"].find("L") != -1):
326                 configurationInfos[name]["informations"]["long"] = True
327                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("L", "")
328             if ("type" in configurationInfos[name]["informations"].keys() and
329                     configurationInfos[name]["informations"]["type"] == "int" and
330                     configurationInfos[name]["value"].find("U") != -1):
331                 configurationInfos[name]["informations"]["unsigned"] = True
332                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("U", "")
333             configurationInfos[name]["description"] = description
334         return configurationInfos
335     except SyntaxError:
336         raise DefineException.ConfigurationDefineException(path, name)
337
338 def loadDefineLists(path):
339     """
340     Return a dict with the name of the list as key and a list of string as value
341     """
342     try:
343         string = open(path, "r").read()
344         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
345         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
346         listDict = {}
347         for comment in commentList:
348             index = comment.find("$WIZARD_LIST")
349             if index != -1:
350                 exec(comment[index + 1:])
351                 listDict.update(WIZARD_LIST)
352         return listDict
353     except SyntaxError:
354         raise DefineException.EnumDefineException(path)
355
356 def loadModuleInfos(path):
357     """
358     Returns the module infos and the lists infos founded in the file located in the path,
359     and the configurations infos for the module defined in this file.
360     """
361     try:
362         moduleInfos = {}
363         listInfos = {}
364         configurationsInfos = {}
365         string = open(path, "r").read()
366         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
367         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
368         if len(commentList) > 0:
369             comment = commentList[0]
370             if comment.find("$WIZARD_MODULE") != -1:
371                 index = comment.find("$WIZARD_MODULE")
372                 if index != -1:
373                     # 14 is the length of "$WIZARD_MODULE"
374                     if len(comment[index + 14:].strip()) > 0:
375                         exec(comment[index + 1:])
376                         moduleInfos[WIZARD_MODULE["name"]] = {"depends": WIZARD_MODULE["depends"],
377                                                                 "configuration": WIZARD_MODULE["configuration"],
378                                                                 "description": "",
379                                                                 "enabled": False}
380                         index = comment.find("\\brief")
381                         if index != -1:
382                             description = comment[index + 7:]
383                             description = description[:description.find(" * ")]
384                             moduleInfos[WIZARD_MODULE["name"]]["description"] = description
385                         if "configuration" in WIZARD_MODULE.keys() and len(WIZARD_MODULE["configuration"]) > 0:
386                             configurationsInfos[WIZARD_MODULE["configuration"]] = {}
387                     listInfos.update(loadDefineLists(path))
388         return moduleInfos, listInfos, configurationsInfos
389     except SyntaxError:
390         raise DefineException.ModuleDefineException(path)
391
392 def sub(string, parameter, value):
393     """
394     Substitute the given value at the given parameter define in the given string
395     """
396     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
397
398 def isInt(informations):
399     """
400     Return True if the value is a simple int.
401     """
402     if ("long" not in informatios.keys() or not informations["long"]) and ("unsigned" not in informations.keys() or informations["unsigned"]):
403         return True
404     else:
405         return False
406
407 def isLong(informations):
408     """
409     Return True if the value is a long.
410     """
411     if "long" in informations.keys() and informations["long"] and "unsigned" not in informations.keys():
412         return True
413     else:
414         return False
415
416 def isUnsigned(informations):
417     """
418     Return True if the value is an unsigned.
419     """
420     if "unsigned" in informations.keys() and informations["unsigned"] and "long" not in informations.keys():
421         return True
422     else:
423         return False
424
425 def isUnsignedLong(informations):
426     """
427     Return True if the value is an unsigned long.
428     """
429     if "unsigned" in informations.keys() and "long" in informations.keys() and informations["unsigned"] and informations["long"]:
430         return True
431     else:
432         return False