|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# SPDX-License-Identifier: BSD-3-Clause |
| 3 | +# Copyright(c) 2024 PANTHEON.tech s.r.o. |
| 4 | +# |
| 5 | +"""Utilities for DTS dependencies. |
| 6 | +
|
| 7 | +The module can be used as an executable script, |
| 8 | +which verifies that the running Python version meets the version requirement of DTS. |
| 9 | +The script exits with the standard exit codes in this mode (0 is success, 1 is failure). |
| 10 | +
|
| 11 | +The module also contains a function, get_missing_imports, |
| 12 | +which looks for runtime dependencies in the DTS pyproject.toml file |
| 13 | +and returns a list of module names used in an import statement (import packages) that are missing. |
| 14 | +This function is not used when the module is run as a script and is available to be imported. |
| 15 | +""" |
| 16 | + |
| 17 | +import configparser |
| 18 | +import importlib.metadata |
| 19 | +import importlib.util |
| 20 | +import os.path |
| 21 | +import platform |
| 22 | + |
| 23 | +from packaging.version import Version |
| 24 | + |
| 25 | +_VERSION_COMPARISON_CHARS = '^<>=' |
| 26 | +# The names of packages used in import statements may be different from distribution package names. |
| 27 | +# We get distribution package names from pyproject.toml. |
| 28 | +# _EXTRA_DEPS adds those import names which don't match their distribution package name. |
| 29 | +_EXTRA_DEPS = { |
| 30 | + 'invoke': {'version': '>=1.3'}, |
| 31 | + 'paramiko': {'version': '>=2.4'}, |
| 32 | + 'PyYAML': {'version': '^6.0', 'import_package': 'yaml'} |
| 33 | +} |
| 34 | +_DPDK_ROOT = os.path.dirname(os.path.dirname(__file__)) |
| 35 | +_DTS_DEP_FILE_PATH = os.path.join(_DPDK_ROOT, 'dts', 'pyproject.toml') |
| 36 | + |
| 37 | + |
| 38 | +def _get_dependencies(cfg_file_path): |
| 39 | + cfg = configparser.ConfigParser() |
| 40 | + with open(cfg_file_path) as f: |
| 41 | + dts_deps_file_str = f.read() |
| 42 | + dts_deps_file_str = dts_deps_file_str.replace("\n]", "]") |
| 43 | + cfg.read_string(dts_deps_file_str) |
| 44 | + |
| 45 | + deps_section = cfg['tool.poetry.dependencies'] |
| 46 | + return {dep: {'version': deps_section[dep].strip('"\'')} for dep in deps_section} |
| 47 | + |
| 48 | + |
| 49 | +def get_missing_imports(): |
| 50 | + """Get missing DTS import packages from third party libraries. |
| 51 | +
|
| 52 | + Scan the DTS pyproject.toml file for dependencies and find those that are not installed. |
| 53 | + The dependencies in pyproject.toml are listed by their distribution package names, |
| 54 | + but the function finds the associated import packages - those used in import statements. |
| 55 | +
|
| 56 | + The function is not used when the module is run as a script. It should be imported. |
| 57 | +
|
| 58 | + Returns: |
| 59 | + A list of missing import packages. |
| 60 | + """ |
| 61 | + missing_imports = [] |
| 62 | + req_deps = _get_dependencies(_DTS_DEP_FILE_PATH) |
| 63 | + req_deps.pop('python') |
| 64 | + |
| 65 | + for req_dep, dep_data in (req_deps | _EXTRA_DEPS).items(): |
| 66 | + req_ver = dep_data['version'] |
| 67 | + try: |
| 68 | + import_package = dep_data['import_package'] |
| 69 | + except KeyError: |
| 70 | + import_package = req_dep |
| 71 | + import_package = import_package.lower().replace('-', '_') |
| 72 | + |
| 73 | + try: |
| 74 | + req_ver = Version(req_ver.strip(_VERSION_COMPARISON_CHARS)) |
| 75 | + found_dep_ver = Version(importlib.metadata.version(req_dep)) |
| 76 | + if found_dep_ver < req_ver: |
| 77 | + print( |
| 78 | + f'The version "{found_dep_ver}" of package "{req_dep}" ' |
| 79 | + f'is lower than required "{req_ver}".' |
| 80 | + ) |
| 81 | + except importlib.metadata.PackageNotFoundError: |
| 82 | + print(f'Package "{req_dep}" not found.') |
| 83 | + missing_imports.append(import_package) |
| 84 | + |
| 85 | + return missing_imports |
| 86 | + |
| 87 | + |
| 88 | +if __name__ == '__main__': |
| 89 | + python_version = _get_dependencies(_DTS_DEP_FILE_PATH).pop('python') |
| 90 | + if python_version: |
| 91 | + sys_ver = Version(platform.python_version()) |
| 92 | + req_ver = Version(python_version['version'].strip(_VERSION_COMPARISON_CHARS)) |
| 93 | + if sys_ver < req_ver: |
| 94 | + print( |
| 95 | + f'The available Python version "{sys_ver}" is lower than required "{req_ver}".' |
| 96 | + ) |
| 97 | + exit(1) |
0 commit comments