xref: /linux/tools/docs/test_doc_build.py (revision f1c2db1f145b5c609ae651d229713e3c7422785a)
154c147f4SMauro Carvalho Chehab#!/usr/bin/env python3
254c147f4SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
354c147f4SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
454c147f4SMauro Carvalho Chehab#
5fb1e8d12SMauro Carvalho Chehab# pylint: disable=R0903,R0912,R0913,R0914,R0917,C0301
654c147f4SMauro Carvalho Chehab
754c147f4SMauro Carvalho Chehab"""
854c147f4SMauro Carvalho ChehabInstall minimal supported requirements for different Sphinx versions
954c147f4SMauro Carvalho Chehaband optionally test the build.
1054c147f4SMauro Carvalho Chehab"""
1154c147f4SMauro Carvalho Chehab
1254c147f4SMauro Carvalho Chehabimport argparse
137649db7dSMauro Carvalho Chehabimport asyncio
1454c147f4SMauro Carvalho Chehabimport os.path
153fa60d28SMauro Carvalho Chehabimport shutil
1654c147f4SMauro Carvalho Chehabimport sys
1754c147f4SMauro Carvalho Chehabimport time
187649db7dSMauro Carvalho Chehabimport subprocess
1954c147f4SMauro Carvalho Chehab
203fa60d28SMauro Carvalho Chehab# Minimal python version supported by the building system.
2154c147f4SMauro Carvalho Chehab
223fa60d28SMauro Carvalho ChehabPYTHON = os.path.basename(sys.executable)
233fa60d28SMauro Carvalho Chehab
243fa60d28SMauro Carvalho Chehabmin_python_bin = None
253fa60d28SMauro Carvalho Chehab
263fa60d28SMauro Carvalho Chehabfor i in range(9, 13):
273fa60d28SMauro Carvalho Chehab    p = f"python3.{i}"
283fa60d28SMauro Carvalho Chehab    if shutil.which(p):
293fa60d28SMauro Carvalho Chehab        min_python_bin = p
303fa60d28SMauro Carvalho Chehab        break
313fa60d28SMauro Carvalho Chehab
323fa60d28SMauro Carvalho Chehabif not min_python_bin:
333fa60d28SMauro Carvalho Chehab    min_python_bin = PYTHON
343fa60d28SMauro Carvalho Chehab
353fa60d28SMauro Carvalho Chehab# Starting from 8.0, Python 3.9 is not supported anymore.
36791b9b03SMauro Carvalho ChehabPYTHON_VER_CHANGES = {(8, 0, 0): PYTHON}
37791b9b03SMauro Carvalho Chehab
38791b9b03SMauro Carvalho ChehabDEFAULT_VERSIONS_TO_TEST = [
39791b9b03SMauro Carvalho Chehab    (3, 4, 3),   # Minimal supported version
40791b9b03SMauro Carvalho Chehab    (5, 3, 0),   # CentOS Stream 9 / AlmaLinux 9
41791b9b03SMauro Carvalho Chehab    (6, 1, 1),   # Debian 12
42791b9b03SMauro Carvalho Chehab    (7, 2, 1),   # openSUSE Leap 15.6
43791b9b03SMauro Carvalho Chehab    (7, 2, 6),   # Ubuntu 24.04 LTS
44791b9b03SMauro Carvalho Chehab    (7, 4, 7),   # Ubuntu 24.10
45791b9b03SMauro Carvalho Chehab    (7, 3, 0),   # openSUSE Tumbleweed
46791b9b03SMauro Carvalho Chehab    (8, 1, 3),   # Fedora 42
47791b9b03SMauro Carvalho Chehab    (8, 2, 3)    # Latest version - covers rolling distros
48791b9b03SMauro Carvalho Chehab]
4954c147f4SMauro Carvalho Chehab
5054c147f4SMauro Carvalho Chehab# Sphinx versions to be installed and their incremental requirements
517649db7dSMauro Carvalho ChehabSPHINX_REQUIREMENTS = {
52792bf019SMauro Carvalho Chehab    # Oldest versions we support for each package required by Sphinx 3.4.3
5354c147f4SMauro Carvalho Chehab    (3, 4, 3): {
54792bf019SMauro Carvalho Chehab        "docutils": "0.16",
55792bf019SMauro Carvalho Chehab        "alabaster": "0.7.12",
56792bf019SMauro Carvalho Chehab        "babel": "2.8.0",
57792bf019SMauro Carvalho Chehab        "certifi": "2020.6.20",
58792bf019SMauro Carvalho Chehab        "docutils": "0.16",
59792bf019SMauro Carvalho Chehab        "idna": "2.10",
60792bf019SMauro Carvalho Chehab        "imagesize": "1.2.0",
61792bf019SMauro Carvalho Chehab        "Jinja2": "2.11.2",
62792bf019SMauro Carvalho Chehab        "MarkupSafe": "1.1.1",
63792bf019SMauro Carvalho Chehab        "packaging": "20.4",
64792bf019SMauro Carvalho Chehab        "Pygments": "2.6.1",
65792bf019SMauro Carvalho Chehab        "PyYAML": "5.1",
66792bf019SMauro Carvalho Chehab        "requests": "2.24.0",
67792bf019SMauro Carvalho Chehab        "snowballstemmer": "2.0.0",
68792bf019SMauro Carvalho Chehab        "sphinxcontrib-applehelp": "1.0.2",
69792bf019SMauro Carvalho Chehab        "sphinxcontrib-devhelp": "1.0.2",
70792bf019SMauro Carvalho Chehab        "sphinxcontrib-htmlhelp": "1.0.3",
71792bf019SMauro Carvalho Chehab        "sphinxcontrib-jsmath": "1.0.1",
72792bf019SMauro Carvalho Chehab        "sphinxcontrib-qthelp": "1.0.3",
73792bf019SMauro Carvalho Chehab        "sphinxcontrib-serializinghtml": "1.1.4",
74792bf019SMauro Carvalho Chehab        "urllib3": "1.25.9",
75792bf019SMauro Carvalho Chehab    },
76792bf019SMauro Carvalho Chehab
77792bf019SMauro Carvalho Chehab    # Update package dependencies to a more modern base. The goal here
78792bf019SMauro Carvalho Chehab    # is to avoid to many incremental changes for the next entries
79791b9b03SMauro Carvalho Chehab    (3, 5, 0): {
8054c147f4SMauro Carvalho Chehab        "alabaster": "0.7.13",
8154c147f4SMauro Carvalho Chehab        "babel": "2.17.0",
8254c147f4SMauro Carvalho Chehab        "certifi": "2025.6.15",
8354c147f4SMauro Carvalho Chehab        "idna": "3.10",
8454c147f4SMauro Carvalho Chehab        "imagesize": "1.4.1",
8554c147f4SMauro Carvalho Chehab        "packaging": "25.0",
86791b9b03SMauro Carvalho Chehab        "Pygments": "2.8.1",
8754c147f4SMauro Carvalho Chehab        "requests": "2.32.4",
8854c147f4SMauro Carvalho Chehab        "snowballstemmer": "3.0.1",
8954c147f4SMauro Carvalho Chehab        "sphinxcontrib-applehelp": "1.0.4",
9054c147f4SMauro Carvalho Chehab        "sphinxcontrib-htmlhelp": "2.0.1",
9154c147f4SMauro Carvalho Chehab        "sphinxcontrib-serializinghtml": "1.1.5",
92791b9b03SMauro Carvalho Chehab        "urllib3": "2.0.0",
9354c147f4SMauro Carvalho Chehab    },
94792bf019SMauro Carvalho Chehab
95792bf019SMauro Carvalho Chehab    # Starting from here, ensure all docutils versions are covered with
96792bf019SMauro Carvalho Chehab    # supported Sphinx versions. Other packages are upgraded only when
97792bf019SMauro Carvalho Chehab    # required by pip
98791b9b03SMauro Carvalho Chehab    (4, 0, 0): {
9954c147f4SMauro Carvalho Chehab        "PyYAML": "5.1",
10054c147f4SMauro Carvalho Chehab    },
101791b9b03SMauro Carvalho Chehab    (4, 1, 0): {
102791b9b03SMauro Carvalho Chehab        "docutils": "0.17",
103791b9b03SMauro Carvalho Chehab        "Pygments": "2.19.1",
104791b9b03SMauro Carvalho Chehab        "Jinja2": "3.0.3",
105791b9b03SMauro Carvalho Chehab        "MarkupSafe": "2.0",
106792bf019SMauro Carvalho Chehab    },
107791b9b03SMauro Carvalho Chehab    (4, 3, 0): {},
10854c147f4SMauro Carvalho Chehab    (4, 4, 0): {},
109791b9b03SMauro Carvalho Chehab    (4, 5, 0): {
110792bf019SMauro Carvalho Chehab        "docutils": "0.17.1",
111791b9b03SMauro Carvalho Chehab    },
112791b9b03SMauro Carvalho Chehab    (5, 0, 0): {},
113791b9b03SMauro Carvalho Chehab    (5, 1, 0): {},
114791b9b03SMauro Carvalho Chehab    (5, 2, 0): {
115791b9b03SMauro Carvalho Chehab        "docutils": "0.18",
11654c147f4SMauro Carvalho Chehab        "Jinja2": "3.1.2",
11754c147f4SMauro Carvalho Chehab        "MarkupSafe": "2.0",
11854c147f4SMauro Carvalho Chehab        "PyYAML": "5.3.1",
11954c147f4SMauro Carvalho Chehab    },
120791b9b03SMauro Carvalho Chehab    (5, 3, 0): {
121792bf019SMauro Carvalho Chehab        "docutils": "0.18.1",
122791b9b03SMauro Carvalho Chehab    },
123791b9b03SMauro Carvalho Chehab    (6, 0, 0): {},
124791b9b03SMauro Carvalho Chehab    (6, 1, 0): {},
125791b9b03SMauro Carvalho Chehab    (6, 2, 0): {
12654c147f4SMauro Carvalho Chehab        "PyYAML": "5.4.1",
12754c147f4SMauro Carvalho Chehab    },
128791b9b03SMauro Carvalho Chehab    (7, 0, 0): {},
129791b9b03SMauro Carvalho Chehab    (7, 1, 0): {},
130791b9b03SMauro Carvalho Chehab    (7, 2, 0): {
131792bf019SMauro Carvalho Chehab        "docutils": "0.19",
13254c147f4SMauro Carvalho Chehab        "PyYAML": "6.0.1",
13354c147f4SMauro Carvalho Chehab        "sphinxcontrib-serializinghtml": "1.1.9",
13454c147f4SMauro Carvalho Chehab    },
135791b9b03SMauro Carvalho Chehab    (7, 2, 6): {
136792bf019SMauro Carvalho Chehab        "docutils": "0.20",
137791b9b03SMauro Carvalho Chehab    },
138791b9b03SMauro Carvalho Chehab    (7, 3, 0): {
13954c147f4SMauro Carvalho Chehab        "alabaster": "0.7.14",
14054c147f4SMauro Carvalho Chehab        "PyYAML": "6.0.1",
141791b9b03SMauro Carvalho Chehab        "tomli": "2.0.1",
14254c147f4SMauro Carvalho Chehab    },
143791b9b03SMauro Carvalho Chehab    (7, 4, 0): {
144791b9b03SMauro Carvalho Chehab        "docutils": "0.20.1",
14554c147f4SMauro Carvalho Chehab        "PyYAML": "6.0.1",
14654c147f4SMauro Carvalho Chehab    },
147791b9b03SMauro Carvalho Chehab    (8, 0, 0): {
148791b9b03SMauro Carvalho Chehab        "docutils": "0.21",
149792bf019SMauro Carvalho Chehab    },
150791b9b03SMauro Carvalho Chehab    (8, 1, 0): {
151791b9b03SMauro Carvalho Chehab        "docutils": "0.21.1",
15254c147f4SMauro Carvalho Chehab        "PyYAML": "6.0.1",
15354c147f4SMauro Carvalho Chehab        "sphinxcontrib-applehelp": "1.0.7",
15454c147f4SMauro Carvalho Chehab        "sphinxcontrib-devhelp": "1.0.6",
15554c147f4SMauro Carvalho Chehab        "sphinxcontrib-htmlhelp": "2.0.6",
15654c147f4SMauro Carvalho Chehab        "sphinxcontrib-qthelp": "1.0.6",
15754c147f4SMauro Carvalho Chehab    },
158791b9b03SMauro Carvalho Chehab    (8, 2, 0): {
159791b9b03SMauro Carvalho Chehab        "docutils": "0.21.2",
16054c147f4SMauro Carvalho Chehab        "PyYAML": "6.0.1",
16154c147f4SMauro Carvalho Chehab        "sphinxcontrib-serializinghtml": "1.1.9",
16254c147f4SMauro Carvalho Chehab    },
16354c147f4SMauro Carvalho Chehab}
16454c147f4SMauro Carvalho Chehab
16554c147f4SMauro Carvalho Chehab
1667649db7dSMauro Carvalho Chehabclass AsyncCommands:
1677649db7dSMauro Carvalho Chehab    """Excecute command synchronously"""
1687649db7dSMauro Carvalho Chehab
169fb1e8d12SMauro Carvalho Chehab    def __init__(self, fp=None):
170fb1e8d12SMauro Carvalho Chehab
171fb1e8d12SMauro Carvalho Chehab        self.stdout = None
172fb1e8d12SMauro Carvalho Chehab        self.stderr = None
173fb1e8d12SMauro Carvalho Chehab        self.output = None
174fb1e8d12SMauro Carvalho Chehab        self.fp = fp
175fb1e8d12SMauro Carvalho Chehab
176fb1e8d12SMauro Carvalho Chehab    def log(self, out, verbose, is_info=True):
1770e93f124SMauro Carvalho Chehab        out = out.removesuffix('\n')
1780e93f124SMauro Carvalho Chehab
179fb1e8d12SMauro Carvalho Chehab        if verbose:
180fb1e8d12SMauro Carvalho Chehab            if is_info:
1810e93f124SMauro Carvalho Chehab                print(out)
182fb1e8d12SMauro Carvalho Chehab            else:
1830e93f124SMauro Carvalho Chehab                print(out, file=sys.stderr)
184fb1e8d12SMauro Carvalho Chehab
185fb1e8d12SMauro Carvalho Chehab        if self.fp:
1860e93f124SMauro Carvalho Chehab            self.fp.write(out + "\n")
1877649db7dSMauro Carvalho Chehab
1887649db7dSMauro Carvalho Chehab    async def _read(self, stream, verbose, is_info):
1897649db7dSMauro Carvalho Chehab        """Ancillary routine to capture while displaying"""
1907649db7dSMauro Carvalho Chehab
1917649db7dSMauro Carvalho Chehab        while stream is not None:
1927649db7dSMauro Carvalho Chehab            line = await stream.readline()
1937649db7dSMauro Carvalho Chehab            if line:
1947649db7dSMauro Carvalho Chehab                out = line.decode("utf-8", errors="backslashreplace")
195fb1e8d12SMauro Carvalho Chehab                self.log(out, verbose, is_info)
1967649db7dSMauro Carvalho Chehab                if is_info:
1977649db7dSMauro Carvalho Chehab                    self.stdout += out
1987649db7dSMauro Carvalho Chehab                else:
1997649db7dSMauro Carvalho Chehab                    self.stderr += out
2007649db7dSMauro Carvalho Chehab            else:
2017649db7dSMauro Carvalho Chehab                break
2027649db7dSMauro Carvalho Chehab
2037649db7dSMauro Carvalho Chehab    async def run(self, cmd, capture_output=False, check=False,
2047649db7dSMauro Carvalho Chehab                  env=None, verbose=True):
2057649db7dSMauro Carvalho Chehab
2067649db7dSMauro Carvalho Chehab        """
2077649db7dSMauro Carvalho Chehab        Execute an arbitrary command, handling errors.
2087649db7dSMauro Carvalho Chehab
2097649db7dSMauro Carvalho Chehab        Please notice that this class is not thread safe
2107649db7dSMauro Carvalho Chehab        """
2117649db7dSMauro Carvalho Chehab
2127649db7dSMauro Carvalho Chehab        self.stdout = ""
2137649db7dSMauro Carvalho Chehab        self.stderr = ""
2147649db7dSMauro Carvalho Chehab
215fb1e8d12SMauro Carvalho Chehab        self.log("$ " + " ".join(cmd), verbose)
2167649db7dSMauro Carvalho Chehab
2177649db7dSMauro Carvalho Chehab        proc = await asyncio.create_subprocess_exec(cmd[0],
2187649db7dSMauro Carvalho Chehab                                                    *cmd[1:],
2197649db7dSMauro Carvalho Chehab                                                    env=env,
2207649db7dSMauro Carvalho Chehab                                                    stdout=asyncio.subprocess.PIPE,
2217649db7dSMauro Carvalho Chehab                                                    stderr=asyncio.subprocess.PIPE)
2227649db7dSMauro Carvalho Chehab
2237649db7dSMauro Carvalho Chehab        # Handle input and output in realtime
2247649db7dSMauro Carvalho Chehab        await asyncio.gather(
2257649db7dSMauro Carvalho Chehab            self._read(proc.stdout, verbose, True),
2267649db7dSMauro Carvalho Chehab            self._read(proc.stderr, verbose, False),
2277649db7dSMauro Carvalho Chehab        )
2287649db7dSMauro Carvalho Chehab
2297649db7dSMauro Carvalho Chehab        await proc.wait()
2307649db7dSMauro Carvalho Chehab
2317649db7dSMauro Carvalho Chehab        if check and proc.returncode > 0:
2327649db7dSMauro Carvalho Chehab            raise subprocess.CalledProcessError(returncode=proc.returncode,
2337649db7dSMauro Carvalho Chehab                                                cmd=" ".join(cmd),
2347649db7dSMauro Carvalho Chehab                                                output=self.stdout,
2357649db7dSMauro Carvalho Chehab                                                stderr=self.stderr)
2367649db7dSMauro Carvalho Chehab
2377649db7dSMauro Carvalho Chehab        if capture_output:
2387649db7dSMauro Carvalho Chehab            if proc.returncode > 0:
239fb1e8d12SMauro Carvalho Chehab                self.log(f"Error {proc.returncode}", verbose=True, is_info=False)
2407649db7dSMauro Carvalho Chehab                return ""
2417649db7dSMauro Carvalho Chehab
2427649db7dSMauro Carvalho Chehab            return self.output
2437649db7dSMauro Carvalho Chehab
2447649db7dSMauro Carvalho Chehab        ret = subprocess.CompletedProcess(args=cmd,
2457649db7dSMauro Carvalho Chehab                                          returncode=proc.returncode,
2467649db7dSMauro Carvalho Chehab                                          stdout=self.stdout,
2477649db7dSMauro Carvalho Chehab                                          stderr=self.stderr)
2487649db7dSMauro Carvalho Chehab
2497649db7dSMauro Carvalho Chehab        return ret
2507649db7dSMauro Carvalho Chehab
2517649db7dSMauro Carvalho Chehab
2527649db7dSMauro Carvalho Chehabclass SphinxVenv:
2537649db7dSMauro Carvalho Chehab    """
2547649db7dSMauro Carvalho Chehab    Installs Sphinx on one virtual env per Sphinx version with a minimal
2557649db7dSMauro Carvalho Chehab    set of dependencies, adjusting them to each specific version.
2567649db7dSMauro Carvalho Chehab    """
2577649db7dSMauro Carvalho Chehab
2587649db7dSMauro Carvalho Chehab    def __init__(self):
2597649db7dSMauro Carvalho Chehab        """Initialize instance variables"""
2607649db7dSMauro Carvalho Chehab
2617649db7dSMauro Carvalho Chehab        self.built_time = {}
2627649db7dSMauro Carvalho Chehab        self.first_run = True
2637649db7dSMauro Carvalho Chehab
264fb1e8d12SMauro Carvalho Chehab    async def _handle_version(self, args, fp,
265fb1e8d12SMauro Carvalho Chehab                              cur_ver, cur_requirements, python_bin):
2667649db7dSMauro Carvalho Chehab        """Handle a single Sphinx version"""
2677649db7dSMauro Carvalho Chehab
268fb1e8d12SMauro Carvalho Chehab        cmd = AsyncCommands(fp)
2697649db7dSMauro Carvalho Chehab
2707649db7dSMauro Carvalho Chehab        ver = ".".join(map(str, cur_ver))
2717649db7dSMauro Carvalho Chehab
272*bb4c5c50SMauro Carvalho Chehab        if not self.first_run and args.wait_input and args.build:
2737649db7dSMauro Carvalho Chehab            ret = input("Press Enter to continue or 'a' to abort: ").strip().lower()
2747649db7dSMauro Carvalho Chehab            if ret == "a":
2757649db7dSMauro Carvalho Chehab                print("Aborted.")
2767649db7dSMauro Carvalho Chehab                sys.exit()
2777649db7dSMauro Carvalho Chehab        else:
2787649db7dSMauro Carvalho Chehab            self.first_run = False
2797649db7dSMauro Carvalho Chehab
2807649db7dSMauro Carvalho Chehab        venv_dir = f"Sphinx_{ver}"
2817649db7dSMauro Carvalho Chehab        req_file = f"requirements_{ver}.txt"
2827649db7dSMauro Carvalho Chehab
283fb1e8d12SMauro Carvalho Chehab        cmd.log(f"\nSphinx {ver} with {python_bin}", verbose=True)
2847649db7dSMauro Carvalho Chehab
2857649db7dSMauro Carvalho Chehab        # Create venv
286fb1e8d12SMauro Carvalho Chehab        await cmd.run([python_bin, "-m", "venv", venv_dir],
287fb1e8d12SMauro Carvalho Chehab                      verbose=args.verbose, check=True)
2887649db7dSMauro Carvalho Chehab        pip = os.path.join(venv_dir, "bin/pip")
2897649db7dSMauro Carvalho Chehab
2907649db7dSMauro Carvalho Chehab        # Create install list
2917649db7dSMauro Carvalho Chehab        reqs = []
2927649db7dSMauro Carvalho Chehab        for pkg, verstr in cur_requirements.items():
2937649db7dSMauro Carvalho Chehab            reqs.append(f"{pkg}=={verstr}")
2947649db7dSMauro Carvalho Chehab
2957649db7dSMauro Carvalho Chehab        reqs.append(f"Sphinx=={ver}")
2967649db7dSMauro Carvalho Chehab
297fb1e8d12SMauro Carvalho Chehab        await cmd.run([pip, "install"] + reqs, check=True, verbose=args.verbose)
2987649db7dSMauro Carvalho Chehab
2997649db7dSMauro Carvalho Chehab        # Freeze environment
3007649db7dSMauro Carvalho Chehab        result = await cmd.run([pip, "freeze"], verbose=False, check=True)
3017649db7dSMauro Carvalho Chehab
3027649db7dSMauro Carvalho Chehab        # Pip install succeeded. Write requirements file
303*bb4c5c50SMauro Carvalho Chehab        if args.req_file:
3047649db7dSMauro Carvalho Chehab            with open(req_file, "w", encoding="utf-8") as fp:
3057649db7dSMauro Carvalho Chehab                fp.write(result.stdout)
3067649db7dSMauro Carvalho Chehab
307*bb4c5c50SMauro Carvalho Chehab        if args.build:
3087649db7dSMauro Carvalho Chehab            start_time = time.time()
3097649db7dSMauro Carvalho Chehab
3107649db7dSMauro Carvalho Chehab            # Prepare a venv environment
3117649db7dSMauro Carvalho Chehab            env = os.environ.copy()
3127649db7dSMauro Carvalho Chehab            bin_dir = os.path.join(venv_dir, "bin")
3137649db7dSMauro Carvalho Chehab            env["PATH"] = bin_dir + ":" + env["PATH"]
3147649db7dSMauro Carvalho Chehab            env["VIRTUAL_ENV"] = venv_dir
3157649db7dSMauro Carvalho Chehab            if "PYTHONHOME" in env:
3167649db7dSMauro Carvalho Chehab                del env["PYTHONHOME"]
3177649db7dSMauro Carvalho Chehab
3187649db7dSMauro Carvalho Chehab            # Test doc build
3197649db7dSMauro Carvalho Chehab            await cmd.run(["make", "cleandocs"], env=env, check=True)
320*bb4c5c50SMauro Carvalho Chehab            make = ["make"]
321*bb4c5c50SMauro Carvalho Chehab
322*bb4c5c50SMauro Carvalho Chehab            if args.output:
323*bb4c5c50SMauro Carvalho Chehab                sphinx_build = os.path.realpath(f"{bin_dir}/sphinx-build")
324*bb4c5c50SMauro Carvalho Chehab                make += [f"O={args.output}", f"SPHINXBUILD={sphinx_build}"]
325*bb4c5c50SMauro Carvalho Chehab
326*bb4c5c50SMauro Carvalho Chehab            if args.make_args:
327*bb4c5c50SMauro Carvalho Chehab                make += args.make_args
328*bb4c5c50SMauro Carvalho Chehab
329*bb4c5c50SMauro Carvalho Chehab            make += args.targets
3307649db7dSMauro Carvalho Chehab
331fb1e8d12SMauro Carvalho Chehab            if args.verbose:
3320e93f124SMauro Carvalho Chehab                cmd.log(f". {bin_dir}/activate", verbose=True)
333fb1e8d12SMauro Carvalho Chehab            await cmd.run(make, env=env, check=True, verbose=True)
334fb1e8d12SMauro Carvalho Chehab            if args.verbose:
3350e93f124SMauro Carvalho Chehab                cmd.log("deactivate", verbose=True)
3367649db7dSMauro Carvalho Chehab
3377649db7dSMauro Carvalho Chehab            end_time = time.time()
3387649db7dSMauro Carvalho Chehab            elapsed_time = end_time - start_time
3397649db7dSMauro Carvalho Chehab            hours, minutes = divmod(elapsed_time, 3600)
3407649db7dSMauro Carvalho Chehab            minutes, seconds = divmod(minutes, 60)
3417649db7dSMauro Carvalho Chehab
3427649db7dSMauro Carvalho Chehab            hours = int(hours)
3437649db7dSMauro Carvalho Chehab            minutes = int(minutes)
3447649db7dSMauro Carvalho Chehab            seconds = int(seconds)
3457649db7dSMauro Carvalho Chehab
3467649db7dSMauro Carvalho Chehab            self.built_time[ver] = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
3477649db7dSMauro Carvalho Chehab
348fb1e8d12SMauro Carvalho Chehab            cmd.log(f"Finished doc build for Sphinx {ver}. Elapsed time: {self.built_time[ver]}", verbose=True)
3497649db7dSMauro Carvalho Chehab
3507649db7dSMauro Carvalho Chehab    async def run(self, args):
3517649db7dSMauro Carvalho Chehab        """
3527649db7dSMauro Carvalho Chehab        Navigate though multiple Sphinx versions, handling each of them
3537649db7dSMauro Carvalho Chehab        on a loop.
3547649db7dSMauro Carvalho Chehab        """
3557649db7dSMauro Carvalho Chehab
356fb1e8d12SMauro Carvalho Chehab        if args.log:
357fb1e8d12SMauro Carvalho Chehab            fp = open(args.log, "w", encoding="utf-8")
358fb1e8d12SMauro Carvalho Chehab            if not args.verbose:
359fb1e8d12SMauro Carvalho Chehab                args.verbose = False
360fb1e8d12SMauro Carvalho Chehab        else:
361fb1e8d12SMauro Carvalho Chehab            fp = None
362fb1e8d12SMauro Carvalho Chehab            if not args.verbose:
363fb1e8d12SMauro Carvalho Chehab                args.verbose = True
364fb1e8d12SMauro Carvalho Chehab
3657649db7dSMauro Carvalho Chehab        cur_requirements = {}
3663fa60d28SMauro Carvalho Chehab        python_bin = min_python_bin
3677649db7dSMauro Carvalho Chehab
368791b9b03SMauro Carvalho Chehab        vers = set(SPHINX_REQUIREMENTS.keys()) | set(args.versions)
369791b9b03SMauro Carvalho Chehab
370791b9b03SMauro Carvalho Chehab        for cur_ver in sorted(vers):
371791b9b03SMauro Carvalho Chehab            if cur_ver in SPHINX_REQUIREMENTS:
372791b9b03SMauro Carvalho Chehab                new_reqs = SPHINX_REQUIREMENTS[cur_ver]
3737649db7dSMauro Carvalho Chehab                cur_requirements.update(new_reqs)
3747649db7dSMauro Carvalho Chehab
3757649db7dSMauro Carvalho Chehab            if cur_ver in PYTHON_VER_CHANGES:          # pylint: disable=R1715
3767649db7dSMauro Carvalho Chehab                python_bin = PYTHON_VER_CHANGES[cur_ver]
3777649db7dSMauro Carvalho Chehab
378791b9b03SMauro Carvalho Chehab            if cur_ver not in args.versions:
379791b9b03SMauro Carvalho Chehab                continue
380791b9b03SMauro Carvalho Chehab
3817649db7dSMauro Carvalho Chehab            if args.min_version:
3827649db7dSMauro Carvalho Chehab                if cur_ver < args.min_version:
3837649db7dSMauro Carvalho Chehab                    continue
3847649db7dSMauro Carvalho Chehab
3857649db7dSMauro Carvalho Chehab            if args.max_version:
3867649db7dSMauro Carvalho Chehab                if cur_ver > args.max_version:
3877649db7dSMauro Carvalho Chehab                    break
3887649db7dSMauro Carvalho Chehab
389fb1e8d12SMauro Carvalho Chehab            await self._handle_version(args, fp, cur_ver, cur_requirements,
3907649db7dSMauro Carvalho Chehab                                       python_bin)
3917649db7dSMauro Carvalho Chehab
392*bb4c5c50SMauro Carvalho Chehab        if args.build:
3930e93f124SMauro Carvalho Chehab            cmd = AsyncCommands(fp)
3940e93f124SMauro Carvalho Chehab            cmd.log("\nSummary:", verbose=True)
3957649db7dSMauro Carvalho Chehab            for ver, elapsed_time in sorted(self.built_time.items()):
3960e93f124SMauro Carvalho Chehab                cmd.log(f"\tSphinx {ver} elapsed time: {elapsed_time}",
3970e93f124SMauro Carvalho Chehab                        verbose=True)
3987649db7dSMauro Carvalho Chehab
399fb1e8d12SMauro Carvalho Chehab        if fp:
400fb1e8d12SMauro Carvalho Chehab            fp.close()
4017649db7dSMauro Carvalho Chehab
40254c147f4SMauro Carvalho Chehabdef parse_version(ver_str):
40354c147f4SMauro Carvalho Chehab    """Convert a version string into a tuple."""
40454c147f4SMauro Carvalho Chehab
40554c147f4SMauro Carvalho Chehab    return tuple(map(int, ver_str.split(".")))
40654c147f4SMauro Carvalho Chehab
40754c147f4SMauro Carvalho Chehab
408791b9b03SMauro Carvalho ChehabDEFAULT_VERS = "    - "
409791b9b03SMauro Carvalho ChehabDEFAULT_VERS += "\n    - ".join(map(lambda v: f"{v[0]}.{v[1]}.{v[2]}",
410791b9b03SMauro Carvalho Chehab                                    DEFAULT_VERSIONS_TO_TEST))
411791b9b03SMauro Carvalho Chehab
412791b9b03SMauro Carvalho ChehabSCRIPT = os.path.relpath(__file__)
413791b9b03SMauro Carvalho Chehab
414791b9b03SMauro Carvalho ChehabDESCRIPTION = f"""
415791b9b03SMauro Carvalho ChehabThis tool allows creating Python virtual environments for different
416791b9b03SMauro Carvalho ChehabSphinx versions that are supported by the Linux Kernel build system.
417791b9b03SMauro Carvalho Chehab
418791b9b03SMauro Carvalho ChehabBesides creating the virtual environment, it can also test building
419*bb4c5c50SMauro Carvalho Chehabthe documentation using "make htmldocs" (and/or other doc targets).
420791b9b03SMauro Carvalho Chehab
421791b9b03SMauro Carvalho ChehabIf called without "--versions" argument, it covers the versions shipped
422791b9b03SMauro Carvalho Chehabon major distros, plus the lowest supported version:
423791b9b03SMauro Carvalho Chehab
424791b9b03SMauro Carvalho Chehab{DEFAULT_VERS}
425791b9b03SMauro Carvalho Chehab
426791b9b03SMauro Carvalho ChehabA typical usage is to run:
427791b9b03SMauro Carvalho Chehab
428791b9b03SMauro Carvalho Chehab   {SCRIPT} -m -l sphinx_builds.log
429791b9b03SMauro Carvalho Chehab
430*bb4c5c50SMauro Carvalho ChehabThis will create one virtual env for the default version set and run
431*bb4c5c50SMauro Carvalho Chehab"make htmldocs" for each version, creating a log file with the
432791b9b03SMauro Carvalho Chehabexcecuted commands on it.
433791b9b03SMauro Carvalho Chehab
434791b9b03SMauro Carvalho ChehabNOTE: The build time can be very long, specially on old versions. Also, there
435791b9b03SMauro Carvalho Chehabis a known bug with Sphinx version 6.0.x: each subprocess uses a lot of
436791b9b03SMauro Carvalho Chehabmemory. That, together with "-jauto" may cause OOM killer to cause
437791b9b03SMauro Carvalho Chehabfailures at the doc generation. To minimize the risk, you may use the
438791b9b03SMauro Carvalho Chehab"-a" command line parameter to constrain the built directories and/or
439791b9b03SMauro Carvalho Chehabreduce the number of threads from "-jauto" to, for instance, "-j4":
440791b9b03SMauro Carvalho Chehab
441791b9b03SMauro Carvalho Chehab    {SCRIPT} -m -V 6.0.1 -a "SPHINXDIRS=process" "SPHINXOPTS='-j4'"
442791b9b03SMauro Carvalho Chehab
443791b9b03SMauro Carvalho Chehab"""
444791b9b03SMauro Carvalho Chehab
445*bb4c5c50SMauro Carvalho ChehabMAKE_TARGETS = [
446*bb4c5c50SMauro Carvalho Chehab    "htmldocs",
447*bb4c5c50SMauro Carvalho Chehab    "texinfodocs",
448*bb4c5c50SMauro Carvalho Chehab    "infodocs",
449*bb4c5c50SMauro Carvalho Chehab    "latexdocs",
450*bb4c5c50SMauro Carvalho Chehab    "pdfdocs",
451*bb4c5c50SMauro Carvalho Chehab    "epubdocs",
452*bb4c5c50SMauro Carvalho Chehab    "xmldocs",
453*bb4c5c50SMauro Carvalho Chehab]
454791b9b03SMauro Carvalho Chehab
4557649db7dSMauro Carvalho Chehabasync def main():
4567649db7dSMauro Carvalho Chehab    """Main program"""
4577649db7dSMauro Carvalho Chehab
458791b9b03SMauro Carvalho Chehab    parser = argparse.ArgumentParser(description=DESCRIPTION,
459791b9b03SMauro Carvalho Chehab                                     formatter_class=argparse.RawDescriptionHelpFormatter)
46054c147f4SMauro Carvalho Chehab
461*bb4c5c50SMauro Carvalho Chehab    ver_group = parser.add_argument_group("Version range options")
462*bb4c5c50SMauro Carvalho Chehab
463*bb4c5c50SMauro Carvalho Chehab    ver_group.add_argument('-V', '--versions', nargs="*",
464*bb4c5c50SMauro Carvalho Chehab                           default=DEFAULT_VERSIONS_TO_TEST,type=parse_version,
465*bb4c5c50SMauro Carvalho Chehab                           help='Sphinx versions to test')
466*bb4c5c50SMauro Carvalho Chehab    ver_group.add_argument('--min-version', "--min", type=parse_version,
467*bb4c5c50SMauro Carvalho Chehab                           help='Sphinx minimal version')
468*bb4c5c50SMauro Carvalho Chehab    ver_group.add_argument('--max-version', "--max", type=parse_version,
469*bb4c5c50SMauro Carvalho Chehab                           help='Sphinx maximum version')
470*bb4c5c50SMauro Carvalho Chehab    ver_group.add_argument('-f', '--full', action='store_true',
471*bb4c5c50SMauro Carvalho Chehab                           help='Add all Sphinx (major,minor) supported versions to the version range')
472*bb4c5c50SMauro Carvalho Chehab
473*bb4c5c50SMauro Carvalho Chehab    build_group = parser.add_argument_group("Build options")
474*bb4c5c50SMauro Carvalho Chehab
475*bb4c5c50SMauro Carvalho Chehab    build_group.add_argument('-b', '--build', action='store_true',
476*bb4c5c50SMauro Carvalho Chehab                             help='Build documentation')
477*bb4c5c50SMauro Carvalho Chehab    build_group.add_argument('-a', '--make-args', nargs="*",
478*bb4c5c50SMauro Carvalho Chehab                             help='extra arguments for make, like SPHINXDIRS=netlink/specs',
479*bb4c5c50SMauro Carvalho Chehab                        )
480*bb4c5c50SMauro Carvalho Chehab    build_group.add_argument('-t', '--targets', nargs="+", choices=MAKE_TARGETS,
481*bb4c5c50SMauro Carvalho Chehab                             default=[MAKE_TARGETS[0]],
482*bb4c5c50SMauro Carvalho Chehab                             help="make build targets. Default: htmldocs.")
483*bb4c5c50SMauro Carvalho Chehab    build_group.add_argument("-o", '--output',
484*bb4c5c50SMauro Carvalho Chehab                             help="output directory for the make O=OUTPUT")
485*bb4c5c50SMauro Carvalho Chehab
486*bb4c5c50SMauro Carvalho Chehab    other_group = parser.add_argument_group("Other options")
487*bb4c5c50SMauro Carvalho Chehab
488*bb4c5c50SMauro Carvalho Chehab    other_group.add_argument('-r', '--req-file', action='store_true',
489*bb4c5c50SMauro Carvalho Chehab                             help='write a requirements.txt file')
490*bb4c5c50SMauro Carvalho Chehab    other_group.add_argument('-l', '--log',
491fb1e8d12SMauro Carvalho Chehab                             help='Log command output on a file')
492*bb4c5c50SMauro Carvalho Chehab    other_group.add_argument('-v', '--verbose', action='store_true',
493*bb4c5c50SMauro Carvalho Chehab                             help='Verbose all commands')
494*bb4c5c50SMauro Carvalho Chehab    other_group.add_argument('-i', '--wait-input', action='store_true',
495*bb4c5c50SMauro Carvalho Chehab                        help='Wait for an enter before going to the next version')
49654c147f4SMauro Carvalho Chehab
49754c147f4SMauro Carvalho Chehab    args = parser.parse_args()
49854c147f4SMauro Carvalho Chehab
49954c147f4SMauro Carvalho Chehab    if not args.make_args:
50054c147f4SMauro Carvalho Chehab        args.make_args = []
50154c147f4SMauro Carvalho Chehab
5027649db7dSMauro Carvalho Chehab    sphinx_versions = sorted(list(SPHINX_REQUIREMENTS.keys()))
50354c147f4SMauro Carvalho Chehab
504791b9b03SMauro Carvalho Chehab    if args.full:
505791b9b03SMauro Carvalho Chehab        args.versions += list(SPHINX_REQUIREMENTS.keys())
50654c147f4SMauro Carvalho Chehab
5077649db7dSMauro Carvalho Chehab    venv = SphinxVenv()
5087649db7dSMauro Carvalho Chehab    await venv.run(args)
50954c147f4SMauro Carvalho Chehab
51054c147f4SMauro Carvalho Chehab
5117649db7dSMauro Carvalho Chehab# Call main method
5127649db7dSMauro Carvalho Chehabif __name__ == "__main__":
5137649db7dSMauro Carvalho Chehab    asyncio.run(main())
514