# -*- coding: utf-8 -*- import subprocess import re import cgi from odoo.modules.module import load_information_from_description_file from odoo.tools import which from .core import Check, CheckSuccess, CheckWarning, CheckFail class DependencyCheck(Check): dependency_type = None def __init__(self, module, dependency): super(DependencyCheck, self).__init__(module) self.dependency = dependency def _dependency_installed(self, env, name): raise NotImplementedError('Should be overriden by the subclass') def _installed_version(self, env, name): raise NotImplementedError('Should be overriden by the subclass') def _details(self): if 'install' in self.dependency: return 'Install command:
{}
'.format(self.dependency['install']) return None def __has_required_version(self, installed_version, version_expression): version_operator = '=' version = self.dependency['version'] if version[:1] in ['=', '~', '^']: version_operator = version[:1] version = version[1:] elif version[:2] in ['>=']: version_operator = version[:2] version = version[2:] try: parsed_version = map(int, version.split('.')) except ValueError: raise CheckFail( 'Invalid version expression', details = """ Allowed expressions are
=x.y.z
,
>=x.y.z
,
^x.z.y
,
~x.y.z. Got 
{}
""".format(cgi.escape(self.dependency['version'])) ) parsed_installed_version = map(int, installed_version.split('.')) parsed_version.extend(0 for _ in range(len(parsed_installed_version) - len(parsed_version))) parsed_installed_version.extend(0 for _ in range(len(parsed_version) - len(parsed_installed_version))) if version_operator == '^': if parsed_installed_version[:1] != parsed_version[:1]: return False version_operator = '>=' elif version_operator == '~': if parsed_installed_version[:2] != parsed_version[:2]: return False version_operator = '>=' if version_operator == '>=': return tuple(parsed_installed_version) >= tuple(parsed_version) elif version_operator == '=': return tuple(parsed_installed_version) == tuple(parsed_version) assert False def _run(self, env): name = self.dependency['name'] if not self._dependency_installed(env, name): raise CheckFail( 'Required {} - {} - is not installed.'.format(self.dependency_type, name), details=self._details() ) if 'version' in self.dependency: version_expression = self.dependency['version'] installed_version = self._installed_version(env, name) if not self.__has_required_version(installed_version, version_expression): raise CheckWarning( 'Required {} - {} - has version {}, but {} is needed.'.format( self.dependency_type, name, installed_version, version_expression ), details=self._details() ) return CheckSuccess( 'Required {} - {} - is installed.'.format(self.dependency_type, name), details=self._details() ) class InternalDependencyCheck(DependencyCheck): dependency_type = 'Odoo module' def _dependency_installed(self, env, name): return name in env.registry._init_modules def _installed_version(self, env, name): return env['ir.module.module'].sudo().search([('name', '=', name)]).latest_version class PythonDependencyCheck(DependencyCheck): dependency_type = 'Python module' def _dependency_installed(self, env, name): try: __import__(name) return True except ImportError: return False def _installed_version(self, env, name): try: return __import__(name).__version__ except AttributeError: raise CheckWarning( 'Could not detect version of the Python module: {}.'.format(name), details=self._details() ) class ExternalDependencyCheck(DependencyCheck): dependency_type = 'system executable' def _dependency_installed(self, env, name): try: which(name) return True except IOError: return False def _installed_version(self, env, name): try: exe = which(name) out = subprocess.check_output([exe, '--version']) match = re.search('[\d.]+', out) if not match: raise CheckWarning( 'Unable to detect version for executable {}'.format(name), details="Command {} --version returned
{}
".format(exe, out) ) return match.group(0) except subprocess.CalledProcessError as e: raise CheckWarning( 'Unable to detect version for executable {}: {}'.format(name, e), details=self._details() ) def get_checks_for_module(module_name): result = [] manifest = load_information_from_description_file(module_name) manifest_checks = manifest.get('environment_checkup') or {} dependencies = manifest_checks.get('dependencies') or {} for dependency in dependencies.get('python') or []: result.append(PythonDependencyCheck(module_name, dependency)) for dependency in dependencies.get('external') or []: result.append(ExternalDependencyCheck(module_name, dependency)) for dependency in dependencies.get('internal') or []: result.append(InternalDependencyCheck(module_name, dependency)) return result def get_checks_for_module_recursive(module): class ModuleDFS(object): def __init__(self): self.visited_modules = set() self.checks = [] def visit(self, module): if module.name in self.visited_modules: return self.visited_modules.add(module.name) self.checks += get_checks_for_module(module.name) for module_dependency in module.dependencies_id: if module_dependency.depend_id: self.visit(module_dependency.depend_id) return self return ModuleDFS().visit(module).checks