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: sphinx-build-wrapper: get rid of uapi/media Makefile

Now that kernel-include directive supports parsing data
structs directly, we can finally get rid of the horrible hack
we added to support parsing media uAPI symbols.

As a side effect, Documentation/output doesn't have anymore
media auto-generated .rst files on it.

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

authored by

Mauro Carvalho Chehab and committed by
Jonathan Corbet
8a298579 a49adfab

+745 -76
+1 -2
Documentation/Makefile
··· 87 87 PYTHONPYCACHEPREFIX ?= $(abspath $(BUILDDIR)/__pycache__) 88 88 89 89 quiet_cmd_sphinx = SPHINX $@ --> file://$(abspath $(BUILDDIR)/$3/$4) 90 - cmd_sphinx = $(MAKE) BUILDDIR=$(abspath $(BUILDDIR)) $(build)=Documentation/userspace-api/media $2 && \ 90 + cmd_sphinx = \ 91 91 PYTHONPYCACHEPREFIX="$(PYTHONPYCACHEPREFIX)" \ 92 92 BUILDDIR=$(abspath $(BUILDDIR)) SPHINX_CONF=$(abspath $(src)/$5/$(SPHINX_CONF)) \ 93 93 $(PYTHON3) $(srctree)/scripts/jobserver-exec \ ··· 171 171 172 172 cleandocs: 173 173 $(Q)rm -rf $(BUILDDIR) 174 - $(Q)$(MAKE) BUILDDIR=$(abspath $(BUILDDIR)) $(build)=Documentation/userspace-api/media clean 175 174 176 175 dochelp: 177 176 @echo ' Linux kernel internal documentation in different formats from ReST:'
-64
Documentation/userspace-api/media/Makefile
··· 1 - # SPDX-License-Identifier: GPL-2.0 2 - 3 - # Rules to convert a .h file to inline RST documentation 4 - 5 - SRC_DIR=$(srctree)/Documentation/userspace-api/media 6 - PARSER = $(srctree)/tools/docs/parse-headers.py 7 - UAPI = $(srctree)/include/uapi/linux 8 - KAPI = $(srctree)/include/linux 9 - 10 - FILES = ca.h.rst dmx.h.rst frontend.h.rst net.h.rst \ 11 - videodev2.h.rst media.h.rst cec.h.rst lirc.h.rst 12 - 13 - TARGETS := $(addprefix $(BUILDDIR)/, $(FILES)) 14 - 15 - gen_rst = \ 16 - echo ${PARSER} $< $@ $(SRC_DIR)/$(notdir $@).exceptions; \ 17 - ${PARSER} $< $@ $(SRC_DIR)/$(notdir $@).exceptions 18 - 19 - quiet_gen_rst = echo ' PARSE $(patsubst $(srctree)/%,%,$<)'; \ 20 - ${PARSER} $< $@ $(SRC_DIR)/$(notdir $@).exceptions 21 - 22 - silent_gen_rst = ${gen_rst} 23 - 24 - $(BUILDDIR)/ca.h.rst: ${UAPI}/dvb/ca.h ${PARSER} $(SRC_DIR)/ca.h.rst.exceptions 25 - @$($(quiet)gen_rst) 26 - 27 - $(BUILDDIR)/dmx.h.rst: ${UAPI}/dvb/dmx.h ${PARSER} $(SRC_DIR)/dmx.h.rst.exceptions 28 - @$($(quiet)gen_rst) 29 - 30 - $(BUILDDIR)/frontend.h.rst: ${UAPI}/dvb/frontend.h ${PARSER} $(SRC_DIR)/frontend.h.rst.exceptions 31 - @$($(quiet)gen_rst) 32 - 33 - $(BUILDDIR)/net.h.rst: ${UAPI}/dvb/net.h ${PARSER} $(SRC_DIR)/net.h.rst.exceptions 34 - @$($(quiet)gen_rst) 35 - 36 - $(BUILDDIR)/videodev2.h.rst: ${UAPI}/videodev2.h ${PARSER} $(SRC_DIR)/videodev2.h.rst.exceptions 37 - @$($(quiet)gen_rst) 38 - 39 - $(BUILDDIR)/media.h.rst: ${UAPI}/media.h ${PARSER} $(SRC_DIR)/media.h.rst.exceptions 40 - @$($(quiet)gen_rst) 41 - 42 - $(BUILDDIR)/cec.h.rst: ${UAPI}/cec.h ${PARSER} $(SRC_DIR)/cec.h.rst.exceptions 43 - @$($(quiet)gen_rst) 44 - 45 - $(BUILDDIR)/lirc.h.rst: ${UAPI}/lirc.h ${PARSER} $(SRC_DIR)/lirc.h.rst.exceptions 46 - @$($(quiet)gen_rst) 47 - 48 - # Media build rules 49 - 50 - .PHONY: all html texinfo epub xml latex 51 - 52 - all: $(IMGDOT) $(BUILDDIR) ${TARGETS} 53 - html: all 54 - texinfo: all 55 - epub: all 56 - xml: all 57 - latex: $(IMGPDF) all 58 - linkcheck: 59 - 60 - clean: 61 - -rm -f $(DOTTGT) $(IMGTGT) ${TARGETS} 2>/dev/null 62 - 63 - $(BUILDDIR): 64 - $(Q)mkdir -p $@
Documentation/userspace-api/media/ca.h.rst.exceptions Documentation/userspace-api/media/dvb/ca.h.rst.exceptions
Documentation/userspace-api/media/cec.h.rst.exceptions Documentation/userspace-api/media/cec/cec.h.rst.exceptions
+3 -2
Documentation/userspace-api/media/cec/cec-header.rst
··· 6 6 CEC Header File 7 7 *************** 8 8 9 - .. kernel-include:: $BUILDDIR/cec.h.rst 10 - 9 + .. kernel-include:: include/uapi/linux/cec.h 10 + :generate-cross-refs: 11 + :exception-file: cec.h.rst.exceptions
Documentation/userspace-api/media/dmx.h.rst.exceptions Documentation/userspace-api/media/dvb/dmx.h.rst.exceptions
+13 -4
Documentation/userspace-api/media/dvb/headers.rst
··· 7 7 Digital TV uAPI headers 8 8 *********************** 9 9 10 - .. kernel-include:: $BUILDDIR/frontend.h.rst 10 + .. kernel-include:: include/uapi/linux/dvb/frontend.h 11 + :generate-cross-refs: 12 + :exception-file: frontend.h.rst.exceptions 11 13 12 - .. kernel-include:: $BUILDDIR/dmx.h.rst 14 + .. kernel-include:: include/uapi/linux/dvb/dmx.h 15 + :generate-cross-refs: 16 + :exception-file: dmx.h.rst.exceptions 13 17 14 - .. kernel-include:: $BUILDDIR/ca.h.rst 18 + .. kernel-include:: include/uapi/linux/dvb/ca.h 19 + :generate-cross-refs: 20 + :exception-file: ca.h.rst.exceptions 15 21 16 - .. kernel-include:: $BUILDDIR/net.h.rst 22 + .. kernel-include:: include/uapi/linux/dvb/net.h 23 + :generate-cross-refs: 24 + :exception-file: net.h.rst.exceptions 25 +
Documentation/userspace-api/media/frontend.h.rst.exceptions Documentation/userspace-api/media/dvb/frontend.h.rst.exceptions
Documentation/userspace-api/media/lirc.h.rst.exceptions Documentation/userspace-api/media/rc/lirc.h.rst.exceptions
Documentation/userspace-api/media/media.h.rst.exceptions Documentation/userspace-api/media/mediactl/media.h.rst.exceptions
+3 -2
Documentation/userspace-api/media/mediactl/media-header.rst
··· 6 6 Media Controller Header File 7 7 **************************** 8 8 9 - .. kernel-include:: $BUILDDIR/media.h.rst 10 - 9 + .. kernel-include:: include/uapi/linux/media.h 10 + :generate-cross-refs: 11 + :exception-file: media.h.rst.exceptions
Documentation/userspace-api/media/net.h.rst.exceptions Documentation/userspace-api/media/dvb/net.h.rst.exceptions
+3 -1
Documentation/userspace-api/media/rc/lirc-header.rst
··· 6 6 LIRC Header File 7 7 **************** 8 8 9 - .. kernel-include:: $BUILDDIR/lirc.h.rst 9 + .. kernel-include:: include/uapi/linux/lirc.h 10 + :generate-cross-refs: 11 + :exception-file: lirc.h.rst.exceptions 10 12
+3 -1
Documentation/userspace-api/media/v4l/videodev.rst
··· 6 6 Video For Linux Two Header File 7 7 ******************************* 8 8 9 - .. kernel-include:: $BUILDDIR/videodev2.h.rst 9 + .. kernel-include:: include/uapi/linux/videodev2.h 10 + :generate-cross-refs: 11 + :exception-file: videodev2.h.rst.exceptions
Documentation/userspace-api/media/videodev2.h.rst.exceptions Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions
+719
scripts/sphinx-build-wrapper
··· 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=R0902, R0912, R0913, R0914, R0915, R0917, C0103 6 + # 7 + # Converted from docs Makefile and parallel-wrapper.sh, both under 8 + # GPLv2, copyrighted since 2008 by the following authors: 9 + # 10 + # Akira Yokosawa <akiyks@gmail.com> 11 + # Arnd Bergmann <arnd@arndb.de> 12 + # Breno Leitao <leitao@debian.org> 13 + # Carlos Bilbao <carlos.bilbao@amd.com> 14 + # Dave Young <dyoung@redhat.com> 15 + # Donald Hunter <donald.hunter@gmail.com> 16 + # Geert Uytterhoeven <geert+renesas@glider.be> 17 + # Jani Nikula <jani.nikula@intel.com> 18 + # Jan Stancek <jstancek@redhat.com> 19 + # Jonathan Corbet <corbet@lwn.net> 20 + # Joshua Clayton <stillcompiling@gmail.com> 21 + # Kees Cook <keescook@chromium.org> 22 + # Linus Torvalds <torvalds@linux-foundation.org> 23 + # Magnus Damm <damm+renesas@opensource.se> 24 + # Masahiro Yamada <masahiroy@kernel.org> 25 + # Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 26 + # Maxim Cournoyer <maxim.cournoyer@gmail.com> 27 + # Peter Foley <pefoley2@pefoley.com> 28 + # Randy Dunlap <rdunlap@infradead.org> 29 + # Rob Herring <robh@kernel.org> 30 + # Shuah Khan <shuahkh@osg.samsung.com> 31 + # Thorsten Blum <thorsten.blum@toblux.com> 32 + # Tomas Winkler <tomas.winkler@intel.com> 33 + 34 + 35 + """ 36 + Sphinx build wrapper that handles Kernel-specific business rules: 37 + 38 + - it gets the Kernel build environment vars; 39 + - it determines what's the best parallelism; 40 + - it handles SPHINXDIRS 41 + 42 + This tool ensures that MIN_PYTHON_VERSION is satisfied. If version is 43 + below that, it seeks for a new Python version. If found, it re-runs using 44 + the newer version. 45 + """ 46 + 47 + import argparse 48 + import locale 49 + import os 50 + import re 51 + import shlex 52 + import shutil 53 + import subprocess 54 + import sys 55 + 56 + from concurrent import futures 57 + from glob import glob 58 + 59 + LIB_DIR = "lib" 60 + SRC_DIR = os.path.dirname(os.path.realpath(__file__)) 61 + 62 + sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) 63 + 64 + from jobserver import JobserverExec # pylint: disable=C0413 65 + 66 + 67 + def parse_version(version): 68 + """Convert a major.minor.patch version into a tuple""" 69 + return tuple(int(x) for x in version.split(".")) 70 + 71 + def ver_str(version): 72 + """Returns a version tuple as major.minor.patch""" 73 + 74 + return ".".join([str(x) for x in version]) 75 + 76 + # Minimal supported Python version needed by Sphinx and its extensions 77 + MIN_PYTHON_VERSION = parse_version("3.7") 78 + 79 + # Default value for --venv parameter 80 + VENV_DEFAULT = "sphinx_latest" 81 + 82 + # List of make targets and its corresponding builder and output directory 83 + TARGETS = { 84 + "cleandocs": { 85 + "builder": "clean", 86 + }, 87 + "htmldocs": { 88 + "builder": "html", 89 + }, 90 + "epubdocs": { 91 + "builder": "epub", 92 + "out_dir": "epub", 93 + }, 94 + "texinfodocs": { 95 + "builder": "texinfo", 96 + "out_dir": "texinfo", 97 + }, 98 + "infodocs": { 99 + "builder": "texinfo", 100 + "out_dir": "texinfo", 101 + }, 102 + "latexdocs": { 103 + "builder": "latex", 104 + "out_dir": "latex", 105 + }, 106 + "pdfdocs": { 107 + "builder": "latex", 108 + "out_dir": "latex", 109 + }, 110 + "xmldocs": { 111 + "builder": "xml", 112 + "out_dir": "xml", 113 + }, 114 + "linkcheckdocs": { 115 + "builder": "linkcheck" 116 + }, 117 + } 118 + 119 + # Paper sizes. An empty value will pick the default 120 + PAPER = ["", "a4", "letter"] 121 + 122 + class SphinxBuilder: 123 + """ 124 + Handles a sphinx-build target, adding needed arguments to build 125 + with the Kernel. 126 + """ 127 + 128 + def is_rust_enabled(self): 129 + """Check if rust is enabled at .config""" 130 + config_path = os.path.join(self.srctree, ".config") 131 + if os.path.isfile(config_path): 132 + with open(config_path, "r", encoding="utf-8") as f: 133 + return "CONFIG_RUST=y" in f.read() 134 + return False 135 + 136 + def get_path(self, path, abs_path=False): 137 + """ 138 + Ancillary routine to handle patches the right way, as shell does. 139 + 140 + It first expands "~" and "~user". Then, if patch is not absolute, 141 + join self.srctree. Finally, if requested, convert to abspath. 142 + """ 143 + 144 + path = os.path.expanduser(path) 145 + if not path.startswith("/"): 146 + path = os.path.join(self.srctree, path) 147 + 148 + if abs_path: 149 + return os.path.abspath(path) 150 + 151 + return path 152 + 153 + def __init__(self, venv=None, verbose=False, n_jobs=None, interactive=None): 154 + """Initialize internal variables""" 155 + self.venv = venv 156 + self.verbose = None 157 + 158 + # Normal variables passed from Kernel's makefile 159 + self.kernelversion = os.environ.get("KERNELVERSION", "unknown") 160 + self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown") 161 + self.pdflatex = os.environ.get("PDFLATEX", "xelatex") 162 + 163 + if not interactive: 164 + self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape") 165 + else: 166 + self.latexopts = os.environ.get("LATEXOPTS", "") 167 + 168 + if not verbose: 169 + verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "") 170 + 171 + # Handle SPHINXOPTS evironment 172 + sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", "")) 173 + 174 + # As we handle number of jobs and quiet in separate, we need to pick 175 + # it the same way as sphinx-build would pick, so let's use argparse 176 + # do to the right argument expansion 177 + parser = argparse.ArgumentParser() 178 + parser.add_argument('-j', '--jobs', type=int) 179 + parser.add_argument('-q', '--quiet', type=int) 180 + 181 + # Other sphinx-build arguments go as-is, so place them 182 + # at self.sphinxopts 183 + sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts) 184 + if sphinx_args.quiet == True: 185 + self.verbose = False 186 + 187 + if sphinx_args.jobs: 188 + self.n_jobs = sphinx_args.jobs 189 + 190 + # Command line arguments was passed, override SPHINXOPTS 191 + if verbose is not None: 192 + self.verbose = verbose 193 + 194 + self.n_jobs = n_jobs 195 + 196 + # Source tree directory. This needs to be at os.environ, as 197 + # Sphinx extensions and media uAPI makefile needs it 198 + self.srctree = os.environ.get("srctree") 199 + if not self.srctree: 200 + self.srctree = "." 201 + os.environ["srctree"] = self.srctree 202 + 203 + # Now that we can expand srctree, get other directories as well 204 + self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build") 205 + self.kerneldoc = self.get_path(os.environ.get("KERNELDOC", 206 + "scripts/kernel-doc.py")) 207 + self.obj = os.environ.get("obj", "Documentation") 208 + self.builddir = self.get_path(os.path.join(self.obj, "output"), 209 + abs_path=True) 210 + 211 + # Media uAPI needs it 212 + os.environ["BUILDDIR"] = self.builddir 213 + 214 + # Detect if rust is enabled 215 + self.config_rust = self.is_rust_enabled() 216 + 217 + # Get directory locations for LaTeX build toolchain 218 + self.pdflatex_cmd = shutil.which(self.pdflatex) 219 + self.latexmk_cmd = shutil.which("latexmk") 220 + 221 + self.env = os.environ.copy() 222 + 223 + # If venv parameter is specified, run Sphinx from venv 224 + if venv: 225 + bin_dir = os.path.join(venv, "bin") 226 + if os.path.isfile(os.path.join(bin_dir, "activate")): 227 + # "activate" virtual env 228 + self.env["PATH"] = bin_dir + ":" + self.env["PATH"] 229 + self.env["VIRTUAL_ENV"] = venv 230 + if "PYTHONHOME" in self.env: 231 + del self.env["PYTHONHOME"] 232 + print(f"Setting venv to {venv}") 233 + else: 234 + sys.exit(f"Venv {venv} not found.") 235 + 236 + def run_sphinx(self, sphinx_build, build_args, *args, **pwargs): 237 + """ 238 + Executes sphinx-build using current python3 command and setting 239 + -j parameter if possible to run the build in parallel. 240 + """ 241 + 242 + with JobserverExec() as jobserver: 243 + if jobserver.claim: 244 + n_jobs = str(jobserver.claim) 245 + else: 246 + n_jobs = "auto" # Supported since Sphinx 1.7 247 + 248 + cmd = [] 249 + 250 + if self.venv: 251 + cmd.append("python") 252 + else: 253 + cmd.append(sys.executable) 254 + 255 + cmd.append(sphinx_build) 256 + 257 + # if present, SPHINXOPTS or command line --jobs overrides default 258 + if self.n_jobs: 259 + n_jobs = str(self.n_jobs) 260 + 261 + if n_jobs: 262 + cmd += [f"-j{n_jobs}"] 263 + 264 + if not self.verbose: 265 + cmd.append("-q") 266 + 267 + cmd += self.sphinxopts 268 + 269 + cmd += build_args 270 + 271 + if self.verbose: 272 + print(" ".join(cmd)) 273 + 274 + rc = subprocess.call(cmd, *args, **pwargs) 275 + 276 + def handle_html(self, css, output_dir): 277 + """ 278 + Extra steps for HTML and epub output. 279 + 280 + For such targets, we need to ensure that CSS will be properly 281 + copied to the output _static directory 282 + """ 283 + 284 + if not css: 285 + return 286 + 287 + css = os.path.expanduser(css) 288 + if not css.startswith("/"): 289 + css = os.path.join(self.srctree, css) 290 + 291 + static_dir = os.path.join(output_dir, "_static") 292 + os.makedirs(static_dir, exist_ok=True) 293 + 294 + try: 295 + shutil.copy2(css, static_dir) 296 + except (OSError, IOError) as e: 297 + print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr) 298 + 299 + def build_pdf_file(self, latex_cmd, from_dir, path): 300 + """Builds a single pdf file using latex_cmd""" 301 + try: 302 + subprocess.run(latex_cmd + [path], 303 + cwd=from_dir, check=True) 304 + 305 + return True 306 + except subprocess.CalledProcessError: 307 + # LaTeX PDF error code is almost useless: it returns 308 + # error codes even when build succeeds but has warnings. 309 + # So, we'll ignore the results 310 + return False 311 + 312 + def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs): 313 + """Build PDF files in parallel if possible""" 314 + builds = {} 315 + build_failed = False 316 + max_len = 0 317 + has_tex = False 318 + 319 + # Process files in parallel 320 + with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor: 321 + jobs = {} 322 + 323 + for from_dir, pdf_dir, entry in tex_files: 324 + name = entry.name 325 + 326 + if not name.endswith(tex_suffix): 327 + continue 328 + 329 + name = name[:-len(tex_suffix)] 330 + 331 + max_len = max(max_len, len(name)) 332 + 333 + has_tex = True 334 + 335 + future = executor.submit(self.build_pdf_file, latex_cmd, 336 + from_dir, entry.path) 337 + jobs[future] = (from_dir, name, entry.path) 338 + 339 + for future in futures.as_completed(jobs): 340 + from_dir, name, path = jobs[future] 341 + 342 + pdf_name = name + ".pdf" 343 + pdf_from = os.path.join(from_dir, pdf_name) 344 + 345 + try: 346 + success = future.result() 347 + 348 + if success and os.path.exists(pdf_from): 349 + pdf_to = os.path.join(pdf_dir, pdf_name) 350 + 351 + os.rename(pdf_from, pdf_to) 352 + builds[name] = os.path.relpath(pdf_to, self.builddir) 353 + else: 354 + builds[name] = "FAILED" 355 + build_failed = True 356 + except Exception as e: 357 + builds[name] = f"FAILED ({str(e)})" 358 + build_failed = True 359 + 360 + # Handle case where no .tex files were found 361 + if not has_tex: 362 + name = "Sphinx LaTeX builder" 363 + max_len = max(max_len, len(name)) 364 + builds[name] = "FAILED (no .tex file was generated)" 365 + build_failed = True 366 + 367 + return builds, build_failed, max_len 368 + 369 + def handle_pdf(self, output_dirs): 370 + """ 371 + Extra steps for PDF output. 372 + 373 + As PDF is handled via a LaTeX output, after building the .tex file, 374 + a new build is needed to create the PDF output from the latex 375 + directory. 376 + """ 377 + builds = {} 378 + max_len = 0 379 + tex_suffix = ".tex" 380 + 381 + # Get all tex files that will be used for PDF build 382 + tex_files = [] 383 + for from_dir in output_dirs: 384 + pdf_dir = os.path.join(from_dir, "../pdf") 385 + os.makedirs(pdf_dir, exist_ok=True) 386 + 387 + if self.latexmk_cmd: 388 + latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"] 389 + else: 390 + latex_cmd = [self.pdflatex] 391 + 392 + latex_cmd.extend(shlex.split(self.latexopts)) 393 + 394 + # Get a list of tex files to process 395 + with os.scandir(from_dir) as it: 396 + for entry in it: 397 + if entry.name.endswith(tex_suffix): 398 + tex_files.append((from_dir, pdf_dir, entry)) 399 + 400 + # When using make, this won't be used, as the number of jobs comes 401 + # from POSIX jobserver. So, this covers the case where build comes 402 + # from command line. On such case, serialize by default, except if 403 + # the user explicitly sets the number of jobs. 404 + n_jobs = 1 405 + 406 + # n_jobs is either an integer or "auto". Only use it if it is a number 407 + if self.n_jobs: 408 + try: 409 + n_jobs = int(self.n_jobs) 410 + except ValueError: 411 + pass 412 + 413 + # When using make, jobserver.claim is the number of jobs that were 414 + # used with "-j" and that aren't used by other make targets 415 + with JobserverExec() as jobserver: 416 + n_jobs = 1 417 + 418 + # Handle the case when a parameter is passed via command line, 419 + # using it as default, if jobserver doesn't claim anything 420 + if self.n_jobs: 421 + try: 422 + n_jobs = int(self.n_jobs) 423 + except ValueError: 424 + pass 425 + 426 + if jobserver.claim: 427 + n_jobs = jobserver.claim 428 + 429 + # Build files in parallel 430 + builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix, 431 + latex_cmd, 432 + tex_files, 433 + n_jobs) 434 + 435 + msg = "Summary" 436 + msg += "\n" + "=" * len(msg) 437 + print() 438 + print(msg) 439 + 440 + for pdf_name, pdf_file in builds.items(): 441 + print(f"{pdf_name:<{max_len}}: {pdf_file}") 442 + 443 + print() 444 + 445 + # return an error if a PDF file is missing 446 + 447 + if build_failed: 448 + sys.exit(f"PDF build failed: not all PDF files were created.") 449 + else: 450 + print("All PDF files were built.") 451 + 452 + def handle_info(self, output_dirs): 453 + """ 454 + Extra steps for Info output. 455 + 456 + For texinfo generation, an additional make is needed from the 457 + texinfo directory. 458 + """ 459 + 460 + for output_dir in output_dirs: 461 + try: 462 + subprocess.run(["make", "info"], cwd=output_dir, check=True) 463 + except subprocess.CalledProcessError as e: 464 + sys.exit(f"Error generating info docs: {e}") 465 + 466 + def cleandocs(self, builder): 467 + 468 + shutil.rmtree(self.builddir, ignore_errors=True) 469 + 470 + def build(self, target, sphinxdirs=None, conf="conf.py", 471 + theme=None, css=None, paper=None): 472 + """ 473 + Build documentation using Sphinx. This is the core function of this 474 + module. It prepares all arguments required by sphinx-build. 475 + """ 476 + 477 + builder = TARGETS[target]["builder"] 478 + out_dir = TARGETS[target].get("out_dir", "") 479 + 480 + # Cleandocs doesn't require sphinx-build 481 + if target == "cleandocs": 482 + self.cleandocs(builder) 483 + return 484 + 485 + # Other targets require sphinx-build 486 + sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"]) 487 + if not sphinxbuild: 488 + sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n") 489 + 490 + if builder == "latex": 491 + if not self.pdflatex_cmd and not self.latexmk_cmd: 492 + sys.exit("Error: pdflatex or latexmk required for PDF generation") 493 + 494 + docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation")) 495 + 496 + # Prepare base arguments for Sphinx build 497 + kerneldoc = self.kerneldoc 498 + if kerneldoc.startswith(self.srctree): 499 + kerneldoc = os.path.relpath(kerneldoc, self.srctree) 500 + 501 + # Prepare common Sphinx options 502 + args = [ 503 + "-b", builder, 504 + "-c", docs_dir, 505 + ] 506 + 507 + if builder == "latex": 508 + if not paper: 509 + paper = PAPER[1] 510 + 511 + args.extend(["-D", f"latex_elements.papersize={paper}paper"]) 512 + 513 + if self.config_rust: 514 + args.extend(["-t", "rustdoc"]) 515 + 516 + if conf: 517 + self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True) 518 + 519 + if not sphinxdirs: 520 + sphinxdirs = os.environ.get("SPHINXDIRS", ".") 521 + 522 + # The sphinx-build tool has a bug: internally, it tries to set 523 + # locale with locale.setlocale(locale.LC_ALL, ''). This causes a 524 + # crash if language is not set. Detect and fix it. 525 + try: 526 + locale.setlocale(locale.LC_ALL, '') 527 + except Exception: 528 + self.env["LC_ALL"] = "C" 529 + self.env["LANG"] = "C" 530 + 531 + # sphinxdirs can be a list or a whitespace-separated string 532 + sphinxdirs_list = [] 533 + for sphinxdir in sphinxdirs: 534 + if isinstance(sphinxdir, list): 535 + sphinxdirs_list += sphinxdir 536 + else: 537 + for name in sphinxdir.split(" "): 538 + sphinxdirs_list.append(name) 539 + 540 + # Build each directory 541 + output_dirs = [] 542 + for sphinxdir in sphinxdirs_list: 543 + src_dir = os.path.join(docs_dir, sphinxdir) 544 + doctree_dir = os.path.join(self.builddir, ".doctrees") 545 + output_dir = os.path.join(self.builddir, sphinxdir, out_dir) 546 + 547 + # Make directory names canonical 548 + src_dir = os.path.normpath(src_dir) 549 + doctree_dir = os.path.normpath(doctree_dir) 550 + output_dir = os.path.normpath(output_dir) 551 + 552 + os.makedirs(doctree_dir, exist_ok=True) 553 + os.makedirs(output_dir, exist_ok=True) 554 + 555 + output_dirs.append(output_dir) 556 + 557 + build_args = args + [ 558 + "-d", doctree_dir, 559 + "-D", f"kerneldoc_bin={kerneldoc}", 560 + "-D", f"version={self.kernelversion}", 561 + "-D", f"release={self.kernelrelease}", 562 + "-D", f"kerneldoc_srctree={self.srctree}", 563 + src_dir, 564 + output_dir, 565 + ] 566 + 567 + # Execute sphinx-build 568 + try: 569 + self.run_sphinx(sphinxbuild, build_args, env=self.env) 570 + except Exception as e: 571 + sys.exit(f"Build failed: {e}") 572 + 573 + # Ensure that html/epub will have needed static files 574 + if target in ["htmldocs", "epubdocs"]: 575 + self.handle_html(css, output_dir) 576 + 577 + # PDF and Info require a second build step 578 + if target == "pdfdocs": 579 + self.handle_pdf(output_dirs) 580 + elif target == "infodocs": 581 + self.handle_info(output_dirs) 582 + 583 + @staticmethod 584 + def get_python_version(cmd): 585 + """ 586 + Get python version from a Python binary. As we need to detect if 587 + are out there newer python binaries, we can't rely on sys.release here. 588 + """ 589 + 590 + result = subprocess.run([cmd, "--version"], check=True, 591 + stdout=subprocess.PIPE, stderr=subprocess.PIPE, 592 + universal_newlines=True) 593 + version = result.stdout.strip() 594 + 595 + match = re.search(r"(\d+\.\d+\.\d+)", version) 596 + if match: 597 + return parse_version(match.group(1)) 598 + 599 + print(f"Can't parse version {version}") 600 + return (0, 0, 0) 601 + 602 + @staticmethod 603 + def find_python(): 604 + """ 605 + Detect if are out there any python 3.xy version newer than the 606 + current one. 607 + 608 + Note: this routine is limited to up to 2 digits for python3. We 609 + may need to update it one day, hopefully on a distant future. 610 + """ 611 + patterns = [ 612 + "python3.[0-9]", 613 + "python3.[0-9][0-9]", 614 + ] 615 + 616 + # Seek for a python binary newer than MIN_PYTHON_VERSION 617 + for path in os.getenv("PATH", "").split(":"): 618 + for pattern in patterns: 619 + for cmd in glob(os.path.join(path, pattern)): 620 + if os.path.isfile(cmd) and os.access(cmd, os.X_OK): 621 + version = SphinxBuilder.get_python_version(cmd) 622 + if version >= MIN_PYTHON_VERSION: 623 + return cmd 624 + 625 + return None 626 + 627 + @staticmethod 628 + def check_python(): 629 + """ 630 + Check if the current python binary satisfies our minimal requirement 631 + for Sphinx build. If not, re-run with a newer version if found. 632 + """ 633 + cur_ver = sys.version_info[:3] 634 + if cur_ver >= MIN_PYTHON_VERSION: 635 + return 636 + 637 + python_ver = ver_str(cur_ver) 638 + 639 + new_python_cmd = SphinxBuilder.find_python() 640 + if not new_python_cmd: 641 + sys.exit(f"Python version {python_ver} is not supported anymore.") 642 + 643 + # Restart script using the newer version 644 + script_path = os.path.abspath(sys.argv[0]) 645 + args = [new_python_cmd, script_path] + sys.argv[1:] 646 + 647 + print(f"Python {python_ver} not supported. Changing to {new_python_cmd}") 648 + 649 + try: 650 + os.execv(new_python_cmd, args) 651 + except OSError as e: 652 + sys.exit(f"Failed to restart with {new_python_cmd}: {e}") 653 + 654 + def jobs_type(value): 655 + """ 656 + Handle valid values for -j. Accepts Sphinx "-jauto", plus a number 657 + equal or bigger than one. 658 + """ 659 + if value is None: 660 + return None 661 + 662 + if value.lower() == 'auto': 663 + return value.lower() 664 + 665 + try: 666 + if int(value) >= 1: 667 + return value 668 + 669 + raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}") 670 + except ValueError: 671 + raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}") 672 + 673 + def main(): 674 + """ 675 + Main function. The only mandatory argument is the target. If not 676 + specified, the other arguments will use default values if not 677 + specified at os.environ. 678 + """ 679 + parser = argparse.ArgumentParser(description="Kernel documentation builder") 680 + 681 + parser.add_argument("target", choices=list(TARGETS.keys()), 682 + help="Documentation target to build") 683 + parser.add_argument("--sphinxdirs", nargs="+", 684 + help="Specific directories to build") 685 + parser.add_argument("--conf", default="conf.py", 686 + help="Sphinx configuration file") 687 + 688 + parser.add_argument("--theme", help="Sphinx theme to use") 689 + 690 + parser.add_argument("--css", help="Custom CSS file for HTML/EPUB") 691 + 692 + parser.add_argument("--paper", choices=PAPER, default=PAPER[0], 693 + help="Paper size for LaTeX/PDF output") 694 + 695 + parser.add_argument("-v", "--verbose", action='store_true', 696 + help="place build in verbose mode") 697 + 698 + parser.add_argument('-j', '--jobs', type=jobs_type, 699 + help="Sets number of jobs to use with sphinx-build") 700 + 701 + parser.add_argument('-i', '--interactive', action='store_true', 702 + help="Change latex default to run in interactive mode") 703 + 704 + parser.add_argument("-V", "--venv", nargs='?', const=f'{VENV_DEFAULT}', 705 + default=None, 706 + help=f'If used, run Sphinx from a venv dir (default dir: {VENV_DEFAULT})') 707 + 708 + args = parser.parse_args() 709 + 710 + SphinxBuilder.check_python() 711 + 712 + builder = SphinxBuilder(venv=args.venv, verbose=args.verbose, 713 + n_jobs=args.jobs, interactive=args.interactive) 714 + 715 + builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf, 716 + theme=args.theme, css=args.css, paper=args.paper) 717 + 718 + if __name__ == "__main__": 719 + main()