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