Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/lib/python/kdoc/python_version.py
122941 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
"""
37
Ïnitialize self.version tuple from a version string.
38
"""
39
self.version = self.parse_version(version)
40
41
@staticmethod
42
def parse_version(version):
43
"""
44
Convert a major.minor.patch version into a tuple.
45
"""
46
return tuple(int(x) for x in version.split("."))
47
48
@staticmethod
49
def ver_str(version):
50
"""
51
Returns a version tuple as major.minor.patch.
52
"""
53
return ".".join([str(x) for x in version])
54
55
@staticmethod
56
def cmd_print(cmd, max_len=80):
57
"""
58
Outputs a command line, repecting maximum width.
59
"""
60
61
cmd_line = []
62
63
for w in cmd:
64
w = shlex.quote(w)
65
66
if cmd_line:
67
if not max_len or len(cmd_line[-1]) + len(w) < max_len:
68
cmd_line[-1] += " " + w
69
continue
70
else:
71
cmd_line[-1] += " \\"
72
cmd_line.append(w)
73
else:
74
cmd_line.append(w)
75
76
return "\n ".join(cmd_line)
77
78
def __str__(self):
79
"""
80
Return a version tuple as major.minor.patch from self.version.
81
"""
82
return self.ver_str(self.version)
83
84
@staticmethod
85
def get_python_version(cmd):
86
"""
87
Get python version from a Python binary. As we need to detect if
88
are out there newer python binaries, we can't rely on sys.release here.
89
"""
90
91
kwargs = {}
92
if sys.version_info < (3, 7):
93
kwargs['universal_newlines'] = True
94
else:
95
kwargs['text'] = True
96
97
result = subprocess.run([cmd, "--version"],
98
stdout = subprocess.PIPE,
99
stderr = subprocess.PIPE,
100
**kwargs, check=False)
101
102
version = result.stdout.strip()
103
104
match = re.search(r"(\d+\.\d+\.\d+)", version)
105
if match:
106
return PythonVersion.parse_version(match.group(1))
107
108
print(f"Can't parse version {version}")
109
return (0, 0, 0)
110
111
@staticmethod
112
def find_python(min_version):
113
"""
114
Detect if are out there any python 3.xy version newer than the
115
current one.
116
117
Note: this routine is limited to up to 2 digits for python3. We
118
may need to update it one day, hopefully on a distant future.
119
"""
120
patterns = [
121
"python3.[0-9][0-9]",
122
"python3.[0-9]",
123
]
124
125
python_cmd = []
126
127
# Seek for a python binary newer than min_version
128
for path in os.getenv("PATH", "").split(":"):
129
for pattern in patterns:
130
for cmd in glob(os.path.join(path, pattern)):
131
if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
132
version = PythonVersion.get_python_version(cmd)
133
if version >= min_version:
134
python_cmd.append((version, cmd))
135
136
return sorted(python_cmd, reverse=True)
137
138
@staticmethod
139
def check_python(min_version, show_alternatives=False, bail_out=False,
140
success_on_error=False):
141
"""
142
Check if the current python binary satisfies our minimal requirement
143
for Sphinx build. If not, re-run with a newer version if found.
144
"""
145
cur_ver = sys.version_info[:3]
146
if cur_ver >= min_version:
147
ver = PythonVersion.ver_str(cur_ver)
148
return
149
150
python_ver = PythonVersion.ver_str(cur_ver)
151
152
available_versions = PythonVersion.find_python(min_version)
153
if not available_versions:
154
print(f"ERROR: Python version {python_ver} is not supported anymore\n")
155
print(" Can't find a new version. This script may fail")
156
return
157
158
script_path = os.path.abspath(sys.argv[0])
159
160
# Check possible alternatives
161
if available_versions:
162
new_python_cmd = available_versions[0][1]
163
else:
164
new_python_cmd = None
165
166
if show_alternatives and available_versions:
167
print("You could run, instead:")
168
for _, cmd in available_versions:
169
args = [cmd, script_path] + sys.argv[1:]
170
171
cmd_str = indent(PythonVersion.cmd_print(args), " ")
172
print(f"{cmd_str}\n")
173
174
if bail_out:
175
msg = f"Python {python_ver} not supported. Bailing out"
176
if success_on_error:
177
print(msg, file=sys.stderr)
178
sys.exit(0)
179
else:
180
sys.exit(msg)
181
182
print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
183
184
# Restart script using the newer version
185
args = [new_python_cmd, script_path] + sys.argv[1:]
186
187
try:
188
os.execv(new_python_cmd, args)
189
except OSError as e:
190
sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
191
192