xref: /dpdk/buildtools/check-dts-requirements.py (revision 7f9326423a045f7346a459280dc98fed4afd6811)
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
7The module can be used as an executable script,
8which verifies that the running Python version meets the version requirement of DTS.
9The script exits with the standard exit codes in this mode (0 is success, 1 is failure).
10
11The module also contains a function, get_missing_imports,
12which looks for runtime dependencies in the DTS pyproject.toml file
13and returns a list of module names used in an import statement (import packages) that are missing.
14This function is not used when the module is run as a script and is available to be imported.
15"""
16
17import configparser
18import importlib.metadata
19import importlib.util
20import os.path
21import platform
22
23from 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
38def _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
49def 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
88if __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)
98