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