mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
tools/docs: python_version: move version check from sphinx-pre-install
The sphinx-pre-install code has some logic to deal with Python version, which ensures that a minimal version will be enforced for documentation build logic. Move it to a separate library to allow re-using its code. No functional changes. Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> Message-ID: <d134ace64b55c827565ce68f0527e20c735f0d2e.1758196090.git.mchehab+huawei@kernel.org> Signed-off-by: Jonathan Corbet <corbet@lwn.net>
This commit is contained in:
committed by
Jonathan Corbet
parent
3f835cb123
commit
adf9dc2592
158
tools/docs/lib/python_version.py
Normal file
158
tools/docs/lib/python_version.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Copyright (c) 2017-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
|
||||
|
||||
"""
|
||||
Handle Python version check logic.
|
||||
|
||||
Not all Python versions are supported by scripts. Yet, on some cases,
|
||||
like during documentation build, a newer version of python could be
|
||||
available.
|
||||
|
||||
This class allows checking if the minimal requirements are followed.
|
||||
|
||||
Better than that, PythonVersion.check_python() not only checks the minimal
|
||||
requirements, but it automatically switches to a the newest available
|
||||
Python version if present.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from glob import glob
|
||||
|
||||
class PythonVersion:
|
||||
"""
|
||||
Ancillary methods that checks for missing dependencies for different
|
||||
types of types, like binaries, python modules, rpm deps, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, version):
|
||||
"""Ïnitialize self.version tuple from a version string"""
|
||||
self.version = self.parse_version(version)
|
||||
|
||||
@staticmethod
|
||||
def parse_version(version):
|
||||
"""Convert a major.minor.patch version into a tuple"""
|
||||
return tuple(int(x) for x in version.split("."))
|
||||
|
||||
@staticmethod
|
||||
def ver_str(version):
|
||||
"""Returns a version tuple as major.minor.patch"""
|
||||
return ".".join([str(x) for x in version])
|
||||
|
||||
def __str__(self):
|
||||
"""Returns a version tuple as major.minor.patch from self.version"""
|
||||
return self.ver_str(self.version)
|
||||
|
||||
@staticmethod
|
||||
def get_python_version(cmd):
|
||||
"""
|
||||
Get python version from a Python binary. As we need to detect if
|
||||
are out there newer python binaries, we can't rely on sys.release here.
|
||||
"""
|
||||
|
||||
kwargs = {}
|
||||
if sys.version_info < (3, 7):
|
||||
kwargs['universal_newlines'] = True
|
||||
else:
|
||||
kwargs['text'] = True
|
||||
|
||||
result = subprocess.run([cmd, "--version"],
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
**kwargs, check=False)
|
||||
|
||||
version = result.stdout.strip()
|
||||
|
||||
match = re.search(r"(\d+\.\d+\.\d+)", version)
|
||||
if match:
|
||||
return PythonVersion.parse_version(match.group(1))
|
||||
|
||||
print(f"Can't parse version {version}")
|
||||
return (0, 0, 0)
|
||||
|
||||
@staticmethod
|
||||
def find_python(min_version):
|
||||
"""
|
||||
Detect if are out there any python 3.xy version newer than the
|
||||
current one.
|
||||
|
||||
Note: this routine is limited to up to 2 digits for python3. We
|
||||
may need to update it one day, hopefully on a distant future.
|
||||
"""
|
||||
patterns = [
|
||||
"python3.[0-9][0-9]",
|
||||
"python3.[0-9]",
|
||||
]
|
||||
|
||||
python_cmd = []
|
||||
|
||||
# Seek for a python binary newer than min_version
|
||||
for path in os.getenv("PATH", "").split(":"):
|
||||
for pattern in patterns:
|
||||
for cmd in glob(os.path.join(path, pattern)):
|
||||
if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
|
||||
version = PythonVersion.get_python_version(cmd)
|
||||
if version >= min_version:
|
||||
python_cmd.append((version, cmd))
|
||||
|
||||
return sorted(python_cmd, reverse=True)
|
||||
|
||||
@staticmethod
|
||||
def check_python(min_version, show_alternatives=False, bail_out=False,
|
||||
success_on_error=False):
|
||||
"""
|
||||
Check if the current python binary satisfies our minimal requirement
|
||||
for Sphinx build. If not, re-run with a newer version if found.
|
||||
"""
|
||||
cur_ver = sys.version_info[:3]
|
||||
if cur_ver >= min_version:
|
||||
ver = PythonVersion.ver_str(cur_ver)
|
||||
return
|
||||
|
||||
python_ver = PythonVersion.ver_str(cur_ver)
|
||||
|
||||
available_versions = PythonVersion.find_python(min_version)
|
||||
if not available_versions:
|
||||
print(f"ERROR: Python version {python_ver} is not spported anymore\n")
|
||||
print(" Can't find a new version. This script may fail")
|
||||
return
|
||||
|
||||
script_path = os.path.abspath(sys.argv[0])
|
||||
|
||||
# Check possible alternatives
|
||||
if available_versions:
|
||||
new_python_cmd = available_versions[0][1]
|
||||
else:
|
||||
new_python_cmd = None
|
||||
|
||||
if show_alternatives:
|
||||
print("You could run, instead:")
|
||||
for _, cmd in available_versions:
|
||||
args = [cmd, script_path] + sys.argv[1:]
|
||||
|
||||
cmd_str = " ".join(args)
|
||||
print(f" {cmd_str}")
|
||||
print()
|
||||
|
||||
if bail_out:
|
||||
msg = f"Python {python_ver} not supported. Bailing out"
|
||||
if success_on_error:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(msg)
|
||||
|
||||
print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
|
||||
|
||||
# Restart script using the newer version
|
||||
args = [new_python_cmd, script_path] + sys.argv[1:]
|
||||
|
||||
try:
|
||||
os.execv(new_python_cmd, args)
|
||||
except OSError as e:
|
||||
sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
|
||||
@@ -32,20 +32,10 @@ import subprocess
|
||||
import sys
|
||||
from glob import glob
|
||||
|
||||
from lib.python_version import PythonVersion
|
||||
|
||||
def parse_version(version):
|
||||
"""Convert a major.minor.patch version into a tuple"""
|
||||
return tuple(int(x) for x in version.split("."))
|
||||
|
||||
|
||||
def ver_str(version):
|
||||
"""Returns a version tuple as major.minor.patch"""
|
||||
|
||||
return ".".join([str(x) for x in version])
|
||||
|
||||
|
||||
RECOMMENDED_VERSION = parse_version("3.4.3")
|
||||
MIN_PYTHON_VERSION = parse_version("3.7")
|
||||
RECOMMENDED_VERSION = PythonVersion("3.4.3").version
|
||||
MIN_PYTHON_VERSION = PythonVersion("3.7").version
|
||||
|
||||
|
||||
class DepManager:
|
||||
@@ -235,122 +225,11 @@ class AncillaryMethods:
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_python_version(cmd):
|
||||
"""
|
||||
Get python version from a Python binary. As we need to detect if
|
||||
are out there newer python binaries, we can't rely on sys.release here.
|
||||
"""
|
||||
|
||||
result = SphinxDependencyChecker.run([cmd, "--version"],
|
||||
capture_output=True, text=True)
|
||||
version = result.stdout.strip()
|
||||
|
||||
match = re.search(r"(\d+\.\d+\.\d+)", version)
|
||||
if match:
|
||||
return parse_version(match.group(1))
|
||||
|
||||
print(f"Can't parse version {version}")
|
||||
return (0, 0, 0)
|
||||
|
||||
@staticmethod
|
||||
def find_python(min_version):
|
||||
"""
|
||||
Detect if are out there any python 3.xy version newer than the
|
||||
current one.
|
||||
|
||||
Note: this routine is limited to up to 2 digits for python3. We
|
||||
may need to update it one day, hopefully on a distant future.
|
||||
"""
|
||||
patterns = [
|
||||
"python3.[0-9][0-9]",
|
||||
"python3.[0-9]",
|
||||
]
|
||||
|
||||
# Seek for a python binary newer than MIN_PYTHON_VERSION
|
||||
python_cmd = []
|
||||
for path in os.getenv("PATH", "").split(":"):
|
||||
for pattern in patterns:
|
||||
for cmd in glob(os.path.join(path, pattern)):
|
||||
if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
|
||||
version = SphinxDependencyChecker.get_python_version(cmd)
|
||||
if version >= min_version:
|
||||
python_cmd.append((version, cmd))
|
||||
|
||||
return sorted(python_cmd, reverse=True)
|
||||
|
||||
@staticmethod
|
||||
def check_python(min_version, show_alternatives=False, bail_out=False,
|
||||
success_on_error=False):
|
||||
"""
|
||||
Check if the current python binary satisfies our minimal requirement
|
||||
for Sphinx build. If not, re-run with a newer version if found.
|
||||
"""
|
||||
cur_ver = sys.version_info[:3]
|
||||
if cur_ver >= MIN_PYTHON_VERSION:
|
||||
ver = ver_str(cur_ver)
|
||||
|
||||
# This could be useful for debugging purposes
|
||||
if SphinxDependencyChecker.which("docutils"):
|
||||
result = SphinxDependencyChecker.run(["docutils", "--version"],
|
||||
capture_output=True, text=True)
|
||||
ver = result.stdout.strip()
|
||||
match = re.search(r"(\d+\.\d+\.\d+)", ver)
|
||||
if match:
|
||||
ver = match.group(1)
|
||||
|
||||
print(f"Docutils version: {ver}")
|
||||
|
||||
return
|
||||
|
||||
python_ver = ver_str(cur_ver)
|
||||
|
||||
available_versions = SphinxDependencyChecker.find_python(min_version)
|
||||
if not available_versions:
|
||||
print(f"ERROR: Python version {python_ver} is not spported anymore\n")
|
||||
print(" Can't find a new version. This script may fail")
|
||||
return
|
||||
|
||||
script_path = os.path.abspath(sys.argv[0])
|
||||
|
||||
# Check possible alternatives
|
||||
if available_versions:
|
||||
new_python_cmd = available_versions[0][1]
|
||||
else:
|
||||
new_python_cmd = None
|
||||
|
||||
if show_alternatives:
|
||||
print("You could run, instead:")
|
||||
for _, cmd in available_versions:
|
||||
args = [cmd, script_path] + sys.argv[1:]
|
||||
|
||||
cmd_str = " ".join(args)
|
||||
print(f" {cmd_str}")
|
||||
print()
|
||||
|
||||
if bail_out:
|
||||
msg = f"Python {python_ver} not supported. Bailing out"
|
||||
if success_on_error:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(msg)
|
||||
|
||||
print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
|
||||
|
||||
# Restart script using the newer version
|
||||
args = [new_python_cmd, script_path] + sys.argv[1:]
|
||||
|
||||
try:
|
||||
os.execv(new_python_cmd, args)
|
||||
except OSError as e:
|
||||
sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
|
||||
|
||||
@staticmethod
|
||||
def run(*args, **kwargs):
|
||||
"""
|
||||
Excecute a command, hiding its output by default.
|
||||
Preserve comatibility with older Python versions.
|
||||
Preserve compatibility with older Python versions.
|
||||
"""
|
||||
|
||||
capture_output = kwargs.pop('capture_output', False)
|
||||
@@ -554,11 +433,11 @@ class MissingCheckers(AncillaryMethods):
|
||||
for line in result.stdout.split("\n"):
|
||||
match = re.match(r"^sphinx-build\s+([\d\.]+)(?:\+(?:/[\da-f]+)|b\d+)?\s*$", line)
|
||||
if match:
|
||||
return parse_version(match.group(1))
|
||||
return PythonVersion.parse_version(match.group(1))
|
||||
|
||||
match = re.match(r"^Sphinx.*\s+([\d\.]+)\s*$", line)
|
||||
if match:
|
||||
return parse_version(match.group(1))
|
||||
return PythonVersion.parse_version(match.group(1))
|
||||
|
||||
def check_sphinx(self, conf):
|
||||
"""
|
||||
@@ -569,7 +448,7 @@ class MissingCheckers(AncillaryMethods):
|
||||
for line in f:
|
||||
match = re.match(r"^\s*needs_sphinx\s*=\s*[\'\"]([\d\.]+)[\'\"]", line)
|
||||
if match:
|
||||
self.min_version = parse_version(match.group(1))
|
||||
self.min_version = PythonVersion.parse_version(match.group(1))
|
||||
break
|
||||
except IOError:
|
||||
sys.exit(f"Can't open {conf}")
|
||||
@@ -589,8 +468,8 @@ class MissingCheckers(AncillaryMethods):
|
||||
sys.exit(f"{sphinx} didn't return its version")
|
||||
|
||||
if self.cur_version < self.min_version:
|
||||
curver = ver_str(self.cur_version)
|
||||
minver = ver_str(self.min_version)
|
||||
curver = PythonVersion.ver_str(self.cur_version)
|
||||
minver = PythonVersion.ver_str(self.min_version)
|
||||
|
||||
print(f"ERROR: Sphinx version is {curver}. It should be >= {minver}")
|
||||
self.need_sphinx = 1
|
||||
@@ -1331,7 +1210,7 @@ class SphinxDependencyChecker(MissingCheckers):
|
||||
else:
|
||||
if self.need_sphinx and ver >= self.min_version:
|
||||
return (f, ver)
|
||||
elif parse_version(ver) > self.cur_version:
|
||||
elif PythonVersion.parse_version(ver) > self.cur_version:
|
||||
return (f, ver)
|
||||
|
||||
return ("", ver)
|
||||
@@ -1438,7 +1317,7 @@ class SphinxDependencyChecker(MissingCheckers):
|
||||
return
|
||||
|
||||
if self.latest_avail_ver:
|
||||
latest_avail_ver = ver_str(self.latest_avail_ver)
|
||||
latest_avail_ver = PythonVersion.ver_str(self.latest_avail_ver)
|
||||
|
||||
if not self.need_sphinx:
|
||||
# sphinx-build is present and its version is >= $min_version
|
||||
@@ -1534,7 +1413,7 @@ class SphinxDependencyChecker(MissingCheckers):
|
||||
else:
|
||||
print("Unknown OS")
|
||||
if self.cur_version != (0, 0, 0):
|
||||
ver = ver_str(self.cur_version)
|
||||
ver = PythonVersion.ver_str(self.cur_version)
|
||||
print(f"Sphinx version: {ver}\n")
|
||||
|
||||
# Check the type of virtual env, depending on Python version
|
||||
@@ -1640,7 +1519,7 @@ def main():
|
||||
|
||||
checker = SphinxDependencyChecker(args)
|
||||
|
||||
checker.check_python(MIN_PYTHON_VERSION)
|
||||
PythonVersion.check_python(MIN_PYTHON_VERSION)
|
||||
checker.check_needs()
|
||||
|
||||
# Call main if not used as module
|
||||
|
||||
Reference in New Issue
Block a user