You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
6.7 KiB
183 lines
6.7 KiB
# -*- 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: <pre>{}</pre>'.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:]
|
|
|
|
# Py3 : map -> list(map
|
|
# https://stackoverflow.com/questions/33717314/attributeerror-map-obejct-has-no-attribute-index-python-3
|
|
try:
|
|
parsed_version = list(map(int, version.split('.')))
|
|
except ValueError:
|
|
raise CheckFail(
|
|
'Invalid version expression',
|
|
details = """
|
|
Allowed expressions are <pre>=x.y.z</pre>, <pre>>=x.y.z</pre>, <pre>^x.z.y</pre>,
|
|
<pre>~x.y.z. Got <pre>{}</pre>""".format(cgi.escape(self.dependency['version']))
|
|
)
|
|
parsed_installed_version = list(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 = str(subprocess.check_output([exe, '--version'])) # Py3 str()
|
|
match = re.search('[\d.]+', out)
|
|
if not match:
|
|
raise CheckWarning(
|
|
'Unable to detect version for executable {}'.format(name),
|
|
details="Command {} --version returned <pre>{}</pre>".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
|