Change the comment style for the modules and the list, and use the newParser to parse...
[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     print "*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h"
225     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
226         listInfosDict.update(loadDefineLists(path + "/" + filename))
227     project.setInfo("MODULES", moduleInfosDict)
228     project.setInfo("LISTS", listInfosDict)
229     project.setInfo("CONFIGURATIONS", configurationsInfoDict)
230
231 def getDefinitionBlocks(text):
232     """
233     Take a text and return a list of tuple (description, name-value).
234     """
235     block = []
236     block_tmp = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
237     for comment, define in block_tmp:
238         block.append((" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip(), define))
239     block += re.findall(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
240     block += [(comment, define) for define, comment in re.findall(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)]
241     return block
242
243 def formatParamNameValue(text):
244     """
245     Take the given string and return a tuple with the name of the parameter in the first position
246     and the value in the second.
247     """
248     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
249     return block[0]
250
251 def getDescriptionInformations(text): 
252     """ 
253     Take the doxygen comment and strip the wizard informations, returning the tuple 
254     (comment, wizard_informations) 
255     """ 
256     index = text.find("$WIZARD") 
257     if index != -1: 
258         exec(text[index + 1:]) 
259         informations = WIZARD 
260         return text[:index].strip(), informations
261     else:
262         return text.strip(), {}
263
264 def loadConfigurationInfos(path):
265     """
266     Return the module configurations found in the given file as a dict with the
267     parameter name as key and a dict containig the fields above as value:
268         "value": the value of the parameter
269         "description": the description of the parameter
270         "informations": a dict containig optional informations:
271             "type": "int" | "boolean" | "enum"
272             "min": the minimum value for integer parameters
273             "max": the maximum value for integer parameters
274             "long": boolean indicating if the num is a long
275             "value_list": the name of the enum for enum parameters
276     """
277     try:
278         configurationInfos = {}
279         for comment, define in getDefinitionBlocks(open(path, "r").read()):
280             name, value = formatParamNameValue(define)
281             description, informations = getDescriptionInformations(comment)
282             configurationInfos[name] = {}
283             configurationInfos[name]["value"] = value
284             configurationInfos[name]["informations"] = informations
285             if ("type" in configurationInfos[name]["informations"].keys() and
286                     configurationInfos[name]["informations"]["type"] == "int" and
287                     configurationInfos[name]["value"].find("L") != -1):
288                 configurationInfos[name]["informations"]["long"] = True
289                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("L", "")
290             if ("type" in configurationInfos[name]["informations"].keys() and
291                     configurationInfos[name]["informations"]["type"] == "int" and
292                     configurationInfos[name]["value"].find("U") != -1):
293                 configurationInfos[name]["informations"]["unsigned"] = True
294                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("U", "")
295             configurationInfos[name]["description"] = description
296         return configurationInfos
297     except SyntaxError:
298         raise DefineException.ConfigurationDefineException(path, name)
299
300 def loadDefineLists(path):
301     """
302     Return a dict with the name of the list as key and a list of string as value
303     """
304     try:
305         string = open(path, "r").read()
306         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
307         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
308         listDict = {}
309         for comment in commentList:
310             index = comment.find("$WIZARD_LIST")
311             if index != -1:
312                 exec(comment[index + 1:])
313                 listDict.update(WIZARD_LIST)
314         return listDict
315     except SyntaxError:
316         raise DefineException.EnumDefineException(path)
317
318 def loadModuleInfos(path):
319     """
320     Returns the module infos and the lists infos founded in the file located in the path,
321     and the configurations infos for the module defined in this file.
322     """
323     try:
324         moduleInfos = {}
325         listInfos = {}
326         configurationsInfos = {}
327         string = open(path, "r").read()
328         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
329         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
330         if len(commentList) > 0:
331             comment = commentList[0]
332             if comment.find("$WIZARD_MODULE") != -1:
333                 index = comment.find("$WIZARD_MODULE")
334                 if index != -1:
335                     # 14 is the length of "$WIZARD_MODULE"
336                     if len(comment[index + 14:].strip()) > 0:
337                         exec(comment[index + 1:])
338                         moduleInfos[WIZARD_MODULE["name"]] = {"depends": WIZARD_MODULE["depends"],
339                                                                 "configuration": WIZARD_MODULE["configuration"],
340                                                                 "description": "",
341                                                                 "enabled": False}
342                         index = comment.find("\\brief")
343                         if index != -1:
344                             description = comment[index + 7:]
345                             description = description[:description.find(" * ")]
346                             moduleInfos[WIZARD_MODULE["name"]]["description"] = description
347                         if "configuration" in WIZARD_MODULE.keys() and len(WIZARD_MODULE["configuration"]) > 0:
348                             configurationsInfos[WIZARD_MODULE["configuration"]] = {}
349                     listInfos.update(loadDefineLists(path))
350         return moduleInfos, listInfos, configurationsInfos
351     except SyntaxError:
352         raise DefineException.ModuleDefineException(path)
353
354 def sub(string, parameter, value):
355     """
356     Substitute the given value at the given parameter define in the given string
357     """
358     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
359
360 def isInt(informations):
361     """
362     Return True if the value is a simple int.
363     """
364     if ("long" not in informatios.keys() or not informations["long"]) and ("unsigned" not in informations.keys() or informations["unsigned"]):
365         return True
366     else:
367         return False
368
369 def isLong(informations):
370     """
371     Return True if the value is a long.
372     """
373     if "long" in informations.keys() and informations["long"] and "unsigned" not in informations.keys():
374         return True
375     else:
376         return False
377
378 def isUnsigned(informations):
379     """
380     Return True if the value is an unsigned.
381     """
382     if "unsigned" in informations.keys() and informations["unsigned"] and "long" not in informations.keys():
383         return True
384     else:
385         return False
386
387 def isUnsignedLong(informations):
388     """
389     Return True if the value is an unsigned long.
390     """
391     if "unsigned" in informations.keys() and "long" in informations.keys() and informations["unsigned"] and informations["long"]:
392         return True
393     else:
394         return False