1adf9dc25SMauro Carvalho Chehab#!/usr/bin/env python3 2adf9dc25SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0-or-later 3adf9dc25SMauro Carvalho Chehab# Copyright (c) 2017-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 4adf9dc25SMauro Carvalho Chehab 5adf9dc25SMauro Carvalho Chehab""" 6adf9dc25SMauro Carvalho ChehabHandle Python version check logic. 7adf9dc25SMauro Carvalho Chehab 8adf9dc25SMauro Carvalho ChehabNot all Python versions are supported by scripts. Yet, on some cases, 9adf9dc25SMauro Carvalho Chehablike during documentation build, a newer version of python could be 10adf9dc25SMauro Carvalho Chehabavailable. 11adf9dc25SMauro Carvalho Chehab 12adf9dc25SMauro Carvalho ChehabThis class allows checking if the minimal requirements are followed. 13adf9dc25SMauro Carvalho Chehab 14adf9dc25SMauro Carvalho ChehabBetter than that, PythonVersion.check_python() not only checks the minimal 15adf9dc25SMauro Carvalho Chehabrequirements, but it automatically switches to a the newest available 16adf9dc25SMauro Carvalho ChehabPython version if present. 17adf9dc25SMauro Carvalho Chehab 18adf9dc25SMauro Carvalho Chehab""" 19adf9dc25SMauro Carvalho Chehab 20adf9dc25SMauro Carvalho Chehabimport os 21adf9dc25SMauro Carvalho Chehabimport re 22adf9dc25SMauro Carvalho Chehabimport subprocess 2362ea383bSMauro Carvalho Chehabimport shlex 24adf9dc25SMauro Carvalho Chehabimport sys 25adf9dc25SMauro Carvalho Chehab 26adf9dc25SMauro Carvalho Chehabfrom glob import glob 2762ea383bSMauro Carvalho Chehabfrom textwrap import indent 28adf9dc25SMauro Carvalho Chehab 29adf9dc25SMauro Carvalho Chehabclass PythonVersion: 30adf9dc25SMauro Carvalho Chehab """ 31adf9dc25SMauro Carvalho Chehab Ancillary methods that checks for missing dependencies for different 32adf9dc25SMauro Carvalho Chehab types of types, like binaries, python modules, rpm deps, etc. 33adf9dc25SMauro Carvalho Chehab """ 34adf9dc25SMauro Carvalho Chehab 35adf9dc25SMauro Carvalho Chehab def __init__(self, version): 36*33220c1fSMauro Carvalho Chehab """ 37*33220c1fSMauro Carvalho Chehab Ïnitialize self.version tuple from a version string. 38*33220c1fSMauro Carvalho Chehab """ 39adf9dc25SMauro Carvalho Chehab self.version = self.parse_version(version) 40adf9dc25SMauro Carvalho Chehab 41adf9dc25SMauro Carvalho Chehab @staticmethod 42adf9dc25SMauro Carvalho Chehab def parse_version(version): 43*33220c1fSMauro Carvalho Chehab """ 44*33220c1fSMauro Carvalho Chehab Convert a major.minor.patch version into a tuple. 45*33220c1fSMauro Carvalho Chehab """ 46adf9dc25SMauro Carvalho Chehab return tuple(int(x) for x in version.split(".")) 47adf9dc25SMauro Carvalho Chehab 48adf9dc25SMauro Carvalho Chehab @staticmethod 49adf9dc25SMauro Carvalho Chehab def ver_str(version): 50*33220c1fSMauro Carvalho Chehab """ 51*33220c1fSMauro Carvalho Chehab Returns a version tuple as major.minor.patch. 52*33220c1fSMauro Carvalho Chehab """ 53adf9dc25SMauro Carvalho Chehab return ".".join([str(x) for x in version]) 54adf9dc25SMauro Carvalho Chehab 5562ea383bSMauro Carvalho Chehab @staticmethod 5662ea383bSMauro Carvalho Chehab def cmd_print(cmd, max_len=80): 57*33220c1fSMauro Carvalho Chehab """ 58*33220c1fSMauro Carvalho Chehab Outputs a command line, repecting maximum width. 59*33220c1fSMauro Carvalho Chehab """ 60*33220c1fSMauro Carvalho Chehab 6162ea383bSMauro Carvalho Chehab cmd_line = [] 6262ea383bSMauro Carvalho Chehab 6362ea383bSMauro Carvalho Chehab for w in cmd: 6462ea383bSMauro Carvalho Chehab w = shlex.quote(w) 6562ea383bSMauro Carvalho Chehab 6662ea383bSMauro Carvalho Chehab if cmd_line: 6762ea383bSMauro Carvalho Chehab if not max_len or len(cmd_line[-1]) + len(w) < max_len: 6862ea383bSMauro Carvalho Chehab cmd_line[-1] += " " + w 6962ea383bSMauro Carvalho Chehab continue 7062ea383bSMauro Carvalho Chehab else: 7162ea383bSMauro Carvalho Chehab cmd_line[-1] += " \\" 7262ea383bSMauro Carvalho Chehab cmd_line.append(w) 7362ea383bSMauro Carvalho Chehab else: 7462ea383bSMauro Carvalho Chehab cmd_line.append(w) 7562ea383bSMauro Carvalho Chehab 7662ea383bSMauro Carvalho Chehab return "\n ".join(cmd_line) 7762ea383bSMauro Carvalho Chehab 78adf9dc25SMauro Carvalho Chehab def __str__(self): 79*33220c1fSMauro Carvalho Chehab """ 80*33220c1fSMauro Carvalho Chehab Return a version tuple as major.minor.patch from self.version. 81*33220c1fSMauro Carvalho Chehab """ 82adf9dc25SMauro Carvalho Chehab return self.ver_str(self.version) 83adf9dc25SMauro Carvalho Chehab 84adf9dc25SMauro Carvalho Chehab @staticmethod 85adf9dc25SMauro Carvalho Chehab def get_python_version(cmd): 86adf9dc25SMauro Carvalho Chehab """ 87adf9dc25SMauro Carvalho Chehab Get python version from a Python binary. As we need to detect if 88adf9dc25SMauro Carvalho Chehab are out there newer python binaries, we can't rely on sys.release here. 89adf9dc25SMauro Carvalho Chehab """ 90adf9dc25SMauro Carvalho Chehab 91adf9dc25SMauro Carvalho Chehab kwargs = {} 92adf9dc25SMauro Carvalho Chehab if sys.version_info < (3, 7): 93adf9dc25SMauro Carvalho Chehab kwargs['universal_newlines'] = True 94adf9dc25SMauro Carvalho Chehab else: 95adf9dc25SMauro Carvalho Chehab kwargs['text'] = True 96adf9dc25SMauro Carvalho Chehab 97adf9dc25SMauro Carvalho Chehab result = subprocess.run([cmd, "--version"], 98adf9dc25SMauro Carvalho Chehab stdout = subprocess.PIPE, 99adf9dc25SMauro Carvalho Chehab stderr = subprocess.PIPE, 100adf9dc25SMauro Carvalho Chehab **kwargs, check=False) 101adf9dc25SMauro Carvalho Chehab 102adf9dc25SMauro Carvalho Chehab version = result.stdout.strip() 103adf9dc25SMauro Carvalho Chehab 104adf9dc25SMauro Carvalho Chehab match = re.search(r"(\d+\.\d+\.\d+)", version) 105adf9dc25SMauro Carvalho Chehab if match: 106adf9dc25SMauro Carvalho Chehab return PythonVersion.parse_version(match.group(1)) 107adf9dc25SMauro Carvalho Chehab 108adf9dc25SMauro Carvalho Chehab print(f"Can't parse version {version}") 109adf9dc25SMauro Carvalho Chehab return (0, 0, 0) 110adf9dc25SMauro Carvalho Chehab 111adf9dc25SMauro Carvalho Chehab @staticmethod 112adf9dc25SMauro Carvalho Chehab def find_python(min_version): 113adf9dc25SMauro Carvalho Chehab """ 114adf9dc25SMauro Carvalho Chehab Detect if are out there any python 3.xy version newer than the 115adf9dc25SMauro Carvalho Chehab current one. 116adf9dc25SMauro Carvalho Chehab 117adf9dc25SMauro Carvalho Chehab Note: this routine is limited to up to 2 digits for python3. We 118adf9dc25SMauro Carvalho Chehab may need to update it one day, hopefully on a distant future. 119adf9dc25SMauro Carvalho Chehab """ 120adf9dc25SMauro Carvalho Chehab patterns = [ 121adf9dc25SMauro Carvalho Chehab "python3.[0-9][0-9]", 122adf9dc25SMauro Carvalho Chehab "python3.[0-9]", 123adf9dc25SMauro Carvalho Chehab ] 124adf9dc25SMauro Carvalho Chehab 125adf9dc25SMauro Carvalho Chehab python_cmd = [] 126adf9dc25SMauro Carvalho Chehab 127adf9dc25SMauro Carvalho Chehab # Seek for a python binary newer than min_version 128adf9dc25SMauro Carvalho Chehab for path in os.getenv("PATH", "").split(":"): 129adf9dc25SMauro Carvalho Chehab for pattern in patterns: 130adf9dc25SMauro Carvalho Chehab for cmd in glob(os.path.join(path, pattern)): 131adf9dc25SMauro Carvalho Chehab if os.path.isfile(cmd) and os.access(cmd, os.X_OK): 132adf9dc25SMauro Carvalho Chehab version = PythonVersion.get_python_version(cmd) 133adf9dc25SMauro Carvalho Chehab if version >= min_version: 134adf9dc25SMauro Carvalho Chehab python_cmd.append((version, cmd)) 135adf9dc25SMauro Carvalho Chehab 136adf9dc25SMauro Carvalho Chehab return sorted(python_cmd, reverse=True) 137adf9dc25SMauro Carvalho Chehab 138adf9dc25SMauro Carvalho Chehab @staticmethod 139adf9dc25SMauro Carvalho Chehab def check_python(min_version, show_alternatives=False, bail_out=False, 140adf9dc25SMauro Carvalho Chehab success_on_error=False): 141adf9dc25SMauro Carvalho Chehab """ 142adf9dc25SMauro Carvalho Chehab Check if the current python binary satisfies our minimal requirement 143adf9dc25SMauro Carvalho Chehab for Sphinx build. If not, re-run with a newer version if found. 144adf9dc25SMauro Carvalho Chehab """ 145adf9dc25SMauro Carvalho Chehab cur_ver = sys.version_info[:3] 146adf9dc25SMauro Carvalho Chehab if cur_ver >= min_version: 147adf9dc25SMauro Carvalho Chehab ver = PythonVersion.ver_str(cur_ver) 148adf9dc25SMauro Carvalho Chehab return 149adf9dc25SMauro Carvalho Chehab 150adf9dc25SMauro Carvalho Chehab python_ver = PythonVersion.ver_str(cur_ver) 151adf9dc25SMauro Carvalho Chehab 152adf9dc25SMauro Carvalho Chehab available_versions = PythonVersion.find_python(min_version) 153adf9dc25SMauro Carvalho Chehab if not available_versions: 1545f88f44dSRandy Dunlap print(f"ERROR: Python version {python_ver} is not supported anymore\n") 155adf9dc25SMauro Carvalho Chehab print(" Can't find a new version. This script may fail") 156adf9dc25SMauro Carvalho Chehab return 157adf9dc25SMauro Carvalho Chehab 158adf9dc25SMauro Carvalho Chehab script_path = os.path.abspath(sys.argv[0]) 159adf9dc25SMauro Carvalho Chehab 160adf9dc25SMauro Carvalho Chehab # Check possible alternatives 161adf9dc25SMauro Carvalho Chehab if available_versions: 162adf9dc25SMauro Carvalho Chehab new_python_cmd = available_versions[0][1] 163adf9dc25SMauro Carvalho Chehab else: 164adf9dc25SMauro Carvalho Chehab new_python_cmd = None 165adf9dc25SMauro Carvalho Chehab 16662ea383bSMauro Carvalho Chehab if show_alternatives and available_versions: 167adf9dc25SMauro Carvalho Chehab print("You could run, instead:") 168adf9dc25SMauro Carvalho Chehab for _, cmd in available_versions: 169adf9dc25SMauro Carvalho Chehab args = [cmd, script_path] + sys.argv[1:] 170adf9dc25SMauro Carvalho Chehab 17162ea383bSMauro Carvalho Chehab cmd_str = indent(PythonVersion.cmd_print(args), " ") 17262ea383bSMauro Carvalho Chehab print(f"{cmd_str}\n") 173adf9dc25SMauro Carvalho Chehab 174adf9dc25SMauro Carvalho Chehab if bail_out: 175adf9dc25SMauro Carvalho Chehab msg = f"Python {python_ver} not supported. Bailing out" 176adf9dc25SMauro Carvalho Chehab if success_on_error: 177adf9dc25SMauro Carvalho Chehab print(msg, file=sys.stderr) 178adf9dc25SMauro Carvalho Chehab sys.exit(0) 179adf9dc25SMauro Carvalho Chehab else: 180adf9dc25SMauro Carvalho Chehab sys.exit(msg) 181adf9dc25SMauro Carvalho Chehab 182adf9dc25SMauro Carvalho Chehab print(f"Python {python_ver} not supported. Changing to {new_python_cmd}") 183adf9dc25SMauro Carvalho Chehab 184adf9dc25SMauro Carvalho Chehab # Restart script using the newer version 185adf9dc25SMauro Carvalho Chehab args = [new_python_cmd, script_path] + sys.argv[1:] 186adf9dc25SMauro Carvalho Chehab 187adf9dc25SMauro Carvalho Chehab try: 188adf9dc25SMauro Carvalho Chehab os.execv(new_python_cmd, args) 189adf9dc25SMauro Carvalho Chehab except OSError as e: 190adf9dc25SMauro Carvalho Chehab sys.exit(f"Failed to restart with {new_python_cmd}: {e}") 191