This commit should fix the ToolchainException when the user is trying to load a preset.
[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 import shutil
41 import copytree
42
43 import DefineException
44
45 from LoadException import VersionException, ToolchainException
46
47 import const
48
49 from bertos_utils import (
50                             # Utility functions
51                             isBertosDir, getTagSet, getInfos, updateConfigurationValues,
52                             loadConfigurationInfos, loadDefineLists, loadModuleDefinition,
53                             getCommentList, sub,
54
55                             # Project creation functions
56                             projectFileGenerator, versionFileGenerator, loadPlugin, 
57                             mergeSources,
58
59                             # Custom exceptions
60                             ParseError, SupportedException
61                         )
62 import bertos_utils
63
64 from compatibility import updateProject
65
66 class BProject(object):
67     """
68     Simple class for store and retrieve project informations.
69     """
70
71     def __init__(self, project_file="", info_dict={}):
72         self.infos = {}
73         self._cached_queries = {}
74         self.edit = False
75         if project_file:
76             self.edit = True
77             self.loadBertosProject(project_file, info_dict)
78
79     #--- Load methods (methods that loads data into project) ------------------#
80
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"]))
90         if project_src_path:
91             self.infos["PROJECT_SRC_PATH"] = project_src_path
92             
93         else:
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"]))
97
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
101         
102         self._loadBertosSourceStuff(project_data["BERTOS_PATH"], info_dict.get("BERTOS_PATH", None))
103         
104         self.infos["PRESET"] = project_data.get("PRESET", False)
105
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())
110
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"])
117
118     def _loadBertosSourceStuff(self, sources_path, forced_version=None):
119         if forced_version:
120             sources_path = forced_version
121         if os.path.exists(sources_path):
122             self.infos["BERTOS_PATH"] = sources_path
123         else:
124             raise VersionException(self)
125
126     def _loadCpuStuff(self, cpu_name, cpu_frequency):
127         self.infos["CPU_NAME"] = cpu_name
128         cpu_info = self.getCpuInfos()
129         for cpu in cpu_info:
130             if cpu["CPU_NAME"] == cpu_name:
131                 self.infos["CPU_INFOS"] = cpu
132                 break
133         tag_list = getTagSet(cpu_info)
134         # Create, fill and store the dict with the tags
135         tag_dict = {}
136         for element in tag_list:
137             tag_dict[element] = False
138         infos = self.info("CPU_INFOS")
139         for tag in tag_dict:
140             if tag in infos["CPU_TAGS"] + [infos["CPU_NAME"], infos["TOOLCHAIN"]]:
141                 tag_dict[tag] = True
142             else:
143                 tag_dict[tag] = False
144         self.infos["ALL_CPU_TAGS"] = tag_dict
145         self.infos["SELECTED_FREQ"] = cpu_frequency
146
147     def _loadToolchainStuff(self, toolchain, forced_toolchain=None):
148         toolchain = toolchain
149         if forced_toolchain:
150             toolchain = forced_toolchain
151         if os.path.exists(toolchain["path"]):
152             self.infos["TOOLCHAIN"] = toolchain
153         else:
154             raise ToolchainException(self)
155
156     def loadProjectFromPreset(self, preset):
157         """
158         Load a project from a preset.
159         NOTE: this is a stub.
160         """
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"])
165
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))
171
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"]
176
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
181
182         self.loadModuleData(True)
183         self.setEnabledModules(project_data["ENABLED_MODULES"])
184
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!
190
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
195
196     def loadProjectPresets(self):
197         """
198         Load the default presets (into the const.PREDEFINED_BOARDS_DIR).
199         """
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 = {}
203         if os.path.exists(preset_path):
204             preset_tree = self._loadProjectPresetTree(preset_path)
205         self.infos["PRESET_TREE"] = preset_tree
206
207     def _loadProjectPresetTree(self, path):
208         _tree = {}
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"
223         else:
224             _tree["info"]["type"] = "project"
225         return _tree
226
227     def _loadPresetInfo(self, preset_spec_file):
228         D = {}
229         execfile(preset_spec_file, {}, D)
230         return D
231
232     def loadModuleData(self, edit=False):
233         module_info_dict = {}
234         list_info_dict = {}
235         configuration_info_dict = {}
236         file_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:
240                 module_info = {}
241                 configuration_info = {}
242                 try:
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)
251                     
252                     # Hack to remove 'bertos/' from the configuration file path.
253                     #
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"]]
258
259                     if "configuration" in information and len(information["configuration"]):
260                         configuration = module_dict[module]["configuration"]
261                         try:
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)
266                         if edit:
267                             try:
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)
273                 module_info_dict.update(module_dict)
274                 configuration_info_dict.update(configuration_info)
275                 if to_be_parsed:
276                     try:
277                         list_dict = loadDefineLists(comment_list[1:])
278                         list_info_dict.update(list_dict)
279                     except ParseError, err:
280                         raise DefineException.EnumDefineException(path, err.line_number, err.line)
281         for tag in self.infos["CPU_INFOS"]["CPU_TAGS"]:
282             for filename, path in self.findDefinitions("*_" + tag + ".h"):
283                 comment_list = getCommentList(open(path + "/" + filename, "r").read())
284                 list_info_dict.update(loadDefineLists(comment_list))
285         self.infos["MODULES"] = module_info_dict
286         self.infos["LISTS"] = list_info_dict
287         self.infos["CONFIGURATIONS"] = configuration_info_dict
288         self.infos["FILES"] = file_dict
289
290     def loadSourceTree(self):
291         """
292         Index BeRTOS source and load it in memory.
293         """
294         # Index only the BERTOS_PATH/bertos content
295         bertos_sources_dir = os.path.join(self.info("BERTOS_PATH"), "bertos")
296         file_dict = {}
297         if os.path.exists(bertos_sources_dir):
298             for element in os.walk(bertos_sources_dir):
299                 for f in element[2]:
300                     file_dict[f] = file_dict.get(f, []) + [element[0]]
301         self.infos["FILE_DICT"] = file_dict
302
303     def reloadCpuInfo(self):
304         for cpu_info in self.getCpuInfos():
305             if cpu_info["CPU_NAME"] == self.infos["CPU_NAME"]:
306                 self.infos["CPU_INFOS"] = cpu_info
307
308     #-------------------------------------------------------------------------#
309
310     def createBertosProject(self):
311         # NOTE: Temporary hack.
312         if self.edit:
313             self._editBertosProject()
314         else:
315             if not self.from_preset:
316                 self._newCustomBertosProject()
317             else:
318                 self._newBertosProjectFromPreset()
319
320     def _newBertosProject(self):
321         for directory in (self.maindir, self.srcdir, self.prjdir, self.cfgdir, self.hwdir):
322             self._createDirectory(directory)
323         # Write the project file
324         self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
325         # VERSION file
326         self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
327         # Destination makefile
328         self._writeMakefile()
329         # Copy the sources
330         self._copySources(self.bertos_maindir, self.srcdir)
331         # Set properly the autoenabled parameters
332         self._setupAutoenabledParameters()
333         # Copy all the configuration files
334         self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
335         # Destination wizard mk file
336         self._writeWizardMkFile()
337
338     def _newCustomBertosProject(self):
339         # Create/write/copy the common things
340         self._newBertosProject()
341         # Copy the clean hw files
342         self._createDirectory(self.hwdir)
343         # Copy all the hw files
344         self._writeHwFiles(self.bertos_srcdir, self.hwdir)
345         # Destination user mk file
346         self._writeUserMkFile()
347         # Destination main.c file
348         self._writeMainFile(self.prjdir + "/main.c")
349         # Create project files for selected plugins
350         self._createProjectFiles()
351
352     def _newBertosProjectFromPreset(self):
353         # Create/write/copy the common things
354         self._newBertosProject()
355
356         # Copy all the files and dirs except cfg/hw/*.mk
357         self._writeCustomSrcFiles()
358         
359         # Copy the hw files
360         self._writeHwFiles(self.src_hwdir, self.hwdir)
361
362         # Copyt the new *_user.mk file
363         self._writeUserMkFileFromPreset()
364
365         if self.infos["EMPTY_MAIN"]:
366             # Create and empty main.c file only if the user check the box
367             self._writeMainFile(self.prjdir + "/main.c")
368
369         # Create project files for selected plugins
370         self._createProjectFiles()
371
372     def _editBertosProject(self):
373         # Write the project file
374         self._writeProjectFile(os.path.join(self.maindir, "project.bertos"))
375         if not self.is_preset:
376             # Generate this files only if the project isn't a preset
377             # VERSION file
378             self._writeVersionFile(os.path.join(self.maindir, "VERSION"))
379             # Destination makefile
380             self._writeMakefile()
381             # Merge sources
382             self._mergeSources(self.bertos_maindir, self.srcdir, self.old_srcdir)
383             # Copy all the hw files
384             self._writeHwFiles(self.bertos_srcdir, self.hwdir)
385             # Destination wizard mk file
386             self._writeWizardMkFile()
387         # Set properly the autoenabled parameters
388         self._setupAutoenabledParameters()
389         # Copy all the configuration files
390         self._writeCfgFiles(self.bertos_srcdir, self.cfgdir)
391         if not self.is_preset:
392             # Create project files for selected plugins only if the project isn't a preset
393             self._createProjectFiles()
394
395     def _createProjectFiles(self):
396         # Files for selected plugins
397         relevants_files = {}
398         for plugin in self.infos["OUTPUT"]:
399             module = loadPlugin(plugin)
400             relevants_files[plugin] = module.createProject(self)
401         self.infos["RELEVANT_FILES"] = relevants_files
402
403     def _writeVersionFile(self, filename):
404         version_file = open(os.path.join(const.DATA_DIR, "vtemplates/VERSION"), "r").read()
405         open(filename, "w").write(versionFileGenerator(self, version_file))
406
407     def _writeProjectFile(self, filename):
408         f = open(filename, "w")
409         f.write(projectFileGenerator(self))
410         f.close()
411
412     def _writeMakefile(self):
413         bertos_utils.makefileGenerator(self)
414
415     def _writeUserMkFile(self):
416         bertos_utils.userMkGenerator(self)
417
418     def _writeUserMkFileFromPreset(self):
419         bertos_utils.userMkGeneratorFromPreset(self)
420
421     def _writeWizardMkFile(self):
422         bertos_utils.mkGenerator(self)
423
424     def _writeMainFile(self, filename):
425         main = open(os.path.join(const.DATA_DIR, "srctemplates/main.c"), "r").read()
426         open(filename, "w").write(main)
427
428     def _writeHwFiles(self, source_dir, destination_dir):
429         for module, information in self.infos["MODULES"].items():
430             for hwfile in information["hw"]:
431                 string = open(source_dir + "/" + hwfile, "r").read()
432                 hwfile_path = destination_dir + "/" + os.path.basename(hwfile)
433                 if not self.edit or not os.path.exists(hwfile_path):
434                     # If not in editing mode it copies all the hw files. If in
435                     # editing mode it copies only the files that don't exist yet
436                     open(os.path.join(destination_dir,os.path.basename(hwfile)), "w").write(string)
437
438     def _writeCfgFiles(self, source_dir, destination_dir):
439         for configuration, information in self.infos["CONFIGURATIONS"].items():
440             string = open(source_dir + "/" + configuration, "r").read()
441             for start, parameter in information["paramlist"]:
442                 infos = information[parameter]
443                 value = infos["value"]
444                 if "unsigned" in infos["informations"] and infos["informations"]["unsigned"]:
445                     value += "U"
446                 if "long" in infos["informations"] and infos["informations"]["long"]:
447                     value += "L"
448                 string = sub(string, parameter, value)
449             f = open(os.path.join(destination_dir, os.path.basename(configuration)), "w")
450             f.write(string)
451             f.close()
452
453     def _writeCustomSrcFiles(self):
454         origin = self.infos["PRESET_SRC_PATH"]
455         # Files to be ignored (all project files, cfg dir, wizard mk file, all global ignored dirs)
456         project_related_stuff = (
457             "cfg",
458             "hw",
459             self.infos["PRESET_NAME"] + ".mk",
460             self.infos["PRESET_NAME"] + "_user.mk",
461             "project.bertos",
462             self.infos["PRESET_NAME"] + ".project",
463             self.infos["PRESET_NAME"] + ".workspace",
464         ) + const.IGNORE_LIST
465         for element in os.listdir(origin):
466             if element not in project_related_stuff:
467                 full_path = os.path.join(origin, element)
468                 if os.path.isdir(full_path):
469                     copytree.copytree(full_path, os.path.join(self.prjdir, element), ignore_list=const.IGNORE_LIST)
470                 else:
471                     shutil.copy(full_path, self.prjdir)
472
473     def _setupAutoenabledParameters(self):
474         for module, information in self.infos["MODULES"].items():
475             if "configuration" in information and information["configuration"] != "":
476                 configurations = self.infos["CONFIGURATIONS"]
477                 configuration = configurations[information["configuration"]]
478                 for start, parameter in configuration["paramlist"]:
479                     if "type" in configuration[parameter]["informations"] and configuration[parameter]["informations"]["type"] == "autoenabled":
480                         configuration[parameter]["value"] = "1" if information["enabled"] else "0"
481                 self.infos["CONFIGURATIONS"] = configurations
482
483     # Project related properties
484     @property
485     def maindir(self):
486         return self.infos.get("PROJECT_PATH", None)
487
488     @property
489     def srcdir(self):
490         if self.maindir:
491             return os.path.join(self.maindir, "bertos")
492         else:
493             return None
494
495     @property
496     def prjdir(self):
497         return self.infos.get("PROJECT_SRC_PATH", None)
498
499     @property
500     def hwdir(self):
501         if self.prjdir:
502             return os.path.join(self.prjdir, "hw")
503         else:
504             return None
505
506     @property
507     def cfgdir(self):
508         if self.prjdir:
509             return os.path.join(self.prjdir, "cfg")
510         else:
511             return None
512
513     @property
514     def old_srcdir(self):
515         return self.infos.get("OLD_BERTOS_PATH", None)
516
517     # BeRTOS sources related properties
518     @property
519     def bertos_maindir(self):
520         return self.infos.get("BERTOS_PATH", None)
521
522     @property
523     def bertos_srcdir(self):
524         if self.bertos_maindir:
525             return os.path.join(self.bertos_maindir, "bertos")
526         else:
527             return None
528
529     @property
530     def src_hwdir(self):
531         if self.from_preset:
532             return os.path.join(self.infos["PRESET_PATH"], self.infos["PRESET_HW_PATH"])
533         else:
534             return self.bertos_maindir
535
536     @property
537     def from_preset(self):
538         return self.infos.get("PROJECT_FROM_PRESET", False)
539
540     @property
541     def is_preset(self):
542         return self.infos.get("PRESET", False)
543
544     def _createDirectory(self, directory):
545         if not directory:
546             return
547         if os.path.isdir(directory):
548             shutil.rmtree(directory, True)
549         os.makedirs(directory)
550
551     def _copySources(self, origin, destination):
552         # If not in editing mode it copies all the bertos sources in the /bertos subdirectory of the project
553         shutil.rmtree(destination, True)
554         copytree.copytree(origin + "/bertos", destination, ignore_list=const.IGNORE_LIST)
555
556     def _mergeSources(self, origin, destination, old_sources_dir):
557         if old_sources_dir:
558             mergeSources(destination, origin, old_sources_dir)
559
560     def setInfo(self, key, value):
561         """
562         Store the given value with the name key.
563         """
564         self.infos[key] = value
565
566     def info(self, key, default=None):
567         """
568         Retrieve the value associated with the name key.
569         """
570         if key in self.infos:
571             return copy.deepcopy(self.infos[key])
572         return default
573
574     def getCpuInfos(self):
575         cpuInfos = []
576         for definition in self.findDefinitions(const.CPU_DEFINITION):
577             cpuInfos.append(getInfos(definition))
578         return cpuInfos
579
580     def searchFiles(self, filename):
581         file_dict = self.infos["FILE_DICT"]
582         return [(filename, dirname) for dirname in file_dict.get(filename, [])]
583
584     def findDefinitions(self, ftype):
585         # Maintain a cache for every scanned BERTOS_PATH
586         definitions_dict = self._cached_queries.get(self.infos["BERTOS_PATH"], {})
587         definitions = definitions_dict.get(ftype, None)
588         if definitions is not None:
589             return definitions
590         file_dict = self.infos["FILE_DICT"]
591         definitions = []
592         for filename in file_dict:
593             if fnmatch.fnmatch(filename, ftype):
594                 definitions += [(filename, dirname) for dirname in file_dict.get(filename, [])]
595
596         # If no cache for the current BERTOS_PATH create an empty one
597         if not definitions_dict:
598             self._cached_queries[self.infos["BERTOS_PATH"]] = {}
599         # Fill the empty cache with the result
600         self._cached_queries[self.infos["BERTOS_PATH"]][ftype] = definitions
601         return definitions
602
603     def setEnabledModules(self, enabled_modules):
604         modules = self.infos["MODULES"]
605         files = {}
606         for module, information in modules.items():
607             information["enabled"] = module in enabled_modules
608             if information["enabled"]:
609                 for dependency in information["depends"]:
610                     if not dependency in modules:
611                         files[dependency] = files.get(dependency, 0) + 1
612         self.infos["MODULES"] = modules
613         self.infos["FILES"] = files
614
615     def __repr__(self):
616         return "<BProject:instance %d>%s" %(id(self), repr(self.infos))