Remove unused functions and rewrite some comments
[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 def isBertosDir(directory):
22    return os.path.exists(directory + "/VERSION")
23
24 def bertosVersion(directory):
25    return open(directory + "/VERSION").readline().strip()
26
27 def createBertosProject(projectInfos):
28     directory = projectInfos.info("PROJECT_PATH")
29     sourcesDir = projectInfos.info("SOURCES_PATH")
30     if not os.path.isdir(directory):
31         os.mkdir(directory)
32     f = open(directory + "/project.bertos", "w")
33     f.write(repr(projectInfos))
34     f.close()
35     ## Destination source dir
36     srcdir = directory + "/bertos"
37     shutil.rmtree(srcdir, True)
38     shutil.copytree(sourcesDir + "/bertos", srcdir)
39     ## Destination makefile
40     makefile = directory + "/Makefile"
41     if os.path.exists(makefile):
42         os.remove(makefile)
43     makefile = open("mktemplates/Makefile").read()
44     makefile = makefileGenerator(projectInfos, makefile)
45     open(directory + "/Makefile", "w").write(makefile)
46     ## Destination project dir
47     prjdir = directory + "/" + os.path.basename(directory)
48     shutil.rmtree(prjdir, True)
49     os.mkdir(prjdir)
50     ## Destination configurations files
51     cfgdir = prjdir + "/cfg"
52     shutil.rmtree(cfgdir, True)
53     os.mkdir(cfgdir)
54     for key, value in projectInfos.info("CONFIGURATIONS").items():
55         string = open(sourcesDir + "/" + key, "r").read()
56         for parameter, infos in value.items():
57             value = infos["value"]
58             if "unsigned" in infos["informations"].keys() and infos["informations"]["unsigned"]:
59                 value += "U"
60             if "long" in infos["informations"].keys() and infos["informations"]["long"]:
61                 value += "L"
62             string = sub(string, parameter, value)
63         f = open(cfgdir + "/" + os.path.basename(key), "w")
64         f.write(string)
65         f.close()
66     ## Destinatio mk file
67     makefile = open("mktemplates/template.mk", "r").read()
68     makefile = mkGenerator(projectInfos, makefile)
69     open(prjdir + "/" + os.path.basename(prjdir) + ".mk", "w").write(makefile)
70
71 def mkGenerator(projectInfos, makefile):
72     """
73     Generates the mk file for the current project.
74     """
75     mkData = {}
76     mkData["pname"] = os.path.basename(projectInfos.info("PROJECT_PATH"))
77     mkData["cpuname"] = projectInfos.info("CPU_INFOS")["CPU_NAME"]
78     mkData["cflags"] = " ".join(projectInfos.info("CPU_INFOS")["C_FLAGS"])
79     mkData["ldflags"] = " ".join(projectInfos.info("CPU_INFOS")["LD_FLAGS"])
80     for key in mkData:
81         while makefile.find(key) != -1:
82             makefile = makefile.replace(key, mkData[key])
83     return makefile
84
85 def makefileGenerator(projectInfos, makefile):
86     """
87     Generate the Makefile for the current project.
88     """
89     # TODO: write a general function that works for both the mk file and the Makefile
90     while makefile.find("project_name") != -1:
91         makefile = makefile.replace("project_name", os.path.basename(projectInfos.info("PROJECT_PATH")))
92     return makefile
93
94 def getSystemPath():
95     path = os.environ["PATH"]
96     if os.name == "nt":
97         path = path.split(";")
98     else:
99         path = path.split(":")
100     return path
101
102 def findToolchains(pathList):
103     toolchains = []
104     for element in pathList:
105         for toolchain in glob.glob(element+ "/" + const.GCC_NAME):
106             if not os.path.islink(toolchain):
107                 toolchains.append(toolchain)
108     return list(set(toolchains))
109
110 def getToolchainInfo(output):
111     info = {}
112     expr = re.compile("Target: .*")
113     target = expr.findall(output)
114     if len(target) == 1:
115         info["target"] = target[0].split("Target: ")[1]
116     expr = re.compile("gcc version [0-9,.]*")
117     version = expr.findall(output)
118     if len(version) == 1:
119         info["version"] = version[0].split("gcc version ")[1]
120     expr = re.compile("gcc version [0-9,.]* \(.*\)")
121     build = expr.findall(output)
122     if len(build) == 1:
123         build = build[0].split("gcc version ")[1]
124         build = build[build.find("(") + 1 : build.find(")")]
125         info["build"] = build
126     expr = re.compile("Configured with: .*")
127     configured = expr.findall(output)
128     if len(configured) == 1:
129         info["configured"] = configured[0].split("Configured with: ")[1]
130     expr = re.compile("Thread model: .*")
131     thread = expr.findall(output)
132     if len(thread) == 1:
133         info["thread"] = thread[0].split("Thread model: ")[1]
134     return info
135
136 def loadSourceTree(project):
137     fileList = [f for f in os.walk(project.info("SOURCES_PATH"))]
138     project.setInfo("FILE_LIST", fileList)
139
140 def findDefinitions(ftype, project):
141     L = project.info("FILE_LIST")
142     definitions = []
143     for element in L:
144         for filename in element[2]:
145             if fnmatch.fnmatch(filename, ftype):
146                 definitions.append((filename, element[0]))
147     return definitions
148
149 def loadCpuInfos(project):
150     cpuInfos = []
151     for definition in findDefinitions(const.CPU_DEFINITION, project):
152         cpuInfos.append(getInfos(definition))
153     return cpuInfos
154
155 def getInfos(definition):
156     D = {}
157     D.update(const.CPU_DEF)
158     def include(filename, dict = D, directory=definition[1]):
159         execfile(directory + "/" + filename, {}, D)
160     D["include"] = include
161     include(definition[0], D)
162     D["CPU_NAME"] = definition[0].split(".")[0]
163     D["DEFINITION_PATH"] = definition[1] + "/" + definition[0]
164     del D["include"]
165     return D
166
167 def loadModuleData(project):
168     """
169     Loads all the module data, like module definition, list definition, and module configurations
170     int the given BProject, using the SOURCES_PATH information from this as the base for find the
171     header files.
172     """
173     moduleInfosDict = {}
174     listInfosDict = {}
175     configurationsInfoDict = {}
176     for filename, path in findDefinitions("*.h", project):
177         moduleInfos, listInfos, configurationInfos= loadModuleInfos(path + "/" + filename)
178         moduleInfosDict.update(moduleInfos)
179         listInfosDict.update(listInfos)
180         for configuration in configurationInfos.keys():
181             configurationsInfoDict[configuration] = loadConfigurationInfos(project.info("SOURCES_PATH") + "/" + configuration)
182     print "*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h"
183     for filename, path in findDefinitions("*_" + project.info("CPU_INFOS")["TOOLCHAIN"] + ".h", project):
184         listInfosDict.update(loadDefineLists(path + "/" + filename))
185     project.setInfo("MODULES", moduleInfosDict)
186     project.setInfo("LISTS", listInfosDict)
187     project.setInfo("CONFIGURATIONS", configurationsInfoDict)
188
189 def getDefinitionBlocks(text):
190     """
191     Take a text and return a list of tuple (description, name-value).
192     """
193     block = []
194     block_tmp = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
195     for comment, define in block_tmp:
196         block.append((" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip(), define))
197     block += re.findall(r"/{3}\s*([^<].*?)\s*#define\s+((?:[^/]*?/?)+)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)
198     block += [(comment, define) for define, comment in re.findall(r"#define\s*(.*?)\s*/{3}<\s*(.+?)\s*?(?:/{2,3}[^<].*?)?$", text, re.MULTILINE)]
199     return block
200
201 def formatParamNameValue(text):
202     """
203     Take the given string and return a tuple with the name of the parameter in the first position
204     and the value in the second.
205     """
206     block = re.findall("\s*([^\s]+)\s*(.+?)\s*$", text, re.MULTILINE)
207     return block[0]
208
209 def getDescriptionInformations(text): 
210     """ 
211     Take the doxygen comment and strip the wizard informations, returning the tuple 
212     (comment, wizard_informations) 
213     """ 
214     index = text.find("$WIZARD") 
215     if index != -1: 
216         exec(text[index + 1:]) 
217         informations = WIZARD 
218         return text[:index].strip(), informations
219     else:
220         return text.strip(), {}
221
222 def loadConfigurationInfos(path):
223     """
224     Return the module configurations found in the given file as a dict with the
225     parameter name as key and a dict containig the fields above as value:
226         "value": the value of the parameter
227         "description": the description of the parameter
228         "informations": a dict containig optional informations:
229             "type": "int" | "boolean" | "enum"
230             "min": the minimum value for integer parameters
231             "max": the maximum value for integer parameters
232             "long": boolean indicating if the num is a long
233             "value_list": the name of the enum for enum parameters
234     """
235     try:
236         configurationInfos = {}
237         for comment, define in getDefinitionBlocks(open(path, "r").read()):
238             name, value = formatParamNameValue(define)
239             description, informations = getDescriptionInformations(comment)
240             configurationInfos[name] = {}
241             configurationInfos[name]["value"] = value
242             configurationInfos[name]["informations"] = informations
243             if ("type" in configurationInfos[name]["informations"].keys() and
244                     configurationInfos[name]["informations"]["type"] == "int" and
245                     configurationInfos[name]["value"].find("L") != -1):
246                 configurationInfos[name]["informations"]["long"] = True
247                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("L", "")
248             if ("type" in configurationInfos[name]["informations"].keys() and
249                     configurationInfos[name]["informations"]["type"] == "int" and
250                     configurationInfos[name]["value"].find("U") != -1):
251                 configurationInfos[name]["informations"]["unsigned"] = True
252                 configurationInfos[name]["value"] = configurationInfos[name]["value"].replace("U", "")
253             configurationInfos[name]["description"] = description
254         return configurationInfos
255     except SyntaxError:
256         raise DefineException.ConfigurationDefineException(path, name)
257
258 def loadDefineLists(path):
259     """
260     Return a dict with the name of the list as key and a list of string as value
261     """
262     try:
263         string = open(path, "r").read()
264         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
265         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
266         listDict = {}
267         for comment in commentList:
268             index = comment.find("$WIZARD_LIST")
269             if index != -1:
270                 exec(comment[index + 1:])
271                 listDict.update(WIZARD_LIST)
272         return listDict
273     except SyntaxError:
274         raise DefineException.EnumDefineException(path)
275
276 def loadModuleInfos(path):
277     """
278     Returns the module infos and the lists infos founded in the file located in the path,
279     and the configurations infos for the module defined in this file.
280     """
281     try:
282         moduleInfos = {}
283         listInfos = {}
284         configurationsInfos = {}
285         string = open(path, "r").read()
286         commentList = re.findall(r"/\*{2}\s*([^*]*\*(?:[^/*][^*]*\*+)*)/", string)
287         commentList = [" ".join(re.findall(r"^\s*\*?\s*(.*?)\s*?(?:/{2}.*?)?$", comment, re.MULTILINE)).strip() for comment in commentList]
288         if len(commentList) > 0:
289             comment = commentList[0]
290             if comment.find("$WIZARD_MODULE") != -1:
291                 index = comment.find("$WIZARD_MODULE")
292                 if index != -1:
293                     # 14 is the length of "$WIZARD_MODULE"
294                     if len(comment[index + 14:].strip()) > 0:
295                         exec(comment[index + 1:])
296                         moduleInfos[WIZARD_MODULE["name"]] = {"depends": WIZARD_MODULE["depends"],
297                                                                 "configuration": WIZARD_MODULE["configuration"],
298                                                                 "description": "",
299                                                                 "enabled": False}
300                         index = comment.find("\\brief")
301                         if index != -1:
302                             description = comment[index + 7:]
303                             description = description[:description.find(" * ")]
304                             moduleInfos[WIZARD_MODULE["name"]]["description"] = description
305                         if "configuration" in WIZARD_MODULE.keys() and len(WIZARD_MODULE["configuration"]) > 0:
306                             configurationsInfos[WIZARD_MODULE["configuration"]] = {}
307                     listInfos.update(loadDefineLists(path))
308         return moduleInfos, listInfos, configurationsInfos
309     except SyntaxError:
310         raise DefineException.ModuleDefineException(path)
311
312 def sub(string, parameter, value):
313     """
314     Substitute the given value at the given parameter define in the given string
315     """
316     return re.sub(r"(?P<define>#define\s+" + parameter + r"\s+)([^\s]+)", r"\g<define>" + value, string)
317
318 def isInt(informations):
319     """
320     Return True if the value is a simple int.
321     """
322     if ("long" not in informatios.keys() or not informations["long"]) and ("unsigned" not in informations.keys() or informations["unsigned"]):
323         return True
324     else:
325         return False
326
327 def isLong(informations):
328     """
329     Return True if the value is a long.
330     """
331     if "long" in informations.keys() and informations["long"] and "unsigned" not in informations.keys():
332         return True
333     else:
334         return False
335
336 def isUnsigned(informations):
337     """
338     Return True if the value is an unsigned.
339     """
340     if "unsigned" in informations.keys() and informations["unsigned"] and "long" not in informations.keys():
341         return True
342     else:
343         return False
344
345 def isUnsignedLong(informations):
346     """
347     Return True if the value is an unsigned long.
348     """
349     if "unsigned" in informations.keys() and "long" in informations.keys() and informations["unsigned"] and informations["long"]:
350         return True
351     else:
352         return False