Stub of preset load method.
[bertos.git] / wizard / BProject.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 #
4 # This file is part of BeRTOS.
5 #
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.
10 #
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.
15 #
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
19 #
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.
28 #
29 # Copyright 2008 Develer S.r.l. (http://www.develer.com/)
30 #
31 # $Id$
32 #
33 # Author: Lorenzo Berni <duplo@develer.com>
34 #
35
36 import os
37 import fnmatch
38 import copy
39 import pickle
40
41 import DefineException
42
43 from LoadException import VersionException, ToolchainException
44
45 import const
46
47 from bertos_utils import (
48                             # Utility functions
49                             isBertosDir, getTagSet, getInfos, updateConfigurationValues,
50                             loadConfigurationInfos, loadDefineLists, loadModuleDefinition,
51                             getCommentList,
52
53                             # Custom exceptions
54                             ParseError, SupportedException
55                         )
56
57 class BProject(object):
58     """
59     Simple class for store and retrieve project informations.
60     """
61
62     def __init__(self, project_file="", info_dict={}):
63         self.infos = {}
64         self._cached_queries = {}
65         if project_file:
66             self.loadBertosProject(project_file, info_dict)
67
68     #--- Load methods (methods that loads data into project) ------------------#
69
70     def loadBertosProject(self, project_file, info_dict):
71         project_dir = os.path.dirname(project_file)
72         project_data = pickle.loads(open(project_file, "r").read())
73         # If PROJECT_NAME is not defined it use the directory name as PROJECT_NAME
74         # NOTE: this can throw an Exception if the user has changed the directory containing the project
75         self.infos["PROJECT_NAME"] = project_data.get("PROJECT_NAME", os.path.basename(project_dir))
76         self.infos["PROJECT_PATH"] = os.path.dirname(project_file)
77
78         # Check for the Wizard version
79         wizard_version = project_data.get("WIZARD_VERSION", 0)
80         if wizard_version < 1:
81             # Ignore the SOURCES_PATH inside the project file for older projects
82             project_data["SOURCES_PATH"] = project_dir
83         self._loadBertosSourceStuff(project_data["SOURCES_PATH"], info_dict.get("SOURCES_PATH", None))
84
85         # For those projects that don't have a VERSION file create a dummy one.
86         if not isBertosDir(project_dir):
87             version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
88             open(os.path.join(project_dir, "VERSION"), "w").write(version_file.replace("$version", "").strip())
89
90         self.loadSourceTree()
91         self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
92         self._loadToolchainStuff(project_data["TOOLCHAIN"], info_dict.get("TOOLCHAIN", None))
93         self.infos["OUTPUT"] = project_data["OUTPUT"]
94         self.loadModuleData(True)
95         self.setEnabledModules(project_data["ENABLED_MODULES"])
96
97     def _loadBertosSourceStuff(self, sources_path, forced_version=None):
98         if forced_version:
99             sources_path = forced_version
100         if os.path.exists(sources_path):
101             self.infos["SOURCES_PATH"] = sources_path
102         else:
103             raise VersionException(self)
104
105     def _loadCpuStuff(self, cpu_name, cpu_frequency):
106         self.infos["CPU_NAME"] = cpu_name
107         cpu_info = self.getCpuInfos()
108         for cpu in cpu_info:
109             if cpu["CPU_NAME"] == cpu_name:
110                 self.infos["CPU_INFOS"] = cpu
111                 break
112         tag_list = getTagSet(cpu_info)
113         # Create, fill and store the dict with the tags
114         tag_dict = {}
115         for element in tag_list:
116             tag_dict[element] = False
117         infos = self.info("CPU_INFOS")
118         for tag in tag_dict:
119             if tag in infos["CPU_TAGS"] + [infos["CPU_NAME"], infos["TOOLCHAIN"]]:
120                 tag_dict[tag] = True
121             else:
122                 tag_dict[tag] = False
123         self.infos["ALL_CPU_TAGS"] = tag_dict
124         self.infos["SELECTED_FREQ"] = cpu_frequency
125
126     def _loadToolchainStuff(self, toolchain, forced_toolchain=None):
127         toolchain = toolchain
128         if forced_toolchain:
129             toolchain = forced_toolchain
130         if os.path.exists(toolchain["path"]):
131             self.infos["TOOLCHAIN"] = toolchain
132         else:
133             raise ToolchainException(self)
134
135     def loadProjectFromPreset(self, preset):
136         """
137         Load a project from a preset.
138         NOTE: this is a stub.
139         """
140         project_file = os.path.join(preset, "project.bertos")
141         project_data = pickle.loads(open(project_file, "r").read())
142         self.loadSourceTree()
143         self._loadCpuStuff(project_data["CPU_NAME"], project_data["SELECTED_FREQ"])
144         self._loadToolchainStuff(project_data["TOOLCHAIN"])
145         # NOTE: this is a HACK!!!
146         # TODO: find a better way to reuse loadModuleData
147         old_project_name = self.infos["PROJECT_NAME"]
148         old_project_path = self.infos["PROJECT_PATH"]
149         self.infos["PROJECT_NAME"] = project_data.get("PROJECT_NAME", os.path.basename(preset))
150         self.infos["PROJECT_PATH"] = preset
151         self.loadModuleData(True)
152         self.setEnabledModules(project_data["ENABLED_MODULES"])
153         self.infos["PROJECT_NAME"] = old_project_name
154         self.infos["PROJECT_PATH"] = old_project_path        
155
156     def loadProjectPresets(self):
157         """
158         Load the default presets (into the const.PREDEFINED_BOARDS_DIR).
159         """
160         # NOTE: this method does nothing (for now).
161         preset_path = os.path.join(self.infos["SOURCES_PATH"], const.PREDEFINED_BOARDS_DIR)
162         preset_tree = {}
163         if os.path.exists(preset_path):
164             preset_tree = self._loadProjectPresetTree(preset_path)
165         self.infos["PRESET_TREE"] = preset_tree
166
167     def _loadProjectPresetTree(self, path):
168         _tree = {}
169         _tree['info'] = self._loadPresetInfo(os.path.join(path, const.PREDEFINED_BOARD_SPEC_FILE))
170         _tree['info']['filename'] = os.path.basename(path)
171         _tree['info']['path'] = path
172         _tree['children'] = []
173         entries = set(os.listdir(path))
174         for entry in entries:
175             _path = os.path.join(path, entry)
176             if os.path.isdir(_path):
177                 sub_entries = set(os.listdir(_path))
178                 if const.PREDEFINED_BOARD_SPEC_FILE in sub_entries:
179                     _tree['children'].append(self._loadProjectPresetTree(_path))
180         # Add into the info dict the dir type (dir/project)
181         if _tree['children']:
182             _tree['info']['type'] = 'dir'
183         else:
184             _tree['info']['type'] = 'project'
185         return _tree
186
187     def _loadPresetInfo(self, preset_spec_file):
188         D = {}
189         execfile(preset_spec_file, {}, D)
190         return D
191
192     def loadModuleData(self, edit=False):
193         module_info_dict = {}
194         list_info_dict = {}
195         configuration_info_dict = {}
196         file_dict = {}
197         for filename, path in self.findDefinitions("*.h") + self.findDefinitions("*.c") + self.findDefinitions("*.s") + self.findDefinitions("*.S"):
198             comment_list = getCommentList(open(path + "/" + filename, "r").read())
199             if len(comment_list) > 0:
200                 module_info = {}
201                 configuration_info = {}
202                 try:
203                     to_be_parsed, module_dict = loadModuleDefinition(comment_list[0])
204                 except ParseError, err:
205                     raise DefineException.ModuleDefineException(path, err.line_number, err.line)
206                 for module, information in module_dict.items():
207                     if "depends" not in information:
208                         information["depends"] = ()
209                     information["depends"] += (filename.split(".")[0],)
210                     information["category"] = os.path.basename(path)
211                     if "configuration" in information and len(information["configuration"]):
212                         configuration = module_dict[module]["configuration"]
213                         try:
214                             configuration_info[configuration] = loadConfigurationInfos(self.infos["SOURCES_PATH"] + "/" + configuration)
215                         except ParseError, err:
216                             raise DefineException.ConfigurationDefineException(self.infos["SOURCES_PATH"] + "/" + configuration, err.line_number, err.line)
217                         if edit:
218                             try:
219                                 path = self.infos["PROJECT_NAME"]
220                                 directory = self.infos["PROJECT_PATH"]
221                                 user_configuration = loadConfigurationInfos(directory + "/" + configuration.replace("bertos", path))
222                                 configuration_info[configuration] = updateConfigurationValues(configuration_info[configuration], user_configuration)
223                             except ParseError, err:
224                                 raise DefineException.ConfigurationDefineException(directory + "/" + configuration.replace("bertos", path))
225                 module_info_dict.update(module_dict)
226                 configuration_info_dict.update(configuration_info)
227                 if to_be_parsed:
228                     try:
229                         list_dict = loadDefineLists(comment_list[1:])
230                         list_info_dict.update(list_dict)
231                     except ParseError, err:
232                         raise DefineException.EnumDefineException(path, err.line_number, err.line)
233         for tag in self.infos["CPU_INFOS"]["CPU_TAGS"]:
234             for filename, path in self.findDefinitions("*_" + tag + ".h"):
235                 comment_list = getCommentList(open(path + "/" + filename, "r").read())
236                 list_info_dict.update(loadDefineLists(comment_list))
237         self.infos["MODULES"] = module_info_dict
238         self.infos["LISTS"] = list_info_dict
239         self.infos["CONFIGURATIONS"] = configuration_info_dict
240         self.infos["FILES"] = file_dict
241
242     def loadSourceTree(self):
243         """
244         Index BeRTOS source and load it in memory.
245         """
246         # Index only the SOURCES_PATH/bertos content
247         bertos_sources_dir = os.path.join(self.info("SOURCES_PATH"), 'bertos')
248         file_dict = {}
249         if os.path.exists(bertos_sources_dir):
250             for element in os.walk(bertos_sources_dir):
251                 for f in element[2]:
252                     file_dict[f] = file_dict.get(f, []) + [element[0]]
253         self.infos["FILE_DICT"] = file_dict
254
255     def reloadCpuInfo(self):
256         for cpu_info in self.getCpuInfos():
257             if cpu_info["CPU_NAME"] == self.infos["CPU_NAME"]:
258                 self.infos["CPU_INFOS"] = cpu_info
259
260     #-------------------------------------------------------------------------#
261
262     def setInfo(self, key, value):
263         """
264         Store the given value with the name key.
265         """
266         self.infos[key] = value
267
268     def info(self, key, default=None):
269         """
270         Retrieve the value associated with the name key.
271         """
272         if key in self.infos:
273             return copy.deepcopy(self.infos[key])
274         return default
275
276     def getCpuInfos(self):
277         cpuInfos = []
278         for definition in self.findDefinitions(const.CPU_DEFINITION):
279             cpuInfos.append(getInfos(definition))
280         return cpuInfos
281
282     def searchFiles(self, filename):
283         file_dict = self.infos["FILE_DICT"]
284         return [(filename, dirname) for dirname in file_dict.get(filename, [])]
285
286     def findDefinitions(self, ftype):
287         # Maintain a cache for every scanned SOURCES_PATH
288         definitions_dict = self._cached_queries.get(self.infos["SOURCES_PATH"], {})
289         definitions = definitions_dict.get(ftype, None)
290         if definitions is not None:
291             return definitions
292         file_dict = self.infos["FILE_DICT"]
293         definitions = []
294         for filename in file_dict:
295             if fnmatch.fnmatch(filename, ftype):
296                 definitions += [(filename, dirname) for dirname in file_dict.get(filename, [])]
297
298         # If no cache for the current SOURCES_PATH create an empty one
299         if not definitions_dict:
300             self._cached_queries[self.infos["SOURCES_PATH"]] = {}
301         # Fill the empty cache with the result
302         self._cached_queries[self.infos["SOURCES_PATH"]][ftype] = definitions
303         return definitions
304
305     def setEnabledModules(self, enabled_modules):
306         modules = self.infos["MODULES"]
307         files = {}
308         for module, information in modules.items():
309             information["enabled"] = module in enabled_modules
310             if information["enabled"]:
311                 for dependency in information["depends"]:
312                     if not dependency in modules:
313                         files[dependency] = files.get(dependency, 0) + 1
314         self.infos["MODULES"] = modules
315         self.infos["FILES"] = files
316
317     def __repr__(self):
318         return repr(self.infos)