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"][_path] = 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):
230 execfile(preset_spec_file, {}, D)
235 def loadModuleData(self, edit=False):
236 module_info_dict = {}
238 configuration_info_dict = {}
240 for filename, path in self.findDefinitions("*.h") + self.findDefinitions("*.c") + self.findDefinitions("*.s") + self.findDefinitions("*.S"):
241 comment_list = getCommentList(open(path + "/" + filename, "r").read())
242 if len(comment_list) > 0:
244 configuration_info = {}
246 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
247 except ParseError, err:
248 raise DefineException.ModuleDefineException(path, err.line_number, err.line)
249 for module, information in module_dict.items():
250 if "depends" not in information:
251 information["depends"] = ()
252 information["depends"] += (filename.split(".")[0],)
253 information["category"] = os.path.basename(path)
255 # Hack to remove 'bertos/' from the configuration file path.
257 # The new module information format substitute paths like 'bertos/cfg/config_file.h'
258 # with the relative path into the bertos directory ('cfg/config_file.h')
259 information["configuration"] = information["configuration"].replace("bertos/", "")
260 information["hw"] = [hw.replace("bertos/", "") for hw in information["hw"]]
262 if "configuration" in information and len(information["configuration"]):
263 configuration = module_dict[module]["configuration"]
265 cfg_file_path = os.path.join(self.bertos_srcdir, configuration)
266 configuration_info[configuration] = loadConfigurationInfos(cfg_file_path)
267 except ParseError, err:
268 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
271 path = self.infos["PROJECT_SRC_PATH"]
272 cfg_file_path = os.path.join(path, configuration)
273 configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], loadConfigurationInfos(cfg_file_path))
274 except ParseError, err:
275 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
277 # The wizard can't find the file, use the default configuration
279 module_info_dict.update(module_dict)
280 configuration_info_dict.update(configuration_info)
283 list_dict = loadDefineLists(comment_list[1:])
284 list_info_dict.update(list_dict)
285 except ParseError, err:
286 raise DefineException.EnumDefineException(path, err.line_number, err.line)
287 for tag in self.infos["CPU_INFOS"]["CPU_TAGS"]:
288 for filename, path in self.findDefinitions("*_" + tag + ".h"):
289 comment_list = getCommentList(open(path + "/" + filename, "r").read())
290 list_info_dict.update(loadDefineLists(comment_list))
291 self.infos["MODULES"] = module_info_dict
292 self.infos["LISTS"] = list_info_dict
293 self.infos["CONFIGURATIONS"] = configuration_info_dict
294 self.infos["FILES"] = file_dict
296 def loadSourceTree(self):
298 Index BeRTOS source and load it in memory.
300 # Index only the BERTOS_PATH/bertos content
301 bertos_sources_dir = os.path.join(self.info("BERTOS_PATH"), "bertos")
303 if os.path.exists(bertos_sources_dir):
304 for element in os.walk(bertos_sources_dir):
306 file_dict[f] = file_dict.get(f, []) + [element[0]]
307 self.infos["FILE_DICT"] = file_dict
309 def reloadCpuInfo(self):
310 for cpu_info in self.getCpuInfos():
311 if cpu_info["CPU_NAME"] == self.infos["CPU_NAME"]:
312 self.infos["CPU_INFOS"] = cpu_info
314 #-------------------------------------------------------------------------#
316 def createBertosProject(self):
317 # NOTE: Temporary hack.
319 self._editBertosProject()
321 if not self.from_preset:
322 self._newCustomBertosProject()
324 self._newBertosProjectFromPreset()
326 def _newBertosProject(self):
327 for directory in (self.maindir, self.srcdir, self.prjdir, self.cfgdir, self.hwdir):
328 self._createDirectory(directory)
329 # Write the project file
330 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
332 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
333 # Destination makefile
334 self._writeMakefile()
336 self._copySources(self.bertos_maindir, self.srcdir)
337 # Set properly the autoenabled parameters
338 self._setupAutoenabledParameters()
339 # Copy all the configuration files
340 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
341 # Destination wizard mk file
342 self._writeWizardMkFile()
344 def _newCustomBertosProject(self):
345 # Create/write/copy the common things
346 self._newBertosProject()
347 # Copy the clean hw files
348 self._createDirectory(self.hwdir)
349 # Copy all the hw files
350 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
351 # Destination user mk file
352 self._writeUserMkFile()
353 # Destination main.c file
354 self._writeMainFile(self.prjdir + "/main.c")
355 # Create project files for selected plugins
356 self._createProjectFiles()
358 def _newBertosProjectFromPreset(self):
359 # Create/write/copy the common things
360 self._newBertosProject()
362 # Copy all the files and dirs except cfg/hw/*.mk
363 self._writeCustomSrcFiles()
366 self._writeAllPresetHwFiles(self.src_hwdir, self.hwdir)
368 # Copyt the new *_user.mk file
369 self._writeUserMkFileFromPreset()
371 if self.infos["EMPTY_MAIN"]:
372 # Create and empty main.c file only if the user check the box
373 self._writeMainFile(self.prjdir + "/main.c")
375 # Create project files for selected plugins
376 self._createProjectFiles()
378 def _editBertosProject(self):
379 # Write the project file
380 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
381 if not self.is_preset:
382 # Generate this files only if the project isn't a preset
384 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
385 # Destination makefile
386 self._writeMakefile()
388 self._mergeSources(self.bertos_maindir, self.srcdir, self.old_srcdir)
389 # Copy all the hw files
390 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
391 # Destination wizard mk file
392 self._writeWizardMkFile()
393 # Set properly the autoenabled parameters
394 self._setupAutoenabledParameters()
395 # Copy all the configuration files
396 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
397 if not self.is_preset:
398 # Create project files for selected plugins only if the project isn't a preset
399 self._createProjectFiles()
401 def _createProjectFiles(self):
402 # Files for selected plugins
404 for plugin in self.infos["OUTPUT"]:
405 module = loadPlugin(plugin)
406 relevants_files[plugin] = module.createProject(self)
407 self.infos["RELEVANT_FILES"] = relevants_files
409 def _writeVersionFile(self, filename):
410 if not self.edit or self.old_srcdir:
411 version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
412 open(filename, "w").write(versionFileGenerator(self, version_file))
414 def _writeProjectFile(self, filename):
415 f = open(filename, "w")
416 f.write(projectFileGenerator(self))
419 def _writeMakefile(self):
420 bertos_utils.makefileGenerator(self)
422 def _writeUserMkFile(self):
423 bertos_utils.userMkGenerator(self)
425 def _writeUserMkFileFromPreset(self):
426 bertos_utils.userMkGeneratorFromPreset(self)
428 def _writeWizardMkFile(self):
429 bertos_utils.mkGenerator(self)
431 def _writeMainFile(self, filename):
432 main = open(os.path.join(const.DATA_DIR, "srctemplates/main.c"), "r").read()
433 open(filename, "w").write(main)
435 def _writeHwFiles(self, source_dir, destination_dir):
436 for module, information in self.infos["MODULES"].items():
437 for hwfile in information["hw"]:
440 string = open(source_dir + "/" + hwfile, "r").read()
441 hwfile_path = destination_dir + "/" + os.path.basename(hwfile)
442 if not self.edit or not os.path.exists(hwfile_path):
443 # If not in editing mode it copies all the hw files. If in
444 # editing mode it copies only the files that don't exist yet
445 open(os.path.join(destination_dir,os.path.basename(hwfile)), "w").write(string)
447 def _writeAllPresetHwFiles(self, source_dir, destination_dir):
449 Copy all but directories contained into the preset hw directory.
450 It's needed because some presets need custom hw files not defined with
451 Wizard directives into modules...
453 source_dir = os.path.join(source_dir, "hw")
454 for f in os.listdir(source_dir):
455 abspath = os.path.join(source_dir, f)
456 if not os.path.isdir(abspath):
457 # Exlude directories from the copy!
458 hw_file = open(os.path.join(source_dir, f), 'r').read()
459 open(os.path.join(destination_dir, f), 'w').write(hw_file)
461 def _writeCfgFiles(self, source_dir, destination_dir):
462 for configuration, information in self.infos["CONFIGURATIONS"].items():
463 string = open(source_dir + "/" + configuration, "r").read()
464 for start, parameter in information["paramlist"]:
465 infos = information[parameter]
466 value = infos["value"]
467 if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
469 if "long" in infos["informations"] and infos["informations"]["long"]:
471 string = sub(string, parameter, value)
472 f = open(os.path.join(destination_dir, os.path.basename(configuration)), "w")
476 def _writeCustomSrcFiles(self):
477 origin = self.infos["PRESET_SRC_PATH"]
478 # Files to be ignored (all project files, cfg dir, wizard mk file, all global ignored dirs)
479 project_related_stuff = (
482 self.infos["PRESET_NAME"] + ".mk",
483 self.infos["PRESET_NAME"] + "_user.mk",
485 self.infos["PRESET_NAME"] + ".project",
486 self.infos["PRESET_NAME"] + ".workspace",
487 ) + const.IGNORE_LIST
488 for element in os.listdir(origin):
489 if element not in project_related_stuff:
490 full_path = os.path.join(origin, element)
491 if os.path.isdir(full_path):
492 copytree.copytree(full_path, os.path.join(self.prjdir, element), ignore_list=const.IGNORE_LIST)
494 shutil.copy(full_path, self.prjdir)
496 def _setupAutoenabledParameters(self):
497 for module, information in self.infos["MODULES"].items():
498 if "configuration" in information and information["configuration"] != "":
499 configurations = self.infos["CONFIGURATIONS"]
500 configuration = configurations[information["configuration"]]
501 for start, parameter in configuration["paramlist"]:
502 if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
503 configuration[parameter]["value"] = "1" if information["enabled"] else "0"
504 self.infos["CONFIGURATIONS"] = configurations
506 # Project related properties
509 return self.infos.get("PROJECT_PATH", None)
514 return os.path.join(self.maindir, "bertos")
520 return self.infos.get("PROJECT_SRC_PATH", None)
525 return os.path.join(self.prjdir, "hw")
532 return os.path.join(self.prjdir, "cfg")
537 def old_srcdir(self):
538 return self.infos.get("OLD_BERTOS_PATH", None)
540 # BeRTOS sources related properties
542 def bertos_maindir(self):
543 return self.infos.get("BERTOS_PATH", None)
546 def bertos_srcdir(self):
547 if self.bertos_maindir:
548 return os.path.join(self.bertos_maindir, "bertos")
555 return os.path.join(self.infos["PRESET_PATH"], self.infos["PRESET_HW_PATH"])
557 return self.bertos_maindir
560 def from_preset(self):
561 return self.infos.get("PROJECT_FROM_PRESET", False)
565 return self.infos.get("PRESET", False)
567 def _createDirectory(self, directory):
570 if os.path.isdir(directory):
571 shutil.rmtree(directory, True)
572 os.makedirs(directory)
574 def _copySources(self, origin, destination):
575 # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
576 shutil.rmtree(destination, True)
577 copytree.copytree(origin + "/bertos", destination, ignore_list=const.IGNORE_LIST)
579 def _mergeSources(self, origin, destination, old_sources_dir):
581 mergeSources(destination, origin, old_sources_dir)
583 def setInfo(self, key, value):
585 Store the given value with the name key.
587 self.infos[key] = value
589 def info(self, key, default=None):
591 Retrieve the value associated with the name key.
593 if key in self.infos:
594 return copy.deepcopy(self.infos[key])
597 def getCpuInfos(self):
599 for definition in self.findDefinitions(const.CPU_DEFINITION):
600 cpuInfos.append(getInfos(definition))
603 def searchFiles(self, filename):
604 file_dict = self.infos["FILE_DICT"]
605 return [(filename, dirname) for dirname in file_dict.get(filename, [])]
607 def findDefinitions(self, ftype):
608 # Maintain a cache for every scanned BERTOS_PATH
609 definitions_dict = self._cached_queries.get(self.infos["BERTOS_PATH"], {})
610 definitions = definitions_dict.get(ftype, None)
611 if definitions is not None:
613 file_dict = self.infos["FILE_DICT"]
615 for filename in file_dict:
616 if fnmatch.fnmatch(filename, ftype):
617 definitions += [(filename, dirname) for dirname in file_dict.get(filename, [])]
619 # If no cache for the current BERTOS_PATH create an empty one
620 if not definitions_dict:
621 self._cached_queries[self.infos["BERTOS_PATH"]] = {}
622 # Fill the empty cache with the result
623 self._cached_queries[self.infos["BERTOS_PATH"]][ftype] = definitions
626 def setEnabledModules(self, enabled_modules):
627 modules = self.infos["MODULES"]
629 for module, information in modules.items():
630 information["enabled"] = module in enabled_modules
631 if information["enabled"]:
632 for dependency in information["depends"]:
633 if not dependency in modules:
634 files[dependency] = files.get(dependency, 0) + 1
635 self.infos["MODULES"] = modules
636 self.infos["FILES"] = files
639 return "<BProject:instance %d>%s" %(id(self), repr(self.infos))