From nobody Thu Oct 2 19:28:03 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 0E32A301485; Fri, 12 Sep 2025 11:46:34 +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=1757677595; cv=none; b=J/zjI/ldZ23BFayaGyZMRB0Or84OiwnGX+ZdgoZCcQtc0RwtRA4731mX7L0nD06QJbJUJHaXgR/oGWdWPu3f+qaVjm7VqaKsUG/lTiWbns9SV8k2/+9rj5nwfyoy2NqkeyeN5n4G404wxW2s7kyvd2GZRwmpvmk+8/yBWHFSd2Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757677595; c=relaxed/simple; bh=fnTPEr295iKUMZRb112PItsVBFn7TVmO7VSK+qWICNA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: Content-Type:MIME-Version; b=ieGU54Xn896YSc5Psfk5nMj2GRM2hRe5Sqf7ID3em11la1uY1PYYU9irOdVTIuktNcXL+vfKJWAt0EK1pFVcyUR1YYIXSppVO9UmzgkycalTCXDMgNgIZEE+ktONzUjv+yQYZ5msIi5SLEtp0KYmBwCZqGJdiQgJvGW+eW8ks+M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gGdsB7JX; 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="gGdsB7JX" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 5AAA4C4CEF4; Fri, 12 Sep 2025 11:46:34 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757677594; bh=fnTPEr295iKUMZRb112PItsVBFn7TVmO7VSK+qWICNA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gGdsB7JXXephzAwu543z4f1m+gG/D4+SLtKqcdqn0OnC3ND38V1XRj2m/a4yT4mGm oEUSMZTzsEk0jDDPAqIBB3RD1aczVppk51qRcnK975yBkW9Zf2CKapnwl39PxeUJfg LyFyobNtssMTJ5zel92OjqXPhfmUvMkof6woVhOxP5T/1oDkREkEQWGAWbibRa/SnE Xx+zETADbG+oGDfNYXB5FX7QwsncIxAwHgBcQysZpRo6wxQ+PUt6U5ohCggnOckCGX 2EAubXlfy0RKV8nW/yeB1jimueF5qGiJRXxJBw1aELN4lhmPYKNlnUe6cVSugsdBaY KD2KciyKZDbPQ== Received: from mchehab by mail.kernel.org with local (Exim 4.98.2) (envelope-from ) id 1ux2Ea-00000008RsD-2XpG; Fri, 12 Sep 2025 13:46:32 +0200 From: Mauro Carvalho Chehab To: Linux Doc Mailing List Cc: Mauro Carvalho Chehab , Jonathan Corbet , Mauro Carvalho Chehab , linux-kernel@vger.kernel.org Subject: [PATCH v5 05/18] tools/docs: python_version: move version check from sphinx-pre-install Date: Fri, 12 Sep 2025 13:46:12 +0200 Message-ID: <89a0df21ed074ba58dbc9d1d9fa1a097dc4c5398.1757677427.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