4 # This file is part of BeRTOS.
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.
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.
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
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.
29 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
33 # Author: Lorenzo Berni <duplo@develer.com>
43 import DefineException
45 from LoadException import VersionException, ToolchainException
49 from bertos_utils import (
51 isBertosDir, getTagSet, getInfos, updateConfigurationValues,
52 loadConfigurationInfos, loadDefineLists, loadModuleDefinition,
55 # Project creation functions
56 projectFileGenerator, versionFileGenerator, loadPlugin,
60 ParseError, SupportedException
64 from compatibility import updateProject
66 class BProject(object):
68 Simple class for store and retrieve project informations.
71 def __init__(self, project_file="", info_dict={}):
73 self._cached_queries = {}
77 self.loadBertosProject(project_file, info_dict)
79 #--- Load methods (methods that loads data into project) ------------------#
81 def loadBertosProject(self, project_file, info_dict):
82 project_dir = os.path.dirname(project_file)
83 project_data = pickle.loads(open(project_file, "r").read())
84 updateProject(project_data)
85 # If PROJECT_NAME is not defined it use the directory name as PROJECT_NAME
86 # NOTE: this can throw an Exception if the user has changed the directory containing the project
87 self.infos["PROJECT_NAME"] = project_data.get("PROJECT_NAME", os.path.basename(project_dir))
88 self.infos["PROJECT_PATH"] = os.path.dirname(project_file)
89 project_src_path = os.path.join(project_dir, project_data.get("PROJECT_SRC_PATH", project_data["PROJECT_NAME"]))
91 self.infos["PROJECT_SRC_PATH"] = project_src_path
94 # In projects created with older versions of the Wizard this metadata doesn't exist
95 self.infos["PROJECT_SRC_PATH"] = os.path.join(self.infos["PROJECT_PATH"], self.infos["PROJECT_NAME"])
96 self.infos["PROJECT_HW_PATH"] = os.path.join(self.infos["PROJECT_PATH"], project_data.get("PROJECT_HW_PATH", self.infos["PROJECT_PATH"]))
98 linked_sources_path = project_data["BERTOS_PATH"]
99 sources_abspath = os.path.abspath(os.path.join(project_dir, linked_sources_path))
100 project_data["BERTOS_PATH"] = sources_abspath
102 self._loadBertosSourceStuff(project_data["BERTOS_PATH"], info_dict.get("BERTOS_PATH", None))
104 self.infos["PRESET"] = project_data.get("PRESET", False)
106 # For those projects that don't have a VERSION file create a dummy one.
107 if not isBertosDir(project_dir) and not self.is_preset:
108 version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
109 open(os.path.join(project_dir, "VERSION"), "w").write(version_file.replace("$version", "").strip())
111 self.loadSourceTree()
112 self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
113 self._loadToolchainStuff(project_data["TOOLCHAIN"], info_dict.get("TOOLCHAIN", None))
114 self.infos["OUTPUT"] = project_data["OUTPUT"]
115 self.loadModuleData(True)
116 self.setEnabledModules(project_data["ENABLED_MODULES"])
118 def _loadBertosSourceStuff(self, sources_path, forced_version=None):
120 sources_path = forced_version
121 if os.path.exists(sources_path):
122 self.infos["BERTOS_PATH"] = sources_path
124 raise VersionException(self)
126 def _loadCpuStuff(self, cpu_name, cpu_frequency):
127 self.infos["CPU_NAME"] = cpu_name
128 cpu_info = self.getCpuInfos()
130 if cpu["CPU_NAME"] == cpu_name:
131 self.infos["CPU_INFOS"] = cpu
133 tag_list = getTagSet(cpu_info)
134 # Create, fill and store the dict with the tags
136 for element in tag_list:
137 tag_dict[element] = False
138 infos = self.info("CPU_INFOS")
140 if tag in infos["CPU_TAGS"] + [infos["CPU_NAME"], infos["TOOLCHAIN"]]:
143 tag_dict[tag] = False
144 self.infos["ALL_CPU_TAGS"] = tag_dict
145 self.infos["SELECTED_FREQ"] = cpu_frequency
147 def _loadToolchainStuff(self, toolchain, forced_toolchain=None):
148 toolchain = toolchain
150 toolchain = forced_toolchain
151 if os.path.exists(toolchain["path"]):
152 self.infos["TOOLCHAIN"] = toolchain
154 raise ToolchainException(self)
156 def loadProjectFromPreset(self, preset):
158 Load a project from a preset.
159 NOTE: this is a stub.
161 project_file = os.path.join(preset, "project.bertos")
162 project_data = pickle.loads(open(project_file, "r").read())
163 self.loadSourceTree()
164 self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
166 # NOTE: this is a HACK!!!
167 # TODO: find a better way to reuse loadModuleData
168 preset_project_name = project_data.get("PROJECT_NAME", os.path.basename(preset))
169 preset_prj_src_path = os.path.join(preset, project_data.get("PROJECT_SRC_PATH", os.path.join(preset, preset_project_name)))
170 preset_prj_hw_path = os.path.join(preset, project_data.get("PROJECT_HW_PATH", preset))
172 old_project_name = self.infos["PROJECT_NAME"]
173 old_project_path = self.infos["PROJECT_PATH"]
174 old_project_src_path = self.infos["PROJECT_SRC_PATH"]
175 old_project_hw_path = self.infos["PROJECT_HW_PATH"]
177 self.infos["PROJECT_NAME"] = preset_project_name
178 self.infos["PROJECT_PATH"] = preset
179 self.infos["PROJECT_SRC_PATH"] = preset_prj_src_path
180 self.infos["PROJECT_HW_PATH"] = preset_prj_hw_path
182 self.loadModuleData(True)
183 self.setEnabledModules(project_data["ENABLED_MODULES"])
185 self.infos["PROJECT_NAME"] = old_project_name
186 self.infos["PROJECT_PATH"] = old_project_path
187 self.infos["PROJECT_SRC_PATH"] = old_project_src_path
188 self.infos["PROJECT_HW_PATH"] = old_project_hw_path
189 # End of the ugly HACK!
191 self.infos["PRESET_NAME"] = preset_project_name
192 self.infos["PRESET_PATH"] = preset
193 self.infos["PRESET_SRC_PATH"] = preset_prj_src_path
194 self.infos["PRESET_HW_PATH"] = preset_prj_hw_path
196 def loadProjectPresets(self):
198 Load the default presets (into the const.PREDEFINED_BOARDS_DIR).
200 # NOTE: this method does nothing (for now).
201 preset_path = os.path.join(self.infos["BERTOS_PATH"], const.PREDEFINED_BOARDS_DIR)
202 preset_tree = {"children": []}
203 if os.path.exists(preset_path):
204 preset_tree = self._loadProjectPresetTree(preset_path)
205 self.infos["PRESET_TREE"] = preset_tree
207 def _loadProjectPresetTree(self, path):
209 _tree["info"] = self._loadPresetInfo(os.path.join(path, const.PREDEFINED_BOARD_SPEC_FILE))
210 _tree["info"]["filename"] = os.path.basename(path)
211 _tree["info"]["path"] = path
212 _tree["children"] = []
213 entries = set(os.listdir(path))
214 for entry in entries:
215 _path = os.path.join(path, entry)
216 if os.path.isdir(_path):
217 sub_entries = set(os.listdir(_path))
218 if const.PREDEFINED_BOARD_SPEC_FILE in sub_entries:
219 _tree["children"].append(self._loadProjectPresetTree(_path))
220 # Add into the info dict the dir type (dir/project)
221 if _tree["children"]:
222 _tree["info"]["type"] = "dir"
224 _tree["info"]["type"] = "project"
227 def _loadPresetInfo(self, preset_spec_file):
229 execfile(preset_spec_file, {}, D)
232 def loadModuleData(self, edit=False):
233 module_info_dict = {}
235 configuration_info_dict = {}
237 for filename, path in self.findDefinitions("*.h") + self.findDefinitions("*.c") + self.findDefinitions("*.s") + self.findDefinitions("*.S"):
238 comment_list = getCommentList(open(path + "/" + filename, "r").read())
239 if len(comment_list) > 0:
241 configuration_info = {}
243 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
244 except ParseError, err:
245 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
246 for module, information in module_dict.items():
247 if "depends" not in information:
248 information["depends"] = ()
249 information["depends"] += (filename.split(".")[0],)
250 information["category"] = os.path.basename(path)
252 # Hack to remove 'bertos/' from the configuration file path.
254 # The new module information format substitute paths like 'bertos/cfg/config_file.h'
255 # with the relative path into the bertos directory ('cfg/config_file.h')
256 information["configuration"] = information["configuration"].replace("bertos/", "")
257 information["hw"] = [hw.replace("bertos/", "") for hw in information["hw"]]
259 if "configuration" in information and len(information["configuration"]):
260 configuration = module_dict[module]["configuration"]
262 cfg_file_path = os.path.join(self.bertos_srcdir, configuration)
263 configuration_info[configuration] = loadConfigurationInfos(cfg_file_path)
264 except ParseError, err:
265 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
268 path = self.infos["PROJECT_SRC_PATH"]
269 cfg_file_path = os.path.join(path, configuration)
270 configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], loadConfigurationInfos(cfg_file_path))
271 except ParseError, err:
272 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
274 # The wizard can't find the file, use the default configuration
276 module_info_dict.update(module_dict)
277 configuration_info_dict.update(configuration_info)
280 list_dict = loadDefineLists(comment_list[1:])
281 list_info_dict.update(list_dict)
282 except ParseError, err:
283 raise DefineException.EnumDefineException(path, err.line_number, err.line)
284 for tag in self.infos["CPU_INFOS"]["CPU_TAGS"]:
285 for filename, path in self.findDefinitions("*_" + tag + ".h"):
286 comment_list = getCommentList(open(path + "/" + filename, "r").read())
287 list_info_dict.update(loadDefineLists(comment_list))
288 self.infos["MODULES"] = module_info_dict
289 self.infos["LISTS"] = list_info_dict
290 self.infos["CONFIGURATIONS"] = configuration_info_dict
291 self.infos["FILES"] = file_dict
293 def loadSourceTree(self):
295 Index BeRTOS source and load it in memory.
297 # Index only the BERTOS_PATH/bertos content
298 bertos_sources_dir = os.path.join(self.info("BERTOS_PATH"), "bertos")
300 if os.path.exists(bertos_sources_dir):
301 for element in os.walk(bertos_sources_dir):
303 file_dict[f] = file_dict.get(f, []) + [element[0]]
304 self.infos["FILE_DICT"] = file_dict
306 def reloadCpuInfo(self):
307 for cpu_info in self.getCpuInfos():
308 if cpu_info["CPU_NAME"] == self.infos["CPU_NAME"]:
309 self.infos["CPU_INFOS"] = cpu_info
311 #-------------------------------------------------------------------------#
313 def createBertosProject(self):
314 # NOTE: Temporary hack.
316 self._editBertosProject()
318 if not self.from_preset:
319 self._newCustomBertosProject()
321 self._newBertosProjectFromPreset()
323 def _newBertosProject(self):
324 for directory in (self.maindir, self.srcdir, self.prjdir, self.cfgdir, self.hwdir):
325 self._createDirectory(directory)
326 # Write the project file
327 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
329 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
330 # Destination makefile
331 self._writeMakefile()
333 self._copySources(self.bertos_maindir, self.srcdir)
334 # Set properly the autoenabled parameters
335 self._setupAutoenabledParameters()
336 # Copy all the configuration files
337 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
338 # Destination wizard mk file
339 self._writeWizardMkFile()
341 def _newCustomBertosProject(self):
342 # Create/write/copy the common things
343 self._newBertosProject()
344 # Copy the clean hw files
345 self._createDirectory(self.hwdir)
346 # Copy all the hw files
347 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
348 # Destination user mk file
349 self._writeUserMkFile()
350 # Destination main.c file
351 self._writeMainFile(self.prjdir + "/main.c")
352 # Create project files for selected plugins
353 self._createProjectFiles()
355 def _newBertosProjectFromPreset(self):
356 # Create/write/copy the common things
357 self._newBertosProject()
359 # Copy all the files and dirs except cfg/hw/*.mk
360 self._writeCustomSrcFiles()
363 self._writeAllPresetHwFiles(self.src_hwdir, self.hwdir)
365 # Copyt the new *_user.mk file
366 self._writeUserMkFileFromPreset()
368 if self.infos["EMPTY_MAIN"]:
369 # Create and empty main.c file only if the user check the box
370 self._writeMainFile(self.prjdir + "/main.c")
372 # Create project files for selected plugins
373 self._createProjectFiles()
375 def _editBertosProject(self):
376 # Write the project file
377 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
378 if not self.is_preset:
379 # Generate this files only if the project isn't a preset
381 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
382 # Destination makefile
383 self._writeMakefile()
385 self._mergeSources(self.bertos_maindir, self.srcdir, self.old_srcdir)
386 # Copy all the hw files
387 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
388 # Destination wizard mk file
389 self._writeWizardMkFile()
390 # Set properly the autoenabled parameters
391 self._setupAutoenabledParameters()
392 # Copy all the configuration files
393 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
394 if not self.is_preset:
395 # Create project files for selected plugins only if the project isn't a preset
396 self._createProjectFiles()
398 def _createProjectFiles(self):
399 # Files for selected plugins
401 for plugin in self.infos["OUTPUT"]:
402 module = loadPlugin(plugin)
403 relevants_files[plugin] = module.createProject(self)
404 self.infos["RELEVANT_FILES"] = relevants_files
406 def _writeVersionFile(self, filename):
407 if not self.edit or self.old_srcdir:
408 version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
409 open(filename, "w").write(versionFileGenerator(self, version_file))
411 def _writeProjectFile(self, filename):
412 f = open(filename, "w")
413 f.write(projectFileGenerator(self))
416 def _writeMakefile(self):
417 bertos_utils.makefileGenerator(self)
419 def _writeUserMkFile(self):
420 bertos_utils.userMkGenerator(self)
422 def _writeUserMkFileFromPreset(self):
423 bertos_utils.userMkGeneratorFromPreset(self)
425 def _writeWizardMkFile(self):
426 bertos_utils.mkGenerator(self)
428 def _writeMainFile(self, filename):
429 main = open(os.path.join(const.DATA_DIR, "srctemplates/main.c"), "r").read()
430 open(filename, "w").write(main)
432 def _writeHwFiles(self, source_dir, destination_dir):
433 for module, information in self.infos["MODULES"].items():
434 for hwfile in information["hw"]:
437 string = open(source_dir + "/" + hwfile, "r").read()
438 hwfile_path = destination_dir + "/" + os.path.basename(hwfile)
439 if not self.edit or not os.path.exists(hwfile_path):
440 # If not in editing mode it copies all the hw files. If in
441 # editing mode it copies only the files that don't exist yet
442 open(os.path.join(destination_dir,os.path.basename(hwfile)), "w").write(string)
444 def _writeAllPresetHwFiles(self, source_dir, destination_dir):
446 Copy all but directories contained into the preset hw directory.
447 It's needed because some presets need custom hw files not defined with
448 Wizard directives into modules...
450 source_dir = os.path.join(source_dir, "hw")
451 for f in os.listdir(source_dir):
452 abspath = os.path.join(source_dir, f)
453 if not os.path.isdir(abspath):
454 # Exlude directories from the copy!
455 hw_file = open(os.path.join(source_dir, f), 'r').read()
456 open(os.path.join(destination_dir, f), 'w').write(hw_file)
458 def _writeCfgFiles(self, source_dir, destination_dir):
459 for configuration, information in self.infos["CONFIGURATIONS"].items():
460 string = open(source_dir + "/" + configuration, "r").read()
461 for start, parameter in information["paramlist"]:
462 infos = information[parameter]
463 value = infos["value"]
464 if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
466 if "long" in infos["informations"] and infos["informations"]["long"]:
468 string = sub(string, parameter, value)
469 f = open(os.path.join(destination_dir, os.path.basename(configuration)), "w")
473 def _writeCustomSrcFiles(self):
474 origin = self.infos["PRESET_SRC_PATH"]
475 # Files to be ignored (all project files, cfg dir, wizard mk file, all global ignored dirs)
476 project_related_stuff = (
479 self.infos["PRESET_NAME"] + ".mk",
480 self.infos["PRESET_NAME"] + "_user.mk",
482 self.infos["PRESET_NAME"] + ".project",
483 self.infos["PRESET_NAME"] + ".workspace",
484 ) + const.IGNORE_LIST
485 for element in os.listdir(origin):
486 if element not in project_related_stuff:
487 full_path = os.path.join(origin, element)
488 if os.path.isdir(full_path):
489 copytree.copytree(full_path, os.path.join(self.prjdir, element), ignore_list=const.IGNORE_LIST)
491 shutil.copy(full_path, self.prjdir)
493 def _setupAutoenabledParameters(self):
494 for module, information in self.infos["MODULES"].items():
495 if "configuration" in information and information["configuration"] != "":
496 configurations = self.infos["CONFIGURATIONS"]
497 configuration = configurations[information["configuration"]]
498 for start, parameter in configuration["paramlist"]:
499 if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
500 configuration[parameter]["value"] = "1" if information["enabled"] else "0"
501 self.infos["CONFIGURATIONS"] = configurations
503 # Project related properties
506 return self.infos.get("PROJECT_PATH", None)
511 return os.path.join(self.maindir, "bertos")
517 return self.infos.get("PROJECT_SRC_PATH", None)
522 return os.path.join(self.prjdir, "hw")
529 return os.path.join(self.prjdir, "cfg")
534 def old_srcdir(self):
535 return self.infos.get("OLD_BERTOS_PATH", None)
537 # BeRTOS sources related properties
539 def bertos_maindir(self):
540 return self.infos.get("BERTOS_PATH", None)
543 def bertos_srcdir(self):
544 if self.bertos_maindir:
545 return os.path.join(self.bertos_maindir, "bertos")
552 return os.path.join(self.infos["PRESET_PATH"], self.infos["PRESET_HW_PATH"])
554 return self.bertos_maindir
557 def from_preset(self):
558 return self.infos.get("PROJECT_FROM_PRESET", False)
562 return self.infos.get("PRESET", False)
564 def _createDirectory(self, directory):
567 if os.path.isdir(directory):
568 shutil.rmtree(directory, True)
569 os.makedirs(directory)
571 def _copySources(self, origin, destination):
572 # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
573 shutil.rmtree(destination, True)
574 copytree.copytree(origin + "/bertos", destination, ignore_list=const.IGNORE_LIST)
576 def _mergeSources(self, origin, destination, old_sources_dir):
578 mergeSources(destination, origin, old_sources_dir)
580 def setInfo(self, key, value):
582 Store the given value with the name key.
584 self.infos[key] = value
586 def info(self, key, default=None):
588 Retrieve the value associated with the name key.
590 if key in self.infos:
591 return copy.deepcopy(self.infos[key])
594 def getCpuInfos(self):
596 for definition in self.findDefinitions(const.CPU_DEFINITION):
597 cpuInfos.append(getInfos(definition))
600 def searchFiles(self, filename):
601 file_dict = self.infos["FILE_DICT"]
602 return [(filename, dirname) for dirname in file_dict.get(filename, [])]
604 def findDefinitions(self, ftype):
605 # Maintain a cache for every scanned BERTOS_PATH
606 definitions_dict = self._cached_queries.get(self.infos["BERTOS_PATH"], {})
607 definitions = definitions_dict.get(ftype, None)
608 if definitions is not None:
610 file_dict = self.infos["FILE_DICT"]
612 for filename in file_dict:
613 if fnmatch.fnmatch(filename, ftype):
614 definitions += [(filename, dirname) for dirname in file_dict.get(filename, [])]
616 # If no cache for the current BERTOS_PATH create an empty one
617 if not definitions_dict:
618 self._cached_queries[self.infos["BERTOS_PATH"]] = {}
619 # Fill the empty cache with the result
620 self._cached_queries[self.infos["BERTOS_PATH"]][ftype] = definitions
623 def setEnabledModules(self, enabled_modules):
624 modules = self.infos["MODULES"]
626 for module, information in modules.items():
627 information["enabled"] = module in enabled_modules
628 if information["enabled"]:
629 for dependency in information["depends"]:
630 if not dependency in modules:
631 files[dependency] = files.get(dependency, 0) + 1
632 self.infos["MODULES"] = modules
633 self.infos["FILES"] = files
636 return "<BProject:instance %d>%s" %(id(self), repr(self.infos))