OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 """Helper to check for availability and version of dependencies.""" |
| 3 |
| 4 from __future__ import print_function |
| 5 import re |
| 6 |
| 7 try: |
| 8 import ConfigParser as configparser |
| 9 except ImportError: |
| 10 import configparser # pylint: disable=import-error |
| 11 |
| 12 |
| 13 class DependencyDefinition(object): |
| 14 """Dependency definition. |
| 15 |
| 16 Attributes: |
| 17 dpkg_name (str): name of the dpkg package that provides the dependency. |
| 18 is_optional (bool): True if the dependency is optional. |
| 19 l2tbinaries_name (str): name of the l2tbinaries package that provides |
| 20 the dependency. |
| 21 maximum_version (str): maximum supported version. |
| 22 minimum_version (str): minimum supported version. |
| 23 name (str): name of (the Python module that provides) the dependency. |
| 24 pypi_name (str): name of the PyPI package that provides the dependency. |
| 25 rpm_name (str): name of the rpm package that provides the dependency. |
| 26 version_property (str): name of the version attribute or function. |
| 27 """ |
| 28 |
| 29 def __init__(self, name): |
| 30 """Initializes a dependency configuation. |
| 31 |
| 32 Args: |
| 33 name (str): name of the dependency. |
| 34 """ |
| 35 super(DependencyDefinition, self).__init__() |
| 36 self.dpkg_name = None |
| 37 self.is_optional = False |
| 38 self.l2tbinaries_name = None |
| 39 self.maximum_version = None |
| 40 self.minimum_version = None |
| 41 self.name = name |
| 42 self.pypi_name = None |
| 43 self.rpm_name = None |
| 44 self.version_property = None |
| 45 |
| 46 |
| 47 class DependencyDefinitionReader(object): |
| 48 """Dependency definition reader.""" |
| 49 |
| 50 _VALUE_NAMES = frozenset([ |
| 51 u'dpkg_name', |
| 52 u'is_optional', |
| 53 u'l2tbinaries_name', |
| 54 u'maximum_version', |
| 55 u'minimum_version', |
| 56 u'pypi_name', |
| 57 u'rpm_name', |
| 58 u'version_property']) |
| 59 |
| 60 def _GetConfigValue(self, config_parser, section_name, value_name): |
| 61 """Retrieves a value from the config parser. |
| 62 |
| 63 Args: |
| 64 config_parser (ConfigParser): configuration parser. |
| 65 section_name (str): name of the section that contains the value. |
| 66 value_name (str): name of the value. |
| 67 |
| 68 Returns: |
| 69 object: value or None if the value does not exists. |
| 70 """ |
| 71 try: |
| 72 return config_parser.get(section_name, value_name) |
| 73 except configparser.NoOptionError: |
| 74 return |
| 75 |
| 76 def Read(self, file_object): |
| 77 """Reads dependency definitions. |
| 78 |
| 79 Args: |
| 80 file_object (file): file-like object to read from. |
| 81 |
| 82 Yields: |
| 83 DependencyDefinition: dependency definition. |
| 84 """ |
| 85 config_parser = configparser.RawConfigParser() |
| 86 config_parser.readfp(file_object) |
| 87 |
| 88 for section_name in config_parser.sections(): |
| 89 dependency_definition = DependencyDefinition(section_name) |
| 90 for value_name in self._VALUE_NAMES: |
| 91 value = self._GetConfigValue(config_parser, section_name, value_name) |
| 92 setattr(dependency_definition, value_name, value) |
| 93 |
| 94 yield dependency_definition |
| 95 |
| 96 |
| 97 class DependencyHelper(object): |
| 98 """Dependency helper.""" |
| 99 |
| 100 _VERSION_SPLIT_REGEX = re.compile(r'\.|\-') |
| 101 |
| 102 def __init__(self): |
| 103 """Initializes a dependency helper.""" |
| 104 super(DependencyHelper, self).__init__() |
| 105 self._dependencies = {} |
| 106 self._test_dependencies = {} |
| 107 |
| 108 dependency_reader = DependencyDefinitionReader() |
| 109 |
| 110 with open(u'dependencies.ini', 'r') as file_object: |
| 111 for dependency in dependency_reader.Read(file_object): |
| 112 self._dependencies[dependency.name] = dependency |
| 113 |
| 114 dependency = DependencyDefinition(u'mock') |
| 115 dependency.minimum_version = u'0.7.1' |
| 116 dependency.version_property = u'__version__' |
| 117 self._test_dependencies[u'mock'] = dependency |
| 118 |
| 119 def _CheckPythonModule(self, dependency): |
| 120 """Checks the availability of a Python module. |
| 121 |
| 122 Args: |
| 123 dependency (DependencyDefinition): dependency definition. |
| 124 |
| 125 Returns: |
| 126 tuple: consists: |
| 127 |
| 128 bool: True if the Python module is available and conforms to |
| 129 the minimum required version, False otherwise. |
| 130 str: status message. |
| 131 """ |
| 132 module_object = self._ImportPythonModule(dependency.name) |
| 133 if not module_object: |
| 134 status_message = u'missing: {0:s}'.format(dependency.name) |
| 135 return dependency.is_optional, status_message |
| 136 |
| 137 if not dependency.version_property or not dependency.minimum_version: |
| 138 return True, dependency.name |
| 139 |
| 140 return self._CheckPythonModuleVersion( |
| 141 dependency.name, module_object, dependency.version_property, |
| 142 dependency.minimum_version, dependency.maximum_version) |
| 143 |
| 144 def _CheckPythonModuleVersion( |
| 145 self, module_name, module_object, version_property, minimum_version, |
| 146 maximum_version): |
| 147 """Checks the version of a Python module. |
| 148 |
| 149 Args: |
| 150 module_object (module): Python module. |
| 151 module_name (str): name of the Python module. |
| 152 version_property (str): version attribute or function. |
| 153 minimum_version (str): minimum version. |
| 154 maximum_version (str): maximum version. |
| 155 |
| 156 Returns: |
| 157 tuple: consists: |
| 158 |
| 159 bool: True if the Python module is available and conforms to |
| 160 the minimum required version, False otherwise. |
| 161 str: status message. |
| 162 """ |
| 163 module_version = None |
| 164 if not version_property.endswith(u'()'): |
| 165 module_version = getattr(module_object, version_property, None) |
| 166 else: |
| 167 version_method = getattr( |
| 168 module_object, version_property[:-2], None) |
| 169 if version_method: |
| 170 module_version = version_method() |
| 171 |
| 172 if not module_version: |
| 173 status_message = ( |
| 174 u'unable to determine version information for: {0:s}').format( |
| 175 module_name) |
| 176 return False, status_message |
| 177 |
| 178 # Make sure the module version is a string. |
| 179 module_version = u'{0!s}'.format(module_version) |
| 180 |
| 181 # Split the version string and convert every digit into an integer. |
| 182 # A string compare of both version strings will yield an incorrect result. |
| 183 module_version_map = list( |
| 184 map(int, self._VERSION_SPLIT_REGEX.split(module_version))) |
| 185 minimum_version_map = list( |
| 186 map(int, self._VERSION_SPLIT_REGEX.split(minimum_version))) |
| 187 |
| 188 if module_version_map < minimum_version_map: |
| 189 status_message = ( |
| 190 u'{0:s} version: {1!s} is too old, {2!s} or later required').format( |
| 191 module_name, module_version, minimum_version) |
| 192 return False, status_message |
| 193 |
| 194 if maximum_version: |
| 195 maximum_version_map = list( |
| 196 map(int, self._VERSION_SPLIT_REGEX.split(maximum_version))) |
| 197 if module_version_map > maximum_version_map: |
| 198 status_message = ( |
| 199 u'{0:s} version: {1!s} is too recent, {2!s} or earlier ' |
| 200 u'required').format(module_name, module_version, maximum_version) |
| 201 return False, status_message |
| 202 |
| 203 status_message = u'{0:s} version: {1!s}'.format(module_name, module_version) |
| 204 return True, status_message |
| 205 |
| 206 def _CheckSQLite3(self): |
| 207 """Checks the availability of sqlite3. |
| 208 |
| 209 Returns: |
| 210 tuple: consists: |
| 211 |
| 212 bool: True if the Python module is available and conforms to |
| 213 the minimum required version, False otherwise. |
| 214 str: status message. |
| 215 """ |
| 216 # On Windows sqlite3 can be provided by both pysqlite2.dbapi2 and |
| 217 # sqlite3. sqlite3 is provided with the Python installation and |
| 218 # pysqlite2.dbapi2 by the pysqlite2 Python module. Typically |
| 219 # pysqlite2.dbapi2 would contain a newer version of sqlite3, hence |
| 220 # we check for its presence first. |
| 221 module_name = u'pysqlite2.dbapi2' |
| 222 minimum_version = u'3.7.8' |
| 223 |
| 224 module_object = self._ImportPythonModule(module_name) |
| 225 if not module_object: |
| 226 module_name = u'sqlite3' |
| 227 |
| 228 module_object = self._ImportPythonModule(module_name) |
| 229 if not module_object: |
| 230 status_message = u'missing: {0:s}.'.format(module_name) |
| 231 return False, status_message |
| 232 |
| 233 return self._CheckPythonModuleVersion( |
| 234 module_name, module_object, u'sqlite_version', minimum_version, None) |
| 235 |
| 236 def _ImportPythonModule(self, module_name): |
| 237 """Imports a Python module. |
| 238 |
| 239 Args: |
| 240 module_name (str): name of the module. |
| 241 |
| 242 Returns: |
| 243 module: Python module or None if the module cannot be imported. |
| 244 """ |
| 245 try: |
| 246 module_object = list(map(__import__, [module_name]))[0] |
| 247 except ImportError: |
| 248 return |
| 249 |
| 250 # If the module name contains dots get the upper most module object. |
| 251 if u'.' in module_name: |
| 252 for submodule_name in module_name.split(u'.')[1:]: |
| 253 module_object = getattr(module_object, submodule_name, None) |
| 254 |
| 255 return module_object |
| 256 |
| 257 def _PrintCheckDependencyStatus( |
| 258 self, dependency, result, status_message, verbose_output=True): |
| 259 """Prints the check dependency status. |
| 260 |
| 261 Args: |
| 262 dependency (DependencyDefinition): dependency definition. |
| 263 result (bool): True if the Python module is available and conforms to |
| 264 the minimum required version, False otherwise. |
| 265 status_message (str): status message. |
| 266 """ |
| 267 if not result or dependency.is_optional: |
| 268 if dependency.is_optional: |
| 269 status_indicator = u'[OPTIONAL]' |
| 270 else: |
| 271 status_indicator = u'[FAILURE]' |
| 272 |
| 273 print(u'{0:s}\t{1:s}.'.format(status_indicator, status_message)) |
| 274 |
| 275 elif verbose_output: |
| 276 print(u'[OK]\t\t{0:s}'.format(status_message)) |
| 277 |
| 278 def CheckDependencies(self, verbose_output=True): |
| 279 """Checks the availability of the dependencies. |
| 280 |
| 281 Args: |
| 282 verbose_output (Optional[bool]): True if output should be verbose. |
| 283 |
| 284 Returns: |
| 285 bool: True if the dependencies are available, False otherwise. |
| 286 """ |
| 287 print(u'Checking availability and versions of dependencies.') |
| 288 check_result = True |
| 289 |
| 290 for module_name, dependency in sorted(self._dependencies.items()): |
| 291 if module_name == u'sqlite3': |
| 292 result, status_message = self._CheckSQLite3() |
| 293 else: |
| 294 result, status_message = self._CheckPythonModule(dependency) |
| 295 |
| 296 if not result: |
| 297 check_result = False |
| 298 |
| 299 self._PrintCheckDependencyStatus( |
| 300 dependency, result, status_message, verbose_output=verbose_output) |
| 301 |
| 302 if check_result and not verbose_output: |
| 303 print(u'[OK]') |
| 304 |
| 305 print(u'') |
| 306 return check_result |
| 307 |
| 308 def CheckTestDependencies(self, verbose_output=True): |
| 309 """Checks the availability of the dependencies when running tests. |
| 310 |
| 311 Args: |
| 312 verbose_output (Optional[bool]): True if output should be verbose. |
| 313 |
| 314 Returns: |
| 315 bool: True if the dependencies are available, False otherwise. |
| 316 """ |
| 317 if not self.CheckDependencies(verbose_output=verbose_output): |
| 318 return False |
| 319 |
| 320 print(u'Checking availability and versions of test dependencies.') |
| 321 check_result = True |
| 322 |
| 323 for dependency in sorted( |
| 324 self._test_dependencies.values(), |
| 325 key=lambda dependency: dependency.name): |
| 326 result, status_message = self._CheckPythonModule(dependency) |
| 327 if not result: |
| 328 check_result = False |
| 329 |
| 330 self._PrintCheckDependencyStatus( |
| 331 dependency, result, status_message, verbose_output=verbose_output) |
| 332 |
| 333 if check_result and not verbose_output: |
| 334 print(u'[OK]') |
| 335 |
| 336 print(u'') |
| 337 return check_result |
| 338 |
| 339 def GetDPKGDepends(self, exclude_version=False): |
| 340 """Retrieves the DPKG control file installation requirements. |
| 341 |
| 342 Args: |
| 343 exclude_version (Optional[bool]): True if the version should be excluded |
| 344 from the dependency definitions. |
| 345 |
| 346 Returns: |
| 347 list[str]: dependency definitions for requires for DPKG control file. |
| 348 """ |
| 349 requires = [] |
| 350 for dependency in sorted( |
| 351 self._dependencies.values(), key=lambda dependency: dependency.name): |
| 352 module_name = dependency.dpkg_name or dependency.name |
| 353 |
| 354 if exclude_version or not dependency.minimum_version: |
| 355 requires_string = module_name |
| 356 else: |
| 357 requires_string = u'{0:s} (>= {1:s})'.format( |
| 358 module_name, dependency.minimum_version) |
| 359 |
| 360 requires.append(requires_string) |
| 361 |
| 362 return sorted(requires) |
| 363 |
| 364 def GetL2TBinaries(self): |
| 365 """Retrieves the l2tbinaries requirements. |
| 366 |
| 367 Returns: |
| 368 list[str]: dependency definitions for l2tbinaries. |
| 369 """ |
| 370 requires = [] |
| 371 for dependency in sorted( |
| 372 self._dependencies.values(), key=lambda dependency: dependency.name): |
| 373 module_name = dependency.l2tbinaries_name or dependency.name |
| 374 |
| 375 requires.append(module_name) |
| 376 |
| 377 return sorted(requires) |
| 378 |
| 379 def GetInstallRequires(self): |
| 380 """Retrieves the setup.py installation requirements. |
| 381 |
| 382 Returns: |
| 383 list[str]: dependency definitions for install_requires for setup.py. |
| 384 """ |
| 385 install_requires = [] |
| 386 for dependency in sorted( |
| 387 self._dependencies.values(), key=lambda dependency: dependency.name): |
| 388 module_name = dependency.pypi_name or dependency.name |
| 389 |
| 390 if module_name == u'efilter': |
| 391 requires_string = u'{0:s} >= 1-{1!s}'.format( |
| 392 module_name, dependency.minimum_version) |
| 393 install_requires.append(requires_string) |
| 394 continue |
| 395 |
| 396 # Use the sqlite3 module provided by the standard library. |
| 397 if module_name == u'pysqlite': |
| 398 continue |
| 399 |
| 400 if not dependency.minimum_version: |
| 401 requires_string = module_name |
| 402 elif not dependency.maximum_version: |
| 403 requires_string = u'{0:s} >= {1!s}'.format( |
| 404 module_name, dependency.minimum_version) |
| 405 else: |
| 406 requires_string = u'{0:s} >= {1!s},<= {2!s}'.format( |
| 407 module_name, dependency.minimum_version, dependency.maximum_version) |
| 408 |
| 409 install_requires.append(requires_string) |
| 410 |
| 411 return sorted(install_requires) |
| 412 |
| 413 def GetRPMRequires(self, exclude_version=False): |
| 414 """Retrieves the setup.cfg RPM installation requirements. |
| 415 |
| 416 Args: |
| 417 exclude_version (Optional[bool]): True if the version should be excluded |
| 418 from the dependency definitions. |
| 419 |
| 420 Returns: |
| 421 list[str]: dependency definitions for requires for setup.cfg. |
| 422 """ |
| 423 requires = [] |
| 424 for dependency in sorted( |
| 425 self._dependencies.values(), key=lambda dependency: dependency.name): |
| 426 module_name = dependency.rpm_name or dependency.name |
| 427 |
| 428 if exclude_version or not dependency.minimum_version: |
| 429 requires_string = module_name |
| 430 else: |
| 431 requires_string = u'{0:s} >= {1:s}'.format( |
| 432 module_name, dependency.minimum_version) |
| 433 |
| 434 requires.append(requires_string) |
| 435 |
| 436 return sorted(requires) |
OLD | NEW |