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/)
32 # Author: Lorenzo Berni <duplo@develer.com>
42 import DefineException
44 from LoadException import VersionException, ToolchainException
48 from bertos_utils import (
50 isBertosDir, getTagSet, getInfos, updateConfigurationValues,
51 loadConfigurationInfos, loadDefineLists, loadModuleDefinition,
54 # Project creation functions
55 projectFileGenerator, versionFileGenerator, loadPlugin,
59 ParseError, SupportedException
63 from compatibility import updateProject
65 class BProject(object):
67 Simple class for store and retrieve project informations.
70 def __init__(self, project_file="", info_dict={}):
72 self._cached_queries = {}
76 self.loadBertosProject(project_file, info_dict)
78 #--- Load methods (methods that loads data into project) ------------------#
80 def loadBertosProject(self, project_file, info_dict):
81 project_dir = os.path.dirname(project_file)
82 project_data = pickle.loads(open(project_file, "r").read())
83 updateProject(project_data)
84 # If PROJECT_NAME is not defined it use the directory name as PROJECT_NAME
85 # NOTE: this can throw an Exception if the user has changed the directory containing the project
86 self.infos["PROJECT_NAME"] = project_data.get("PROJECT_NAME", os.path.basename(project_dir))
87 self.infos["PROJECT_PATH"] = os.path.dirname(project_file)
88 project_src_path = os.path.join(project_dir, project_data.get("PROJECT_SRC_PATH", project_data["PROJECT_NAME"]))
90 self.infos["PROJECT_SRC_PATH"] = project_src_path
93 # In projects created with older versions of the Wizard this metadata doesn't exist
94 self.infos["PROJECT_SRC_PATH"] = os.path.join(self.infos["PROJECT_PATH"], self.infos["PROJECT_NAME"])
95 self.infos["PROJECT_HW_PATH"] = os.path.join(self.infos["PROJECT_PATH"], project_data.get("PROJECT_HW_PATH", self.infos["PROJECT_PATH"]))
97 self.infos["PROJECT_SRC_PATH_FROM_MAKEFILE"] = project_data.get("PROJECT_SRC_PATH_FROM_MAKEFILE")
98 self.infos["PROJECT_HW_PATH_FROM_MAKEFILE"] = project_data.get("PROJECT_HW_PATH_FROM_MAKEFILE")
100 linked_sources_path = project_data["BERTOS_PATH"]
101 sources_abspath = os.path.abspath(os.path.join(project_dir, linked_sources_path))
102 project_data["BERTOS_PATH"] = sources_abspath
104 self._loadBertosSourceStuff(project_data["BERTOS_PATH"], info_dict.get("BERTOS_PATH", None))
106 self.infos["PRESET"] = project_data.get("PRESET", False)
108 # For those projects that don't have a VERSION file create a dummy one.
109 if not isBertosDir(project_dir) and not self.is_preset:
110 version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
111 open(os.path.join(project_dir, "VERSION"), "w").write(version_file.replace("$version", "").strip())
113 self.loadSourceTree()
114 self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
115 self._loadToolchainStuff(project_data["TOOLCHAIN"], info_dict.get("TOOLCHAIN", None))
116 self.infos["OUTPUT"] = project_data["OUTPUT"]
117 self.loadModuleData(True)
118 self.setEnabledModules(project_data["ENABLED_MODULES"])
120 def _loadBertosSourceStuff(self, sources_path, forced_version=None):
122 sources_path = forced_version
123 if os.path.exists(sources_path):
124 self.infos["BERTOS_PATH"] = sources_path
126 raise VersionException(self)
128 def _loadCpuStuff(self, cpu_name, cpu_frequency):
129 self.infos["CPU_NAME"] = cpu_name
130 cpu_info = self.getCpuInfos()
132 if cpu["CPU_NAME"] == cpu_name:
133 self.infos["CPU_INFOS"] = cpu
135 tag_list = getTagSet(cpu_info)
136 # Create, fill and store the dict with the tags
138 for element in tag_list:
139 tag_dict[element] = False
140 infos = self.info("CPU_INFOS")
142 if tag in infos["CPU_TAGS"] + [infos["CPU_NAME"], infos["TOOLCHAIN"]]:
145 tag_dict[tag] = False
146 self.infos["ALL_CPU_TAGS"] = tag_dict
147 self.infos["SELECTED_FREQ"] = cpu_frequency
149 def _loadToolchainStuff(self, toolchain, forced_toolchain=None):
150 toolchain = toolchain
152 toolchain = forced_toolchain
153 if os.path.exists(toolchain["path"]) or bertos_utils.findInPath(toolchain["path"]):
154 self.infos["TOOLCHAIN"] = toolchain
156 raise ToolchainException(self)
158 def loadProjectFromPreset(self, preset):
160 Load a project from a preset.
161 NOTE: this is a stub.
163 project_file = os.path.join(preset, "project.bertos")
164 project_data = pickle.loads(open(project_file, "r").read())
165 self.loadSourceTree()
166 self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
168 # NOTE: this is a HACK!!!
169 # TODO: find a better way to reuse loadModuleData
170 preset_project_name = project_data.get("PROJECT_NAME", os.path.basename(preset))
171 preset_prj_src_path = os.path.join(preset, project_data.get("PROJECT_SRC_PATH", os.path.join(preset, preset_project_name)))
172 preset_prj_hw_path = os.path.join(preset, project_data.get("PROJECT_HW_PATH", preset))
174 old_project_name = self.infos["PROJECT_NAME"]
175 old_project_path = self.infos["PROJECT_PATH"]
176 old_project_src_path = self.infos["PROJECT_SRC_PATH"]
177 old_project_hw_path = self.infos["PROJECT_HW_PATH"]
179 self.infos["PROJECT_NAME"] = preset_project_name
180 self.infos["PROJECT_PATH"] = preset
181 self.infos["PROJECT_SRC_PATH"] = preset_prj_src_path
182 self.infos["PROJECT_HW_PATH"] = preset_prj_hw_path
184 self.loadModuleData(True)
185 self.setEnabledModules(project_data["ENABLED_MODULES"])
187 self.infos["PROJECT_NAME"] = old_project_name
188 self.infos["PROJECT_PATH"] = old_project_path
189 self.infos["PROJECT_SRC_PATH"] = old_project_src_path
190 self.infos["PROJECT_HW_PATH"] = old_project_hw_path
191 # End of the ugly HACK!
193 self.infos["PRESET_NAME"] = preset_project_name
194 self.infos["PRESET_PATH"] = preset
195 self.infos["PRESET_SRC_PATH"] = preset_prj_src_path
196 self.infos["PRESET_HW_PATH"] = preset_prj_hw_path
198 def loadProjectPresets(self):
200 Load the default presets (into the const.PREDEFINED_BOARDS_DIR).
202 # NOTE: this method does nothing (for now).
203 preset_path = os.path.join(self.infos["BERTOS_PATH"], const.PREDEFINED_BOARDS_DIR)
204 preset_tree = {"children": []}
205 if os.path.exists(preset_path):
206 preset_tree = self._loadProjectPresetTree(preset_path)
207 self.infos["PRESET_TREE"] = preset_tree
209 def _loadProjectPresetTree(self, path):
211 _tree["info"] = self._loadPresetInfo(os.path.join(path, const.PREDEFINED_BOARD_SPEC_FILE))
212 _tree["info"]["filename"] = os.path.basename(path)
213 _tree["info"]["path"] = path
214 _tree["children"] = {}
215 entries = set(os.listdir(path))
216 for entry in entries:
217 _path = os.path.join(path, entry)
218 if os.path.isdir(_path):
219 sub_entries = set(os.listdir(_path))
220 if const.PREDEFINED_BOARD_SPEC_FILE in sub_entries:
221 _tree["children"][_path] = self._loadProjectPresetTree(_path)
222 # Add into the info dict the dir type (dir/project)
223 if _tree["children"]:
224 _tree["info"]["type"] = "dir"
226 _tree["info"]["type"] = "project"
229 def _loadPresetInfo(self, preset_spec_file):
232 execfile(preset_spec_file, {}, D)
237 def loadModuleData(self, edit=False):
238 module_info_dict = {}
240 configuration_info_dict = {}
242 for filename, path in self.findDefinitions("*.h") + self.findDefinitions("*.c") + self.findDefinitions("*.s") + self.findDefinitions("*.S"):
243 comment_list = getCommentList(open(path + "/" + filename, "r").read())
244 if len(comment_list) > 0:
246 configuration_info = {}
248 to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
249 except ParseError, err:
250 raise DefineException.ModuleDefineException(os.path.join(path, filename), err.line_number, err.line)
251 for module, information in module_dict.items():
252 if "depends" not in information:
253 information["depends"] = ()
254 information["depends"] += (filename.split(".")[0],)
255 information["category"] = os.path.basename(path)
257 # Hack to remove 'bertos/' from the configuration file path.
259 # The new module information format substitute paths like 'bertos/cfg/config_file.h'
260 # with the relative path into the bertos directory ('cfg/config_file.h')
261 information["configuration"] = information["configuration"].replace("bertos/", "")
262 information["hw"] = [hw.replace("bertos/", "") for hw in information["hw"]]
264 if "configuration" in information and len(information["configuration"]):
265 configuration = module_dict[module]["configuration"]
267 cfg_file_path = os.path.join(self.bertos_srcdir, configuration)
268 configuration_info[configuration] = loadConfigurationInfos(cfg_file_path)
269 except ParseError, err:
270 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
273 path = self.infos["PROJECT_SRC_PATH"]
274 cfg_file_path = os.path.join(path, configuration)
275 configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], loadConfigurationInfos(cfg_file_path))
276 except ParseError, err:
277 raise DefineException.ConfigurationDefineException(cfg_file_path, err.line_number, err.line)
279 # The wizard can't find the file, use the default configuration
281 module_info_dict.update(module_dict)
282 configuration_info_dict.update(configuration_info)
285 list_dict = loadDefineLists(comment_list[1:])
286 list_info_dict.update(list_dict)
287 except ParseError, err:
288 raise DefineException.EnumDefineException(os.path.join(path, filename), err.line_number, err.line)
289 for tag in self.infos["CPU_INFOS"]["CPU_TAGS"]:
290 for filename, path in self.findDefinitions("*_" + tag + ".h"):
291 comment_list = getCommentList(open(path + "/" + filename, "r").read())
292 list_info_dict.update(loadDefineLists(comment_list))
293 self.infos["MODULES"] = module_info_dict
294 self.infos["LISTS"] = list_info_dict
295 self.infos["CONFIGURATIONS"] = configuration_info_dict
296 self.infos["FILES"] = file_dict
298 def loadSourceTree(self):
300 Index BeRTOS source and load it in memory.
302 # Index only the BERTOS_PATH/bertos content
303 bertos_sources_dir = os.path.join(self.info("BERTOS_PATH"), "bertos")
305 if os.path.exists(bertos_sources_dir):
306 for element in os.walk(bertos_sources_dir):
308 file_dict[f] = file_dict.get(f, []) + [element[0]]
309 self.infos["FILE_DICT"] = file_dict
311 def reloadCpuInfo(self):
312 for cpu_info in self.getCpuInfos():
313 if cpu_info["CPU_NAME"] == self.infos["CPU_NAME"]:
314 self.infos["CPU_INFOS"] = cpu_info
316 #-------------------------------------------------------------------------#
318 def createBertosProject(self):
319 # NOTE: Temporary hack.
321 self._editBertosProject()
323 if not self.from_preset:
324 self._newCustomBertosProject()
326 self._newBertosProjectFromPreset()
328 def _newBertosProject(self):
329 for directory in (self.maindir, self.srcdir, self.prjdir, self.cfgdir, self.hwdir):
330 self._createDirectory(directory)
331 # Write the project file
332 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
334 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
335 # Destination makefile
336 self._writeMakefile()
338 self._copySources(self.bertos_maindir, self.srcdir)
339 # Set properly the autoenabled parameters
340 self._setupAutoenabledParameters()
341 # Copy all the configuration files
342 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
343 # Destination wizard mk file
344 self._writeWizardMkFile()
346 def _newCustomBertosProject(self):
347 # Create/write/copy the common things
348 self._newBertosProject()
349 # Copy the clean hw files
350 self._createDirectory(self.hwdir)
351 # Copy all the hw files
352 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
353 # Destination user mk file
354 self._writeUserMkFile()
355 # Destination main.c file
356 self._writeMainFile(self.prjdir + "/main.c")
357 # Create project files for selected plugins
358 self._createProjectFiles()
360 def _newBertosProjectFromPreset(self):
361 # Create/write/copy the common things
362 self._newBertosProject()
364 # Copy all the files and dirs except cfg/hw/*.mk
365 self._writeCustomSrcFiles()
368 self._writeAllPresetHwFiles(self.src_hwdir, self.hwdir)
370 # Copyt the new *_user.mk file
371 self._writeUserMkFileFromPreset()
373 # Create project files for selected plugins
374 self._createProjectFiles()
376 def _editBertosProject(self):
377 # Write the project file
378 self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
379 if not self.is_preset:
380 # Generate this files only if the project isn't a preset
382 self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
383 # Destination makefile
384 self._writeMakefile()
386 self._mergeSources(self.bertos_maindir, self.srcdir, self.old_srcdir)
387 # Copy all the hw files
388 self._writeHwFiles(self.bertos_srcdir, self.hwdir)
390 # Destination wizard mk file (it seems that this file need to be
391 # rewritten also if the project is a preset)...
392 self._writeWizardMkFile()
394 # Set properly the autoenabled parameters
395 self._setupAutoenabledParameters()
396 # Copy all the configuration files
397 self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
398 if not self.is_preset:
399 # Create project files for selected plugins only if the project isn't a preset
400 self._createProjectFiles()
402 def _createProjectFiles(self):
403 # Files for selected plugins
405 for plugin in self.infos["OUTPUT"]:
406 module = loadPlugin(plugin)
407 relevants_files[plugin] = module.createProject(self)
408 self.infos["RELEVANT_FILES"] = relevants_files
410 def _writeVersionFile(self, filename):
411 if not self.edit or self.old_srcdir:
412 version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
413 open(filename, "w").write(versionFileGenerator(self, version_file))
415 def _writeProjectFile(self, filename):
416 f = open(filename, "w")
417 f.write(projectFileGenerator(self))
420 def _writeMakefile(self):
421 bertos_utils.makefileGenerator(self)
423 def _writeUserMkFile(self):
424 bertos_utils.userMkGenerator(self)
426 def _writeUserMkFileFromPreset(self):
427 bertos_utils.userMkGeneratorFromPreset(self)
429 def _writeWizardMkFile(self):
430 bertos_utils.mkGenerator(self)
432 def _writeMainFile(self, filename):
433 main = open(os.path.join(const.DATA_DIR, "srctemplates/main.c"), "r").read()
434 open(filename, "w").write(main)
436 def _writeHwFiles(self, source_dir, destination_dir):
437 for module, information in self.infos["MODULES"].items():
438 for hwfile in information["hw"]:
441 string = open(source_dir + "/" + hwfile, "r").read()
442 hwfile_path = destination_dir + "/" + os.path.basename(hwfile)
443 if not self.edit or not os.path.exists(hwfile_path):
444 # If not in editing mode it copies all the hw files. If in
445 # editing mode it copies only the files that don't exist yet
446 open(os.path.join(destination_dir,os.path.basename(hwfile)), "w").write(string)
448 def _writeAllPresetHwFiles(self, source_dir, destination_dir):
450 Copy all but directories contained into the preset hw directory.
451 It's needed because some presets need custom hw files not defined with
452 Wizard directives into modules...
454 source_dir = os.path.join(source_dir, "hw")
455 for f in os.listdir(source_dir):
456 abspath = os.path.join(source_dir, f)
457 if not os.path.isdir(abspath):
458 # Exlude directories from the copy!
459 hw_file = open(os.path.join(source_dir, f), 'r').read()
460 open(os.path.join(destination_dir, f), 'w').write(hw_file)
462 def _writeCfgFiles(self, source_dir, destination_dir):
463 for configuration, information in self.infos["CONFIGURATIONS"].items():
464 string = open(source_dir + "/" + configuration, "r").read()
465 for start, parameter in information["paramlist"]:
466 infos = information[parameter]
467 value = infos["value"]
468 if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
470 if "long" in infos["informations"] and infos["informations"]["long"]:
472 string = sub(string, parameter, value)
473 f = open(os.path.join(destination_dir, os.path.basename(configuration)), "w")
477 def _writeCustomSrcFiles(self):
478 origin = self.infos["PRESET_SRC_PATH"]
479 # Files to be ignored (all project files, cfg dir, wizard mk file, all global ignored dirs)
480 project_related_stuff = (
483 self.infos["PRESET_NAME"] + ".mk",
484 self.infos["PRESET_NAME"] + "_user.mk",
486 self.infos["PRESET_NAME"] + ".project",
487 self.infos["PRESET_NAME"] + ".workspace",
488 ) + const.IGNORE_LIST
489 for element in os.listdir(origin):
490 if element not in project_related_stuff:
491 full_path = os.path.join(origin, element)
492 if os.path.isdir(full_path):
493 copytree.copytree(full_path, os.path.join(self.prjdir, element), ignore_list=const.IGNORE_LIST)
495 shutil.copy(full_path, self.prjdir)
497 def _setupAutoenabledParameters(self):
498 for module, information in self.infos["MODULES"].items():
499 if "configuration" in information and information["configuration"] != "":
500 configurations = self.infos["CONFIGURATIONS"]
501 configuration = configurations[information["configuration"]]
502 for start, parameter in configuration["paramlist"]:
503 if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
504 configuration[parameter]["value"] = "1" if information["enabled"] else "0"
505 self.infos["CONFIGURATIONS"] = configurations
507 # Project related properties
510 return self.infos.get("PROJECT_PATH", None)
515 return os.path.join(self.maindir, "bertos")
521 return self.infos.get("PROJECT_SRC_PATH", None)
526 return os.path.join(self.prjdir, "hw")
533 return os.path.join(self.prjdir, "cfg")
538 def old_srcdir(self):
539 return self.infos.get("OLD_BERTOS_PATH", None)
541 # BeRTOS sources related properties
543 def bertos_maindir(self):
544 return self.infos.get("BERTOS_PATH", None)
547 def bertos_srcdir(self):
548 if self.bertos_maindir:
549 return os.path.join(self.bertos_maindir, "bertos")
556 return os.path.join(self.infos["PRESET_PATH"], self.infos["PRESET_HW_PATH"])
558 return self.bertos_maindir
561 def from_preset(self):
562 return self.infos.get("PROJECT_FROM_PRESET", False)
566 return self.infos.get("PRESET", False)
568 def _createDirectory(self, directory):
571 if os.path.isdir(directory):
572 shutil.rmtree(directory, True)
573 os.makedirs(directory)
575 def _copySources(self, origin, destination):
576 # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
577 shutil.rmtree(destination, True)
578 copytree.copytree(origin + "/bertos", destination, ignore_list=const.IGNORE_LIST)
580 def _mergeSources(self, origin, destination, old_sources_dir):
582 mergeSources(destination, origin, old_sources_dir)
584 def setInfo(self, key, value):
586 Store the given value with the name key.
588 self.infos[key] = value
590 def info(self, key, default=None):
592 Retrieve the value associated with the name key.
594 if key in self.infos:
595 return copy.deepcopy(self.infos[key])
598 def getCpuInfos(self):
600 for definition in self.findDefinitions(const.CPU_DEFINITION):
601 cpuInfos.append(getInfos(definition))
604 def searchFiles(self, filename):
605 file_dict = self.infos["FILE_DICT"]
606 return [(filename, dirname) for dirname in file_dict.get(filename, [])]
608 def findDefinitions(self, ftype):
609 # Maintain a cache for every scanned BERTOS_PATH
610 definitions_dict = self._cached_queries.get(self.infos["BERTOS_PATH"], {})
611 definitions = definitions_dict.get(ftype, None)
612 if definitions is not None:
614 file_dict = self.infos["FILE_DICT"]
616 for filename in file_dict:
617 if fnmatch.fnmatch(filename, ftype):
618 definitions += [(filename, dirname) for dirname in file_dict.get(filename, [])]
620 # If no cache for the current BERTOS_PATH create an empty one
621 if not definitions_dict:
622 self._cached_queries[self.infos["BERTOS_PATH"]] = {}
623 # Fill the empty cache with the result
624 self._cached_queries[self.infos["BERTOS_PATH"]][ftype] = definitions
627 def setEnabledModules(self, enabled_modules):
628 modules = self.infos["MODULES"]
630 for module, information in modules.items():
631 information["enabled"] = module in enabled_modules
632 if information["enabled"]:
633 for dependency in information["depends"]:
634 if not dependency in modules:
635 files[dependency] = files.get(dependency, 0) + 1
636 self.infos["MODULES"] = modules
637 self.infos["FILES"] = files
640 return "<BProject:instance %d>%s" %(id(self), repr(self.infos))