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

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. # -*- coding: utf-8 -*-
  2. import subprocess
  3. import re
  4. import cgi
  5. from odoo.modules.module import load_information_from_description_file
  6. from odoo.tools import which
  7. from .core import Check, CheckSuccess, CheckWarning, CheckFail
  8. class DependencyCheck(Check):
  9. dependency_type = None
  10. def __init__(self, module, dependency):
  11. super(DependencyCheck, self).__init__(module)
  12. self.dependency = dependency
  13. def _dependency_installed(self, env, name):
  14. raise NotImplementedError('Should be overriden by the subclass')
  15. def _installed_version(self, env, name):
  16. raise NotImplementedError('Should be overriden by the subclass')
  17. def _details(self):
  18. if 'install' in self.dependency:
  19. return 'Install command: <pre>{}</pre>'.format(self.dependency['install'])
  20. return None
  21. def __has_required_version(self, installed_version, version_expression):
  22. version_operator = '='
  23. version = self.dependency['version']
  24. if version[:1] in ['=', '~', '^']:
  25. version_operator = version[:1]
  26. version = version[1:]
  27. elif version[:2] in ['>=']:
  28. version_operator = version[:2]
  29. version = version[2:]
  30. # Py3 : map -> list(map
  31. # https://stackoverflow.com/questions/33717314/attributeerror-map-obejct-has-no-attribute-index-python-3
  32. try:
  33. parsed_version = list(map(int, version.split('.')))
  34. except ValueError:
  35. raise CheckFail(
  36. 'Invalid version expression',
  37. details = """
  38. Allowed expressions are <pre>=x.y.z</pre>, <pre>&gt;=x.y.z</pre>, <pre>^x.z.y</pre>,
  39. <pre>~x.y.z. Got <pre>{}</pre>""".format(cgi.escape(self.dependency['version']))
  40. )
  41. parsed_installed_version = list(map(int, installed_version.split('.')))
  42. parsed_version.extend(0 for _ in range(len(parsed_installed_version) - len(parsed_version)))
  43. parsed_installed_version.extend(0 for _ in range(len(parsed_version) - len(parsed_installed_version)))
  44. if version_operator == '^':
  45. if parsed_installed_version[:1] != parsed_version[:1]:
  46. return False
  47. version_operator = '>='
  48. elif version_operator == '~':
  49. if parsed_installed_version[:2] != parsed_version[:2]:
  50. return False
  51. version_operator = '>='
  52. if version_operator == '>=':
  53. return tuple(parsed_installed_version) >= tuple(parsed_version)
  54. elif version_operator == '=':
  55. return tuple(parsed_installed_version) == tuple(parsed_version)
  56. assert False
  57. def _run(self, env):
  58. name = self.dependency['name']
  59. if not self._dependency_installed(env, name):
  60. raise CheckFail(
  61. 'Required {} - {} - is not installed.'.format(self.dependency_type, name),
  62. details=self._details()
  63. )
  64. if 'version' in self.dependency:
  65. version_expression = self.dependency['version']
  66. installed_version = self._installed_version(env, name)
  67. if not self.__has_required_version(installed_version, version_expression):
  68. raise CheckWarning(
  69. 'Required {} - {} - has version {}, but {} is needed.'.format(
  70. self.dependency_type,
  71. name,
  72. installed_version,
  73. version_expression
  74. ),
  75. details=self._details()
  76. )
  77. return CheckSuccess(
  78. 'Required {} - {} - is installed.'.format(self.dependency_type, name),
  79. details=self._details()
  80. )
  81. class InternalDependencyCheck(DependencyCheck):
  82. dependency_type = 'Odoo module'
  83. def _dependency_installed(self, env, name):
  84. return name in env.registry._init_modules
  85. def _installed_version(self, env, name):
  86. return env['ir.module.module'].sudo().search([('name', '=', name)]).latest_version
  87. class PythonDependencyCheck(DependencyCheck):
  88. dependency_type = 'Python module'
  89. def _dependency_installed(self, env, name):
  90. try:
  91. __import__(name)
  92. return True
  93. except ImportError:
  94. return False
  95. def _installed_version(self, env, name):
  96. try:
  97. return __import__(name).__version__
  98. except AttributeError:
  99. raise CheckWarning(
  100. 'Could not detect version of the Python module: {}.'.format(name),
  101. details=self._details()
  102. )
  103. class ExternalDependencyCheck(DependencyCheck):
  104. dependency_type = 'system executable'
  105. def _dependency_installed(self, env, name):
  106. try:
  107. which(name)
  108. return True
  109. except IOError:
  110. return False
  111. def _installed_version(self, env, name):
  112. try:
  113. exe = which(name)
  114. out = str(subprocess.check_output([exe, '--version'])) # Py3 str()
  115. match = re.search('[\d.]+', out)
  116. if not match:
  117. raise CheckWarning(
  118. 'Unable to detect version for executable {}'.format(name),
  119. details="Command {} --version returned <pre>{}</pre>".format(exe, out)
  120. )
  121. return match.group(0)
  122. except subprocess.CalledProcessError as e:
  123. raise CheckWarning(
  124. 'Unable to detect version for executable {}: {}'.format(name, e),
  125. details=self._details()
  126. )
  127. def get_checks_for_module(module_name):
  128. result = []
  129. manifest = load_information_from_description_file(module_name)
  130. manifest_checks = manifest.get('environment_checkup') or {}
  131. dependencies = manifest_checks.get('dependencies') or {}
  132. for dependency in dependencies.get('python') or []:
  133. result.append(PythonDependencyCheck(module_name, dependency))
  134. for dependency in dependencies.get('external') or []:
  135. result.append(ExternalDependencyCheck(module_name, dependency))
  136. for dependency in dependencies.get('internal') or []:
  137. result.append(InternalDependencyCheck(module_name, dependency))
  138. return result
  139. def get_checks_for_module_recursive(module):
  140. class ModuleDFS(object):
  141. def __init__(self):
  142. self.visited_modules = set()
  143. self.checks = []
  144. def visit(self, module):
  145. if module.name in self.visited_modules:
  146. return
  147. self.visited_modules.add(module.name)
  148. self.checks += get_checks_for_module(module.name)
  149. for module_dependency in module.dependencies_id:
  150. if module_dependency.depend_id:
  151. self.visit(module_dependency.depend_id)
  152. return self
  153. return ModuleDFS().visit(module).checks