1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 # Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as
10 # published by the Free Software Foundation, either version 3 of the
11 # License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
23 """ Modules dependency graph. """
26 from os.path import join as opj
32 import openerp.osv as osv
33 import openerp.tools as tools
34 import openerp.tools.osutil as osutil
35 from openerp.tools.safe_eval import safe_eval as eval
36 import openerp.pooler as pooler
37 from openerp.tools.translate import _
39 import openerp.netsvc as netsvc
42 import openerp.release as release
46 from zipfile import PyZipFile, ZIP_DEFLATED
47 from cStringIO import StringIO
51 logger = netsvc.Logger()
55 """ Modules dependency graph.
57 The graph is a mapping from module name to Nodes.
61 def add_node(self, name, deps):
62 max_depth, father = 0, None
63 for n in [Node(x, self) for x in deps]:
64 if n.depth >= max_depth:
68 return father.add_child(name)
70 return Node(name, self)
72 def update_from_db(self, cr):
75 # update the graph with values from the database (if exist)
76 ## First, we set the default values for each package in graph
77 additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
78 ## Then we get the values from the database
79 cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
80 ' FROM ir_module_module'
81 ' WHERE name IN %s',(tuple(additional_data),)
84 ## and we update the default values with values from the database
85 additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
87 for package in self.values():
88 for k, v in additional_data[package.name].items():
89 setattr(package, k, v)
91 def add_module(self, cr, module, force=None):
92 self.add_modules(cr, [module], force)
94 def add_modules(self, cr, module_list, force=None):
99 for module in module_list:
100 # This will raise an exception if no/unreadable descriptor file.
101 # NOTE The call to load_information_from_description_file is already
102 # done by db.initialize, so it is possible to not do it again here.
103 info = openerp.modules.module.load_information_from_description_file(module)
104 if info['installable']:
105 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
107 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
109 dependencies = dict([(p, info['depends']) for p, info in packages])
110 current, later = set([p for p, info in packages]), set()
112 while packages and current > later:
113 package, info = packages[0]
114 deps = info['depends']
116 # if all dependencies of 'package' are already in the graph, add 'package' in the graph
117 if reduce(lambda x, y: x and y in self, deps, True):
118 if not package in current:
122 current.remove(package)
123 node = self.add_node(package, deps)
125 for kind in ('init', 'demo', 'update'):
126 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
127 setattr(node, kind, True)
130 packages.append((package, info))
133 self.update_from_db(cr)
135 for package in later:
136 unmet_deps = filter(lambda p: p not in self, dependencies[package])
137 logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
139 result = len(self) - len_graph
140 if result != len(module_list):
141 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
147 done = set(self.keys())
149 level_modules = [(name, module) for name, module in self.items() if module.depth==level]
150 for name, module in level_modules:
156 class Singleton(object):
157 def __new__(cls, name, graph):
161 inst = object.__new__(cls)
167 class Node(Singleton):
168 """ One module in the modules dependency graph.
170 Node acts as a per-module singleton.
174 def __init__(self, name, graph):
176 if not hasattr(self, 'children'):
178 if not hasattr(self, 'depth'):
181 def add_child(self, name):
182 node = Node(name, self.graph)
183 node.depth = self.depth + 1
184 if node not in self.children:
185 self.children.append(node)
186 for attr in ('init', 'update', 'demo'):
187 if hasattr(self, attr):
188 setattr(node, attr, True)
189 self.children.sort(lambda x, y: cmp(x.name, y.name))
192 def __setattr__(self, name, value):
193 super(Singleton, self).__setattr__(name, value)
194 if name in ('init', 'update', 'demo'):
195 tools.config[name][self.name] = 1
196 for child in self.children:
197 setattr(child, name, value)
199 for child in self.children:
200 setattr(child, name, value + 1)
203 return itertools.chain(iter(self.children), *map(iter, self.children))
206 return self._pprint()
208 def _pprint(self, depth=0):
209 s = '%s\n' % self.name
210 for c in self.children:
211 s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1))
215 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: