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.

181 lines
6.5 KiB

  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. try:
  31. parsed_version = map(int, version.split('.'))
  32. except ValueError:
  33. raise CheckFail(
  34. 'Invalid version expression',
  35. details = """
  36. Allowed expressions are <pre>=x.y.z</pre>, <pre>&gt;=x.y.z</pre>, <pre>^x.z.y</pre>,
  37. <pre>~x.y.z. Got <pre>{}</pre>""".format(cgi.escape(self.dependency['version']))
  38. )
  39. parsed_installed_version = map(int, installed_version.split('.'))
  40. parsed_version.extend(0 for _ in range(len(parsed_installed_version) - len(parsed_version)))
  41. parsed_installed_version.extend(0 for _ in range(len(parsed_version) - len(parsed_installed_version)))
  42. if version_operator == '^':
  43. if parsed_installed_version[:1] != parsed_version[:1]:
  44. return False
  45. version_operator = '>='
  46. elif version_operator == '~':
  47. if parsed_installed_version[:2] != parsed_version[:2]:
  48. return False
  49. version_operator = '>='
  50. if version_operator == '>=':
  51. return tuple(parsed_installed_version) >= tuple(parsed_version)
  52. elif version_operator == '=':
  53. return tuple(parsed_installed_version) == tuple(parsed_version)
  54. assert False
  55. def _run(self, env):
  56. name = self.dependency['name']
  57. if not self._dependency_installed(env, name):
  58. raise CheckFail(
  59. 'Required {} - {} - is not installed.'.format(self.dependency_type, name),
  60. details=self._details()
  61. )
  62. if 'version' in self.dependency:
  63. version_expression = self.dependency['version']
  64. installed_version = self._installed_version(env, name)
  65. if not self.__has_required_version(installed_version, version_expression):
  66. raise CheckWarning(
  67. 'Required {} - {} - has version {}, but {} is needed.'.format(
  68. self.dependency_type,
  69. name,
  70. installed_version,
  71. version_expression
  72. ),
  73. details=self._details()
  74. )
  75. return CheckSuccess(
  76. 'Required {} - {} - is installed.'.format(self.dependency_type, name),
  77. details=self._details()
  78. )
  79. class InternalDependencyCheck(DependencyCheck):
  80. dependency_type = 'Odoo module'
  81. def _dependency_installed(self, env, name):
  82. return name in env.registry._init_modules
  83. def _installed_version(self, env, name):
  84. return env['ir.module.module'].sudo().search([('name', '=', name)]).latest_version
  85. class PythonDependencyCheck(DependencyCheck):
  86. dependency_type = 'Python module'
  87. def _dependency_installed(self, env, name):
  88. try:
  89. __import__(name)
  90. return True
  91. except ImportError:
  92. return False
  93. def _installed_version(self, env, name):
  94. try:
  95. return __import__(name).__version__
  96. except AttributeError:
  97. raise CheckWarning(
  98. 'Could not detect version of the Python module: {}.'.format(name),
  99. details=self._details()
  100. )
  101. class ExternalDependencyCheck(DependencyCheck):
  102. dependency_type = 'system executable'
  103. def _dependency_installed(self, env, name):
  104. try:
  105. which(name)
  106. return True
  107. except IOError:
  108. return False
  109. def _installed_version(self, env, name):
  110. try:
  111. exe = which(name)
  112. out = subprocess.check_output([exe, '--version'])
  113. match = re.search('[\d.]+', out)
  114. if not match:
  115. raise CheckWarning(
  116. 'Unable to detect version for executable {}'.format(name),
  117. details="Command {} --version returned <pre>{}</pre>".format(exe, out)
  118. )
  119. return match.group(0)
  120. except subprocess.CalledProcessError as e:
  121. raise CheckWarning(
  122. 'Unable to detect version for executable {}: {}'.format(name, e),
  123. details=self._details()
  124. )
  125. def get_checks_for_module(module_name):
  126. result = []
  127. manifest = load_information_from_description_file(module_name)
  128. manifest_checks = manifest.get('environment_checkup') or {}
  129. dependencies = manifest_checks.get('dependencies') or {}
  130. for dependency in dependencies.get('python') or []:
  131. result.append(PythonDependencyCheck(module_name, dependency))
  132. for dependency in dependencies.get('external') or []:
  133. result.append(ExternalDependencyCheck(module_name, dependency))
  134. for dependency in dependencies.get('internal') or []:
  135. result.append(InternalDependencyCheck(module_name, dependency))
  136. return result
  137. def get_checks_for_module_recursive(module):
  138. class ModuleDFS(object):
  139. def __init__(self):
  140. self.visited_modules = set()
  141. self.checks = []
  142. def visit(self, module):
  143. if module.name in self.visited_modules:
  144. return
  145. self.visited_modules.add(module.name)
  146. self.checks += get_checks_for_module(module.name)
  147. for module_dependency in module.dependencies_id:
  148. if module_dependency.depend_id:
  149. self.visit(module_dependency.depend_id)
  150. return self
  151. return ModuleDFS().visit(module).checks