Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

scripts: scripts/test_doc_build.py: add script to test doc build

Testing Sphinx backward-compatibility is hard, as per version
minimal Python dependency requirements can be a nightmare.

Add a script to help automate such checks.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Signed-off-by: Jonathan Corbet <corbet@lwn.net>
Link: https://lore.kernel.org/r/93faf6c35ec865566246ca094868a8e6d85dde39.1750571906.git.mchehab+huawei@kernel.org

authored by

Mauro Carvalho Chehab and committed by
Jonathan Corbet
54c147f4 30c83405

+241
+241
scripts/test_doc_build.py
··· 1 + #!/usr/bin/env python3 2 + # SPDX-License-Identifier: GPL-2.0 3 + # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 4 + # 5 + # pylint: disable=C0103,R1715 6 + 7 + """ 8 + Install minimal supported requirements for different Sphinx versions 9 + and optionally test the build. 10 + """ 11 + 12 + import argparse 13 + import os.path 14 + import sys 15 + import time 16 + 17 + from subprocess import run 18 + 19 + # Minimal python version supported by the building system 20 + python_bin = "python3.9" 21 + 22 + # Starting from 8.0.2, Python 3.9 becomes too old 23 + python_changes = {(8, 0, 2): "python3"} 24 + 25 + # Sphinx versions to be installed and their incremental requirements 26 + sphinx_requirements = { 27 + (3, 4, 3): { 28 + "alabaster": "0.7.13", 29 + "babel": "2.17.0", 30 + "certifi": "2025.6.15", 31 + "charset-normalizer": "3.4.2", 32 + "docutils": "0.15", 33 + "idna": "3.10", 34 + "imagesize": "1.4.1", 35 + "Jinja2": "3.0.3", 36 + "MarkupSafe": "2.0", 37 + "packaging": "25.0", 38 + "Pygments": "2.19.1", 39 + "PyYAML": "5.1", 40 + "requests": "2.32.4", 41 + "snowballstemmer": "3.0.1", 42 + "sphinxcontrib-applehelp": "1.0.4", 43 + "sphinxcontrib-devhelp": "1.0.2", 44 + "sphinxcontrib-htmlhelp": "2.0.1", 45 + "sphinxcontrib-jsmath": "1.0.1", 46 + "sphinxcontrib-qthelp": "1.0.3", 47 + "sphinxcontrib-serializinghtml": "1.1.5", 48 + "urllib3": "2.4.0", 49 + }, 50 + (3, 5, 4): {}, 51 + (4, 0, 3): { 52 + "docutils": "0.17.1", 53 + "PyYAML": "5.1", 54 + }, 55 + (4, 1, 2): {}, 56 + (4, 3, 2): {}, 57 + (4, 4, 0): {}, 58 + (4, 5, 0): {}, 59 + (5, 0, 2): {}, 60 + (5, 1, 1): {}, 61 + (5, 2, 3): { 62 + "Jinja2": "3.1.2", 63 + "MarkupSafe": "2.0", 64 + "PyYAML": "5.3.1", 65 + }, 66 + (5, 3, 0): { 67 + "docutils": "0.18.1", 68 + "PyYAML": "5.3.1", 69 + }, 70 + (6, 0, 1): {}, 71 + (6, 1, 3): {}, 72 + (6, 2, 1): { 73 + "PyYAML": "5.4.1", 74 + }, 75 + (7, 0, 1): {}, 76 + (7, 1, 2): {}, 77 + (7, 2, 3): { 78 + "PyYAML": "6.0.1", 79 + "sphinxcontrib-serializinghtml": "1.1.9", 80 + }, 81 + (7, 3, 7): { 82 + "alabaster": "0.7.14", 83 + "PyYAML": "6.0.1", 84 + }, 85 + (7, 4, 7): { 86 + "docutils": "0.20", 87 + "PyYAML": "6.0.1", 88 + }, 89 + (8, 0, 2): {}, 90 + (8, 1, 3): { 91 + "PyYAML": "6.0.1", 92 + "sphinxcontrib-applehelp": "1.0.7", 93 + "sphinxcontrib-devhelp": "1.0.6", 94 + "sphinxcontrib-htmlhelp": "2.0.6", 95 + "sphinxcontrib-qthelp": "1.0.6", 96 + }, 97 + (8, 2, 3): { 98 + "PyYAML": "6.0.1", 99 + "sphinxcontrib-serializinghtml": "1.1.9", 100 + }, 101 + } 102 + 103 + 104 + def parse_version(ver_str): 105 + """Convert a version string into a tuple.""" 106 + 107 + return tuple(map(int, ver_str.split("."))) 108 + 109 + 110 + parser = argparse.ArgumentParser(description="Build docs for different sphinx_versions.") 111 + 112 + parser.add_argument('-v', '--version', help='Sphinx single version', 113 + type=parse_version) 114 + parser.add_argument('--min-version', "--min", help='Sphinx minimal version', 115 + type=parse_version) 116 + parser.add_argument('--max-version', "--max", help='Sphinx maximum version', 117 + type=parse_version) 118 + parser.add_argument('-a', '--make_args', 119 + help='extra arguments for make htmldocs, like SPHINXDIRS=netlink/specs', 120 + nargs="*") 121 + parser.add_argument('-w', '--write', help='write a requirements.txt file', 122 + action='store_true') 123 + parser.add_argument('-m', '--make', 124 + help='Make documentation', 125 + action='store_true') 126 + parser.add_argument('-i', '--wait-input', 127 + help='Wait for an enter before going to the next version', 128 + action='store_true') 129 + 130 + args = parser.parse_args() 131 + 132 + if not args.make_args: 133 + args.make_args = [] 134 + 135 + if args.version: 136 + if args.min_version or args.max_version: 137 + sys.exit("Use either --version or --min-version/--max-version") 138 + else: 139 + args.min_version = args.version 140 + args.max_version = args.version 141 + 142 + sphinx_versions = sorted(list(sphinx_requirements.keys())) 143 + 144 + if not args.min_version: 145 + args.min_version = sphinx_versions[0] 146 + 147 + if not args.max_version: 148 + args.max_version = sphinx_versions[-1] 149 + 150 + first_run = True 151 + cur_requirements = {} 152 + built_time = {} 153 + 154 + for cur_ver, new_reqs in sphinx_requirements.items(): 155 + cur_requirements.update(new_reqs) 156 + 157 + if cur_ver in python_changes: 158 + python_bin = python_changes[cur_ver] 159 + 160 + ver = ".".join(map(str, cur_ver)) 161 + 162 + if args.min_version: 163 + if cur_ver < args.min_version: 164 + continue 165 + 166 + if args.max_version: 167 + if cur_ver > args.max_version: 168 + break 169 + 170 + if not first_run and args.wait_input and args.make: 171 + ret = input("Press Enter to continue or 'a' to abort: ").strip().lower() 172 + if ret == "a": 173 + print("Aborted.") 174 + sys.exit() 175 + else: 176 + first_run = False 177 + 178 + venv_dir = f"Sphinx_{ver}" 179 + req_file = f"requirements_{ver}.txt" 180 + 181 + print(f"\nSphinx {ver} with {python_bin}") 182 + 183 + # Create venv 184 + run([python_bin, "-m", "venv", venv_dir], check=True) 185 + pip = os.path.join(venv_dir, "bin/pip") 186 + 187 + # Create install list 188 + reqs = [] 189 + for pkg, verstr in cur_requirements.items(): 190 + reqs.append(f"{pkg}=={verstr}") 191 + 192 + reqs.append(f"Sphinx=={ver}") 193 + 194 + run([pip, "install"] + reqs, check=True) 195 + 196 + # Freeze environment 197 + result = run([pip, "freeze"], capture_output=True, text=True, check=True) 198 + 199 + # Pip install succeeded. Write requirements file 200 + if args.write: 201 + with open(req_file, "w", encoding="utf-8") as fp: 202 + fp.write(result.stdout) 203 + 204 + if args.make: 205 + start_time = time.time() 206 + 207 + # Prepare a venv environment 208 + env = os.environ.copy() 209 + bin_dir = os.path.join(venv_dir, "bin") 210 + env["PATH"] = bin_dir + ":" + env["PATH"] 211 + env["VIRTUAL_ENV"] = venv_dir 212 + if "PYTHONHOME" in env: 213 + del env["PYTHONHOME"] 214 + 215 + # Test doc build 216 + run(["make", "cleandocs"], env=env, check=True) 217 + make = ["make"] + args.make_args + ["htmldocs"] 218 + 219 + print(f". {bin_dir}/activate") 220 + print(" ".join(make)) 221 + print("deactivate") 222 + run(make, env=env, check=True) 223 + 224 + end_time = time.time() 225 + elapsed_time = end_time - start_time 226 + hours, minutes = divmod(elapsed_time, 3600) 227 + minutes, seconds = divmod(minutes, 60) 228 + 229 + hours = int(hours) 230 + minutes = int(minutes) 231 + seconds = int(seconds) 232 + 233 + built_time[ver] = f"{hours:02d}:{minutes:02d}:{seconds:02d}" 234 + 235 + print(f"Finished doc build for Sphinx {ver}. Elapsed time: {built_time[ver]}") 236 + 237 + if args.make: 238 + print() 239 + print("Summary:") 240 + for ver, elapsed_time in sorted(built_time.items()): 241 + print(f"\tSphinx {ver} elapsed time: {elapsed_time}")