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.
199 lines
6.9 KiB
199 lines
6.9 KiB
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
|