Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/lib/python/kdoc/python_version.py
38186 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0-or-later
3
# Copyright (c) 2017-2025 Mauro Carvalho Chehab <[email protected]>
4
5
"""
6
Handle Python version check logic.
7
8
Not all Python versions are supported by scripts. Yet, on some cases,
9
like during documentation build, a newer version of python could be
10
available.
11
12
This class allows checking if the minimal requirements are followed.
13
14
Better than that, PythonVersion.check_python() not only checks the minimal
15
requirements, but it automatically switches to a the newest available
16
Python version if present.
17
18
"""
19
20
import os
21
import re
22
import subprocess
23
import shlex
24
import sys
25
26
from glob import glob
27
from textwrap import indent
28
29
class PythonVersion:
30
"""
31
Ancillary methods that checks for missing dependencies for different
32
types of types, like binaries, python modules, rpm deps, etc.
33
"""
34
35
def __init__(self, version):
36
"""Ïnitialize self.version tuple from a version string"""
37
self.version = self.parse_version(version)
38
39
@staticmethod
40
def parse_version(version):
41
"""Convert a major.minor.patch version into a tuple"""
42
return tuple(int(x) for x in version.split("."))
43
44
@staticmethod
45
def ver_str(version):
46
"""Returns a version tuple as major.minor.patch"""
47
return ".".join([str(x) for x in version])
48
49
@staticmethod
50
def cmd_print(cmd, max_len=80):
51
cmd_line = []
52
53
for w in cmd:
54
w = shlex.quote(w)
55
56
if cmd_line:
57
if not max_len or len(cmd_line[-1]) + len(w) < max_len:
58
cmd_line[-1] += " " + w
59
continue
60
else:
61
cmd_line[-1] += " \\"
62
cmd_line.append(w)
63
else:
64
cmd_line.append(w)
65
66
return "\n ".join(cmd_line)
67
68
def __str__(self):
69
"""Returns a version tuple as major.minor.patch from self.version"""
70
return self.ver_str(self.version)
71
72
@staticmethod
73
def get_python_version(cmd):
74
"""
75
Get python version from a Python binary. As we need to detect if
76
are out there newer python binaries, we can't rely on sys.release here.
77
"""
78
79
kwargs = {}
80
if sys.version_info < (3, 7):
81
kwargs['universal_newlines'] = True
82
else:
83
kwargs['text'] = True
84
85
result = subprocess.run([cmd, "--version"],
86
stdout = subprocess.PIPE,
87
stderr = subprocess.PIPE,
88
**kwargs, check=False)
89
90
version = result.stdout.strip()
91
92
match = re.search(r"(\d+\.\d+\.\d+)", version)
93
if match:
94
return PythonVersion.parse_version(match.group(1))
95
96
print(f"Can't parse version {version}")
97
return (0, 0, 0)
98
99
@staticmethod
100
def find_python(min_version):
101
"""
102
Detect if are out there any python 3.xy version newer than the
103
current one.
104
105
Note: this routine is limited to up to 2 digits for python3. We
106
may need to update it one day, hopefully on a distant future.
107
"""
108
patterns = [
109
"python3.[0-9][0-9]",
110
"python3.[0-9]",
111
]
112
113
python_cmd = []
114
115
# Seek for a python binary newer than min_version
116
for path in os.getenv("PATH", "").split(":"):
117
for pattern in patterns:
118
for cmd in glob(os.path.join(path, pattern)):
119
if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
120
version = PythonVersion.get_python_version(cmd)
121
if version >= min_version:
122
python_cmd.append((version, cmd))
123
124
return sorted(python_cmd, reverse=True)
125
126
@staticmethod
127
def check_python(min_version, show_alternatives=False, bail_out=False,
128
success_on_error=False):
129
"""
130
Check if the current python binary satisfies our minimal requirement
131
for Sphinx build. If not, re-run with a newer version if found.
132
"""
133
cur_ver = sys.version_info[:3]
134
if cur_ver >= min_version:
135
ver = PythonVersion.ver_str(cur_ver)
136
return
137
138
python_ver = PythonVersion.ver_str(cur_ver)
139
140
available_versions = PythonVersion.find_python(min_version)
141
if not available_versions:
142
print(f"ERROR: Python version {python_ver} is not supported anymore\n")
143
print(" Can't find a new version. This script may fail")
144
return
145
146
script_path = os.path.abspath(sys.argv[0])
147
148
# Check possible alternatives
149
if available_versions:
150
new_python_cmd = available_versions[0][1]
151
else:
152
new_python_cmd = None
153
154
if show_alternatives and available_versions:
155
print("You could run, instead:")
156
for _, cmd in available_versions:
157
args = [cmd, script_path] + sys.argv[1:]
158
159
cmd_str = indent(PythonVersion.cmd_print(args), " ")
160
print(f"{cmd_str}\n")
161
162
if bail_out:
163
msg = f"Python {python_ver} not supported. Bailing out"
164
if success_on_error:
165
print(msg, file=sys.stderr)
166
sys.exit(0)
167
else:
168
sys.exit(msg)
169
170
print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
171
172
# Restart script using the newer version
173
args = [new_python_cmd, script_path] + sys.argv[1:]
174
175
try:
176
os.execv(new_python_cmd, args)
177
except OSError as e:
178
sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
179
180