Publishing 2019 R1 content
[platform/upstream/dldt.git] / model-optimizer / mo / utils / versions_checker.py
1 """
2  Copyright (c) 2018-2019 Intel Corporation
3
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 """
16
17
18 import logging as log
19 import os
20 import re
21 import sys
22 from distutils.version import LooseVersion
23
24 modules = {
25     "protobuf": "google.protobuf",
26     "test-generator": "generator",
27 }
28 critical_modules = ["networkx"]
29
30 message = "\nDetected not satisfied dependencies:\n" \
31           "{}\n" \
32           "Please install required versions of components or use install_prerequisites script\n" \
33           "{}\n" \
34           "Note that install_prerequisites scripts may install additional components."
35
36
37 def check_python_version():
38     """
39     Checks python version to be greater or equal than 3.4
40     :return: exit code (1 - error, None - successful)
41     """
42     if sys.version_info < (3, 4):
43         print('Python version should be of version 3.4 or newer')
44         return 1
45
46
47 def get_module_version_list_from_file(file_name):
48     """
49     Please do not add parameter type annotations (param:type).
50     Because we import this file while checking Python version.
51     Python 2.x will fail with no clear message on type annotations.
52
53     Reads file with requirements
54     :param file_name: Name of the requirements file
55     :return: list of tuples of strings like (name_of_module, sign, version)
56
57     File content example:
58     tensorflow>=1.2.0
59     networkx==2.1
60     numpy
61
62     Returned object is:
63     [('tensorflow', '>=', '1.2.0'), ('networkx', '==', '2.1'), ('numpy', None, None)]
64     """
65     req_dict = list()
66     with open(file_name) as f:
67         for line in f:
68             line = line.strip('\n')
69             line = line.strip(' ')
70             if line == '':
71                 continue
72             splited_line = re.split(r"==|>=|<=|>|<", line)
73             if len(splited_line) == 1:
74                 req_dict.append((splited_line[0], None, None))
75             else:
76                 if '==' in line:
77                     req_dict.append((splited_line[0], '==', splited_line[1]))
78                 elif '>=' in line:
79                     req_dict.append((splited_line[0], '>=', splited_line[1]))
80                 elif '<=' in line:
81                     req_dict.append((splited_line[0], '<=', splited_line[1]))
82                 elif '<' in line:
83                     req_dict.append((splited_line[0], '<', splited_line[1]))
84                 elif '>' in line:
85                     req_dict.append((splited_line[0], '>', splited_line[1]))
86     return req_dict
87
88
89 def version_check(name, installed_v, required_v, sign, not_satisfied_v, exit_code):
90     """
91     Please do not add parameter type annotations (param:type).
92     Because we import this file while checking Python version.
93     Python 2.x will fail with no clear message on type annotations.
94
95     Evaluates comparison of installed and required versions according to requirements file of one module.
96     If installed version does not satisfy requirements appends this module to not_stisfied_v list.
97     :param name: module name
98     :param installed_v: installed version of module
99     :param required_v: required version of module
100     :param sign: sing for comparison of required and installed versions
101     :param not_satisfied_v: list of modules with not satisfying versions
102     :param exit_code: flag of successful execution (0 - successful, 1 - error)
103     :return: exit code
104     """
105     if sign is not None:
106         req_ver = LooseVersion(required_v)
107         satisfied = False
108         if sign == '>':
109             satisfied = installed_v > req_ver
110         elif sign == '>=':
111             satisfied = installed_v >= req_ver
112         elif sign == '<=':
113             satisfied = installed_v <= req_ver
114         elif sign == '<':
115             satisfied = installed_v < req_ver
116         elif sign == '==':
117             satisfied = installed_v == req_ver
118         else:
119             log.error("Error during version comparison")
120     else:
121         satisfied = True
122     if not satisfied:
123         not_satisfied_v.append((name, 'installed: {}'.format(installed_v), 'required: {}'.format(required_v)))
124         if name in critical_modules:
125             exit_code = 1
126     return exit_code
127
128
129 def check_requirements(framework=None):
130     """
131     Please do not add parameter type annotations (param:type).
132     Because we import this file while checking Python version.
133     Python 2.x will fail with no clear message on type annotations.
134
135     Checks if installed modules versions satisfy required versions in requirements file
136     Logs a warning in case of permissible dissatisfaction
137     Logs an error in cases of critical dissatisfaction
138     :param framework: framework name
139     :return: exit code (0 - execution successful, 1 - error)
140     """
141     if framework is None:
142         framework_suffix = ""
143     else:
144         framework_suffix = "_{}".format(framework)
145     file_name = "requirements{}.txt".format(framework_suffix)
146     requirements_file = os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, file_name))
147     requirements_list = get_module_version_list_from_file(requirements_file)
148     not_satisfied_versions = []
149     exit_code = 0
150     for name, key, required_version in requirements_list:
151         try:
152             importable_name = modules.get(name, name)
153             exec("import {}".format(importable_name))
154             installed_version = sys.modules[importable_name].__version__
155             exit_code = version_check(name, installed_version, required_version, key, not_satisfied_versions, exit_code)
156             exec("del {}".format(importable_name))
157         except (AttributeError, ImportError):
158             not_satisfied_versions.append((name, 'not installed', 'required: {}'.format(required_version)))
159             exit_code = 1
160             continue
161         except Exception as e:
162             log.error('Error happened while importing {} module. It may happen due to unsatisfied requirements of '
163                       'that module. Please run requirements installation script once more.\n'
164                       'Details on module importing failure: {}'.format(name, e))
165             not_satisfied_versions.append((name, 'package error', 'required: {}'.format(required_version)))
166             exit_code = 1
167             continue
168
169     if len(not_satisfied_versions) != 0:
170         extension = 'bat' if os.name == 'nt' else 'sh'
171         install_file = 'install_prerequisites{0}.{1}'.format(framework_suffix, extension)
172         helper_command = os.path.join(os.path.dirname(requirements_file), 'install_prerequisites', install_file)
173         missed_modules_message = ""
174         for module in not_satisfied_versions:
175             missed_modules_message += "\t{}: {}, {}\n".format(module[0], module[1], module[2])
176         if exit_code:
177             log.error(message.format(missed_modules_message, helper_command))
178         else:
179             log.error(message.format(missed_modules_message, helper_command), extra={'is_warning': True})
180     return exit_code