From nobody Thu Oct 2 14:25:56 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AF9F43054E1; Tue, 16 Sep 2025 10:23:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758018216; cv=none; b=m7Gqa7hbxuQMOCTFHqlkdIIDC9BxNGgU8pPG6AsOFPkDnqA6d97K9v2ph6or/rDTaDjtGOSxUWCTUYEVv/mQeGDgnVWglmv9t+eIVuKlnIkQMQJeaZMu77JU401epBcMu7EbmE/HBp7qYGiEBVCr1e/BveeuswNUQqGQuDYBXEE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758018216; c=relaxed/simple; bh=fnTPEr295iKUMZRb112PItsVBFn7TVmO7VSK+qWICNA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=h5EqtdzhfXD2w7NspNTziPWp4K5WcTCbQVM9OSlHcc9zI22fOixWOsacjqg4dZW54X4BPTP/aeO5JX19DcxTibzqvk/qKnjo9a/BUBoMtDcTRbmDkBbpz08S8hvBgfgaswLS0Aqygv+Tt8wUfclEJOfXXsbou1wrY75/A7jfIwo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=BHR3nvZ3; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="BHR3nvZ3" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 24B5AC4CEEB; Tue, 16 Sep 2025 10:23:36 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1758018216; bh=fnTPEr295iKUMZRb112PItsVBFn7TVmO7VSK+qWICNA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BHR3nvZ3m+F5KGL98CuL/Dx5IPBljFgQuTUuUHVj75oGRVYQltzEpm45bG1ifm4XP W3wrzX432834yVTTBc7c7/5DFLrQ9SJcbZIvpDOmyUV19VmqG6zD1kA8WcsHSqPC1i ZNG2Cxc4hItLOolpjJRrOtQq7iKel73dPj/+XGlCd7uvBc7ZmwyeEuD4xm3wERdEtr Om5XAQNM9Dh/Bx1VTaZG7hyJ6YmePOhR6FTHopT0iIhq0AkDE49bB93jwkHy7ia2fP lkEAEXmW3iYES8CzoppIXed1CXhGIQI0r09PZDzNkiModGBGMpkDLZ2qdHK/2Xj9TV SgwFZjfshXZPg== Received: from mchehab by mail.kernel.org with local (Exim 4.98.2) (envelope-from ) id 1uySqS-0000000BBP2-3PqL; Tue, 16 Sep 2025 12:23:32 +0200 From: Mauro Carvalho Chehab To: Jonathan Corbet , Linux Doc Mailing List Cc: Mauro Carvalho Chehab , Mauro Carvalho Chehab , linux-kernel@vger.kernel.org Subject: [PATCH v6 07/21] tools/docs: python_version: move version check from sphinx-pre-install Date: Tue, 16 Sep 2025 12:22:43 +0200 Message-ID: <91b4fdce958c8004c8e8637699eaf971181694bc.1758018030.git.mchehab+huawei@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: References: Content-Type: text/plain; charset="utf-8" Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: Mauro Carvalho Chehab 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 --- tools/docs/lib/python_version.py | 133 +++++++++++++++++++++++++++++++ tools/docs/sphinx-pre-install | 120 +++------------------------- 2 files changed, 146 insertions(+), 107 deletions(-) create mode 100644 tools/docs/lib/python_version.py diff --git a/tools/docs/lib/python_version.py b/tools/docs/lib/python_versi= on.py new file mode 100644 index 000000000000..0519d524e547 --- /dev/null +++ b/tools/docs/lib/python_version.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (c) 2017-2025 Mauro Carvalho Chehab + +""" +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): + """=EF=BF=BDnitialize self.version tuple from a version string""" + self.version =3D 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 =3D {} + if sys.version_info < (3, 7): + kwargs['universal_newlines'] =3D True + else: + kwargs['text'] =3D True + + result =3D subprocess.run([cmd, "--version"], + stdout =3D subprocess.PIPE, + stderr =3D subprocess.PIPE, + **kwargs, check=3DFalse) + + version =3D result.stdout.strip() + + match =3D 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 =3D [ + "python3.[0-9]", + "python3.[0-9][0-9]", + ] + + # 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 =3D PythonVersion.get_python_version(cmd) + if version >=3D min_version: + return cmd + + return None + + @staticmethod + def check_python(min_version): + """ + Check if the current python binary satisfies our minimal requireme= nt + for Sphinx build. If not, re-run with a newer version if found. + """ + cur_ver =3D sys.version_info[:3] + if cur_ver >=3D min_version: + ver =3D PythonVersion.ver_str(cur_ver) + print(f"Python version: {ver}") + + return + + python_ver =3D PythonVersion.ver_str(cur_ver) + + new_python_cmd =3D PythonVersion.find_python(min_version) + if not new_python_cmd: + print(f"ERROR: Python version {python_ver} is not spported any= more\n") + print(" Can't find a new version. This script may fail") + return + + # Restart script using the newer version + script_path =3D os.path.abspath(sys.argv[0]) + args =3D [new_python_cmd, script_path] + sys.argv[1:] + + print(f"Python {python_ver} not supported. Changing to {new_python= _cmd}") + + try: + os.execv(new_python_cmd, args) + except OSError as e: + sys.exit(f"Failed to restart with {new_python_cmd}: {e}") diff --git a/tools/docs/sphinx-pre-install b/tools/docs/sphinx-pre-install index 954ed3dc0645..d6d673b7945c 100755 --- a/tools/docs/sphinx-pre-install +++ b/tools/docs/sphinx-pre-install @@ -32,20 +32,10 @@ import subprocess import sys from glob import glob =20 +from lib.python_version import PythonVersion =20 -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 =3D parse_version("3.4.3") -MIN_PYTHON_VERSION =3D parse_version("3.7") +RECOMMENDED_VERSION =3D PythonVersion("3.4.3").version +MIN_PYTHON_VERSION =3D PythonVersion("3.7").version =20 =20 class DepManager: @@ -235,95 +225,11 @@ class AncillaryMethods: =20 return None =20 - @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 =3D SphinxDependencyChecker.run([cmd, "--version"], - capture_output=3DTrue, text=3D= True) - version =3D result.stdout.strip() - - match =3D 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(): - """ - 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 =3D [ - "python3.[0-9]", - "python3.[0-9][0-9]", - ] - - # Seek for a python binary newer than MIN_PYTHON_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 =3D SphinxDependencyChecker.get_python_ver= sion(cmd) - if version >=3D MIN_PYTHON_VERSION: - return cmd - - @staticmethod - def check_python(): - """ - Check if the current python binary satisfies our minimal requireme= nt - for Sphinx build. If not, re-run with a newer version if found. - """ - cur_ver =3D sys.version_info[:3] - if cur_ver >=3D MIN_PYTHON_VERSION: - ver =3D ver_str(cur_ver) - print(f"Python version: {ver}") - - # This could be useful for debugging purposes - if SphinxDependencyChecker.which("docutils"): - result =3D SphinxDependencyChecker.run(["docutils", "--ver= sion"], - capture_output=3DTrue,= text=3DTrue) - ver =3D result.stdout.strip() - match =3D re.search(r"(\d+\.\d+\.\d+)", ver) - if match: - ver =3D match.group(1) - - print(f"Docutils version: {ver}") - - return - - python_ver =3D ver_str(cur_ver) - - new_python_cmd =3D SphinxDependencyChecker.find_python() - if not new_python_cmd: - print(f"ERROR: Python version {python_ver} is not spported any= more\n") - print(" Can't find a new version. This script may fail") - return - - # Restart script using the newer version - script_path =3D os.path.abspath(sys.argv[0]) - args =3D [new_python_cmd, script_path] + sys.argv[1:] - - print(f"Python {python_ver} not supported. Changing to {new_python= _cmd}") - - 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. """ =20 capture_output =3D kwargs.pop('capture_output', False) @@ -527,11 +433,11 @@ class MissingCheckers(AncillaryMethods): for line in result.stdout.split("\n"): match =3D 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)) =20 match =3D re.match(r"^Sphinx.*\s+([\d\.]+)\s*$", line) if match: - return parse_version(match.group(1)) + return PythonVersion.parse_version(match.group(1)) =20 def check_sphinx(self, conf): """ @@ -542,7 +448,7 @@ class MissingCheckers(AncillaryMethods): for line in f: match =3D re.match(r"^\s*needs_sphinx\s*=3D\s*[\'\"]([= \d\.]+)[\'\"]", line) if match: - self.min_version =3D parse_version(match.group(1)) + self.min_version =3D PythonVersion.parse_version(m= atch.group(1)) break except IOError: sys.exit(f"Can't open {conf}") @@ -562,8 +468,8 @@ class MissingCheckers(AncillaryMethods): sys.exit(f"{sphinx} didn't return its version") =20 if self.cur_version < self.min_version: - curver =3D ver_str(self.cur_version) - minver =3D ver_str(self.min_version) + curver =3D PythonVersion.ver_str(self.cur_version) + minver =3D PythonVersion.ver_str(self.min_version) =20 print(f"ERROR: Sphinx version is {curver}. It should be >=3D {= minver}") self.need_sphinx =3D 1 @@ -1304,7 +1210,7 @@ class SphinxDependencyChecker(MissingCheckers): else: if self.need_sphinx and ver >=3D self.min_version: return (f, ver) - elif parse_version(ver) > self.cur_version: + elif PythonVersion.parse_version(ver) > self.cur_version: return (f, ver) =20 return ("", ver) @@ -1411,7 +1317,7 @@ class SphinxDependencyChecker(MissingCheckers): return =20 if self.latest_avail_ver: - latest_avail_ver =3D ver_str(self.latest_avail_ver) + latest_avail_ver =3D PythonVersion.ver_str(self.latest_avail_v= er) =20 if not self.need_sphinx: # sphinx-build is present and its version is >=3D $min_version @@ -1507,7 +1413,7 @@ class SphinxDependencyChecker(MissingCheckers): else: print("Unknown OS") if self.cur_version !=3D (0, 0, 0): - ver =3D ver_str(self.cur_version) + ver =3D PythonVersion.ver_str(self.cur_version) print(f"Sphinx version: {ver}\n") =20 # Check the type of virtual env, depending on Python version @@ -1613,7 +1519,7 @@ def main(): =20 checker =3D SphinxDependencyChecker(args) =20 - checker.check_python() + PythonVersion.check_python(MIN_PYTHON_VERSION) checker.check_needs() =20 # Call main if not used as module --=20 2.51.0