1*819667bcSMauro Carvalho Chehab#!/usr/bin/env python3 2*819667bcSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0 3*819667bcSMauro Carvalho Chehab# Copyright (C) 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 4*819667bcSMauro Carvalho Chehab# 5*819667bcSMauro Carvalho Chehab# pylint: disable=R0902, R0912, R0913, R0914, R0915, R0917, C0103 6*819667bcSMauro Carvalho Chehab# 7*819667bcSMauro Carvalho Chehab# Converted from docs Makefile and parallel-wrapper.sh, both under 8*819667bcSMauro Carvalho Chehab# GPLv2, copyrighted since 2008 by the following authors: 9*819667bcSMauro Carvalho Chehab# 10*819667bcSMauro Carvalho Chehab# Akira Yokosawa <akiyks@gmail.com> 11*819667bcSMauro Carvalho Chehab# Arnd Bergmann <arnd@arndb.de> 12*819667bcSMauro Carvalho Chehab# Breno Leitao <leitao@debian.org> 13*819667bcSMauro Carvalho Chehab# Carlos Bilbao <carlos.bilbao@amd.com> 14*819667bcSMauro Carvalho Chehab# Dave Young <dyoung@redhat.com> 15*819667bcSMauro Carvalho Chehab# Donald Hunter <donald.hunter@gmail.com> 16*819667bcSMauro Carvalho Chehab# Geert Uytterhoeven <geert+renesas@glider.be> 17*819667bcSMauro Carvalho Chehab# Jani Nikula <jani.nikula@intel.com> 18*819667bcSMauro Carvalho Chehab# Jan Stancek <jstancek@redhat.com> 19*819667bcSMauro Carvalho Chehab# Jonathan Corbet <corbet@lwn.net> 20*819667bcSMauro Carvalho Chehab# Joshua Clayton <stillcompiling@gmail.com> 21*819667bcSMauro Carvalho Chehab# Kees Cook <keescook@chromium.org> 22*819667bcSMauro Carvalho Chehab# Linus Torvalds <torvalds@linux-foundation.org> 23*819667bcSMauro Carvalho Chehab# Magnus Damm <damm+renesas@opensource.se> 24*819667bcSMauro Carvalho Chehab# Masahiro Yamada <masahiroy@kernel.org> 25*819667bcSMauro Carvalho Chehab# Mauro Carvalho Chehab <mchehab+huawei@kernel.org> 26*819667bcSMauro Carvalho Chehab# Maxim Cournoyer <maxim.cournoyer@gmail.com> 27*819667bcSMauro Carvalho Chehab# Peter Foley <pefoley2@pefoley.com> 28*819667bcSMauro Carvalho Chehab# Randy Dunlap <rdunlap@infradead.org> 29*819667bcSMauro Carvalho Chehab# Rob Herring <robh@kernel.org> 30*819667bcSMauro Carvalho Chehab# Shuah Khan <shuahkh@osg.samsung.com> 31*819667bcSMauro Carvalho Chehab# Thorsten Blum <thorsten.blum@toblux.com> 32*819667bcSMauro Carvalho Chehab# Tomas Winkler <tomas.winkler@intel.com> 33*819667bcSMauro Carvalho Chehab 34*819667bcSMauro Carvalho Chehab 35*819667bcSMauro Carvalho Chehab""" 36*819667bcSMauro Carvalho ChehabSphinx build wrapper that handles Kernel-specific business rules: 37*819667bcSMauro Carvalho Chehab 38*819667bcSMauro Carvalho Chehab- it gets the Kernel build environment vars; 39*819667bcSMauro Carvalho Chehab- it determines what's the best parallelism; 40*819667bcSMauro Carvalho Chehab- it handles SPHINXDIRS 41*819667bcSMauro Carvalho Chehab 42*819667bcSMauro Carvalho ChehabThis tool ensures that MIN_PYTHON_VERSION is satisfied. If version is 43*819667bcSMauro Carvalho Chehabbelow that, it seeks for a new Python version. If found, it re-runs using 44*819667bcSMauro Carvalho Chehabthe newer version. 45*819667bcSMauro Carvalho Chehab""" 46*819667bcSMauro Carvalho Chehab 47*819667bcSMauro Carvalho Chehabimport argparse 48*819667bcSMauro Carvalho Chehabimport os 49*819667bcSMauro Carvalho Chehabimport shlex 50*819667bcSMauro Carvalho Chehabimport shutil 51*819667bcSMauro Carvalho Chehabimport subprocess 52*819667bcSMauro Carvalho Chehabimport sys 53*819667bcSMauro Carvalho Chehab 54*819667bcSMauro Carvalho Chehabfrom lib.python_version import PythonVersion 55*819667bcSMauro Carvalho Chehabfrom lib.latex_fonts import LatexFontChecker 56*819667bcSMauro Carvalho Chehab 57*819667bcSMauro Carvalho ChehabLIB_DIR = "../../scripts/lib" 58*819667bcSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__)) 59*819667bcSMauro Carvalho Chehab 60*819667bcSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) 61*819667bcSMauro Carvalho Chehab 62*819667bcSMauro Carvalho Chehabfrom jobserver import JobserverExec # pylint: disable=C0413,C0411,E0401 63*819667bcSMauro Carvalho Chehab 64*819667bcSMauro Carvalho Chehab# 65*819667bcSMauro Carvalho Chehab# Some constants 66*819667bcSMauro Carvalho Chehab# 67*819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version 68*819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"] 69*819667bcSMauro Carvalho Chehab 70*819667bcSMauro Carvalho ChehabTARGETS = { 71*819667bcSMauro Carvalho Chehab "cleandocs": { "builder": "clean" }, 72*819667bcSMauro Carvalho Chehab "linkcheckdocs": { "builder": "linkcheck" }, 73*819667bcSMauro Carvalho Chehab "htmldocs": { "builder": "html" }, 74*819667bcSMauro Carvalho Chehab "epubdocs": { "builder": "epub", "out_dir": "epub" }, 75*819667bcSMauro Carvalho Chehab "texinfodocs": { "builder": "texinfo", "out_dir": "texinfo" }, 76*819667bcSMauro Carvalho Chehab "infodocs": { "builder": "texinfo", "out_dir": "texinfo" }, 77*819667bcSMauro Carvalho Chehab "latexdocs": { "builder": "latex", "out_dir": "latex" }, 78*819667bcSMauro Carvalho Chehab "pdfdocs": { "builder": "latex", "out_dir": "latex" }, 79*819667bcSMauro Carvalho Chehab "xmldocs": { "builder": "xml", "out_dir": "xml" }, 80*819667bcSMauro Carvalho Chehab} 81*819667bcSMauro Carvalho Chehab 82*819667bcSMauro Carvalho Chehab 83*819667bcSMauro Carvalho Chehab# 84*819667bcSMauro Carvalho Chehab# SphinxBuilder class 85*819667bcSMauro Carvalho Chehab# 86*819667bcSMauro Carvalho Chehab 87*819667bcSMauro Carvalho Chehabclass SphinxBuilder: 88*819667bcSMauro Carvalho Chehab """ 89*819667bcSMauro Carvalho Chehab Handles a sphinx-build target, adding needed arguments to build 90*819667bcSMauro Carvalho Chehab with the Kernel. 91*819667bcSMauro Carvalho Chehab """ 92*819667bcSMauro Carvalho Chehab 93*819667bcSMauro Carvalho Chehab def is_rust_enabled(self): 94*819667bcSMauro Carvalho Chehab """Check if rust is enabled at .config""" 95*819667bcSMauro Carvalho Chehab config_path = os.path.join(self.srctree, ".config") 96*819667bcSMauro Carvalho Chehab if os.path.isfile(config_path): 97*819667bcSMauro Carvalho Chehab with open(config_path, "r", encoding="utf-8") as f: 98*819667bcSMauro Carvalho Chehab return "CONFIG_RUST=y" in f.read() 99*819667bcSMauro Carvalho Chehab return False 100*819667bcSMauro Carvalho Chehab 101*819667bcSMauro Carvalho Chehab def get_path(self, path, use_cwd=False, abs_path=False): 102*819667bcSMauro Carvalho Chehab """ 103*819667bcSMauro Carvalho Chehab Ancillary routine to handle patches the right way, as shell does. 104*819667bcSMauro Carvalho Chehab 105*819667bcSMauro Carvalho Chehab It first expands "~" and "~user". Then, if patch is not absolute, 106*819667bcSMauro Carvalho Chehab join self.srctree. Finally, if requested, convert to abspath. 107*819667bcSMauro Carvalho Chehab """ 108*819667bcSMauro Carvalho Chehab 109*819667bcSMauro Carvalho Chehab path = os.path.expanduser(path) 110*819667bcSMauro Carvalho Chehab if not path.startswith("/"): 111*819667bcSMauro Carvalho Chehab if use_cwd: 112*819667bcSMauro Carvalho Chehab base = os.getcwd() 113*819667bcSMauro Carvalho Chehab else: 114*819667bcSMauro Carvalho Chehab base = self.srctree 115*819667bcSMauro Carvalho Chehab 116*819667bcSMauro Carvalho Chehab path = os.path.join(base, path) 117*819667bcSMauro Carvalho Chehab 118*819667bcSMauro Carvalho Chehab if abs_path: 119*819667bcSMauro Carvalho Chehab return os.path.abspath(path) 120*819667bcSMauro Carvalho Chehab 121*819667bcSMauro Carvalho Chehab return path 122*819667bcSMauro Carvalho Chehab 123*819667bcSMauro Carvalho Chehab def get_sphinx_extra_opts(self, n_jobs): 124*819667bcSMauro Carvalho Chehab """ 125*819667bcSMauro Carvalho Chehab Get the number of jobs to be used for docs build passed via command 126*819667bcSMauro Carvalho Chehab line and desired sphinx verbosity. 127*819667bcSMauro Carvalho Chehab 128*819667bcSMauro Carvalho Chehab The number of jobs can be on different places: 129*819667bcSMauro Carvalho Chehab 130*819667bcSMauro Carvalho Chehab 1) It can be passed via "-j" argument; 131*819667bcSMauro Carvalho Chehab 2) The SPHINXOPTS="-j8" env var may have "-j"; 132*819667bcSMauro Carvalho Chehab 3) if called via GNU make, -j specifies the desired number of jobs. 133*819667bcSMauro Carvalho Chehab with GNU makefile, this number is available via POSIX jobserver; 134*819667bcSMauro Carvalho Chehab 4) if none of the above is available, it should default to "-jauto", 135*819667bcSMauro Carvalho Chehab and let sphinx decide the best value. 136*819667bcSMauro Carvalho Chehab """ 137*819667bcSMauro Carvalho Chehab 138*819667bcSMauro Carvalho Chehab # 139*819667bcSMauro Carvalho Chehab # SPHINXOPTS env var, if used, contains extra arguments to be used 140*819667bcSMauro Carvalho Chehab # by sphinx-build time. Among them, it may contain sphinx verbosity 141*819667bcSMauro Carvalho Chehab # and desired number of parallel jobs. 142*819667bcSMauro Carvalho Chehab # 143*819667bcSMauro Carvalho Chehab parser = argparse.ArgumentParser() 144*819667bcSMauro Carvalho Chehab parser.add_argument('-j', '--jobs', type=int) 145*819667bcSMauro Carvalho Chehab parser.add_argument('-q', '--quiet', type=int) 146*819667bcSMauro Carvalho Chehab 147*819667bcSMauro Carvalho Chehab # 148*819667bcSMauro Carvalho Chehab # Other sphinx-build arguments go as-is, so place them 149*819667bcSMauro Carvalho Chehab # at self.sphinxopts, using shell parser 150*819667bcSMauro Carvalho Chehab # 151*819667bcSMauro Carvalho Chehab sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", "")) 152*819667bcSMauro Carvalho Chehab 153*819667bcSMauro Carvalho Chehab # 154*819667bcSMauro Carvalho Chehab # Build a list of sphinx args, honoring verbosity here if specified 155*819667bcSMauro Carvalho Chehab # 156*819667bcSMauro Carvalho Chehab 157*819667bcSMauro Carvalho Chehab verbose = self.verbose 158*819667bcSMauro Carvalho Chehab sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts) 159*819667bcSMauro Carvalho Chehab if sphinx_args.quiet is True: 160*819667bcSMauro Carvalho Chehab verbose = False 161*819667bcSMauro Carvalho Chehab 162*819667bcSMauro Carvalho Chehab # 163*819667bcSMauro Carvalho Chehab # If the user explicitly sets "-j" at command line, use it. 164*819667bcSMauro Carvalho Chehab # Otherwise, pick it from SPHINXOPTS args 165*819667bcSMauro Carvalho Chehab # 166*819667bcSMauro Carvalho Chehab if n_jobs: 167*819667bcSMauro Carvalho Chehab self.n_jobs = n_jobs 168*819667bcSMauro Carvalho Chehab elif sphinx_args.jobs: 169*819667bcSMauro Carvalho Chehab self.n_jobs = sphinx_args.jobs 170*819667bcSMauro Carvalho Chehab else: 171*819667bcSMauro Carvalho Chehab self.n_jobs = None 172*819667bcSMauro Carvalho Chehab 173*819667bcSMauro Carvalho Chehab if not verbose: 174*819667bcSMauro Carvalho Chehab self.sphinxopts += ["-q"] 175*819667bcSMauro Carvalho Chehab 176*819667bcSMauro Carvalho Chehab def __init__(self, builddir, verbose=False, n_jobs=None): 177*819667bcSMauro Carvalho Chehab """Initialize internal variables""" 178*819667bcSMauro Carvalho Chehab self.verbose = None 179*819667bcSMauro Carvalho Chehab 180*819667bcSMauro Carvalho Chehab # 181*819667bcSMauro Carvalho Chehab # Normal variables passed from Kernel's makefile 182*819667bcSMauro Carvalho Chehab # 183*819667bcSMauro Carvalho Chehab self.kernelversion = os.environ.get("KERNELVERSION", "unknown") 184*819667bcSMauro Carvalho Chehab self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown") 185*819667bcSMauro Carvalho Chehab self.pdflatex = os.environ.get("PDFLATEX", "xelatex") 186*819667bcSMauro Carvalho Chehab self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape") 187*819667bcSMauro Carvalho Chehab 188*819667bcSMauro Carvalho Chehab if not verbose: 189*819667bcSMauro Carvalho Chehab verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "") 190*819667bcSMauro Carvalho Chehab 191*819667bcSMauro Carvalho Chehab if verbose is not None: 192*819667bcSMauro Carvalho Chehab self.verbose = verbose 193*819667bcSMauro Carvalho Chehab 194*819667bcSMauro Carvalho Chehab # 195*819667bcSMauro Carvalho Chehab # Source tree directory. This needs to be at os.environ, as 196*819667bcSMauro Carvalho Chehab # Sphinx extensions use it 197*819667bcSMauro Carvalho Chehab # 198*819667bcSMauro Carvalho Chehab self.srctree = os.environ.get("srctree") 199*819667bcSMauro Carvalho Chehab if not self.srctree: 200*819667bcSMauro Carvalho Chehab self.srctree = "." 201*819667bcSMauro Carvalho Chehab os.environ["srctree"] = self.srctree 202*819667bcSMauro Carvalho Chehab 203*819667bcSMauro Carvalho Chehab # 204*819667bcSMauro Carvalho Chehab # Now that we can expand srctree, get other directories as well 205*819667bcSMauro Carvalho Chehab # 206*819667bcSMauro Carvalho Chehab self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build") 207*819667bcSMauro Carvalho Chehab self.kerneldoc = self.get_path(os.environ.get("KERNELDOC", 208*819667bcSMauro Carvalho Chehab "scripts/kernel-doc.py")) 209*819667bcSMauro Carvalho Chehab self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True) 210*819667bcSMauro Carvalho Chehab 211*819667bcSMauro Carvalho Chehab self.config_rust = self.is_rust_enabled() 212*819667bcSMauro Carvalho Chehab 213*819667bcSMauro Carvalho Chehab # 214*819667bcSMauro Carvalho Chehab # Get directory locations for LaTeX build toolchain 215*819667bcSMauro Carvalho Chehab # 216*819667bcSMauro Carvalho Chehab self.pdflatex_cmd = shutil.which(self.pdflatex) 217*819667bcSMauro Carvalho Chehab self.latexmk_cmd = shutil.which("latexmk") 218*819667bcSMauro Carvalho Chehab 219*819667bcSMauro Carvalho Chehab self.env = os.environ.copy() 220*819667bcSMauro Carvalho Chehab 221*819667bcSMauro Carvalho Chehab self.get_sphinx_extra_opts(n_jobs) 222*819667bcSMauro Carvalho Chehab 223*819667bcSMauro Carvalho Chehab def run_sphinx(self, sphinx_build, build_args, *args, **pwargs): 224*819667bcSMauro Carvalho Chehab """ 225*819667bcSMauro Carvalho Chehab Executes sphinx-build using current python3 command. 226*819667bcSMauro Carvalho Chehab 227*819667bcSMauro Carvalho Chehab When calling via GNU make, POSIX jobserver is used to tell how 228*819667bcSMauro Carvalho Chehab many jobs are still available from a job pool. claim all remaining 229*819667bcSMauro Carvalho Chehab jobs, as we don't want sphinx-build to run in parallel with other 230*819667bcSMauro Carvalho Chehab jobs. 231*819667bcSMauro Carvalho Chehab 232*819667bcSMauro Carvalho Chehab Despite that, the user may actually force a different value than 233*819667bcSMauro Carvalho Chehab the number of available jobs via command line. 234*819667bcSMauro Carvalho Chehab 235*819667bcSMauro Carvalho Chehab The "with" logic here is used to ensure that the claimed jobs will 236*819667bcSMauro Carvalho Chehab be freed once subprocess finishes 237*819667bcSMauro Carvalho Chehab """ 238*819667bcSMauro Carvalho Chehab 239*819667bcSMauro Carvalho Chehab with JobserverExec() as jobserver: 240*819667bcSMauro Carvalho Chehab if jobserver.claim: 241*819667bcSMauro Carvalho Chehab # 242*819667bcSMauro Carvalho Chehab # when GNU make is used, claim available jobs from jobserver 243*819667bcSMauro Carvalho Chehab # 244*819667bcSMauro Carvalho Chehab n_jobs = str(jobserver.claim) 245*819667bcSMauro Carvalho Chehab else: 246*819667bcSMauro Carvalho Chehab # 247*819667bcSMauro Carvalho Chehab # Otherwise, let sphinx decide by default 248*819667bcSMauro Carvalho Chehab # 249*819667bcSMauro Carvalho Chehab n_jobs = "auto" 250*819667bcSMauro Carvalho Chehab 251*819667bcSMauro Carvalho Chehab # 252*819667bcSMauro Carvalho Chehab # If explicitly requested via command line, override default 253*819667bcSMauro Carvalho Chehab # 254*819667bcSMauro Carvalho Chehab if self.n_jobs: 255*819667bcSMauro Carvalho Chehab n_jobs = str(self.n_jobs) 256*819667bcSMauro Carvalho Chehab 257*819667bcSMauro Carvalho Chehab cmd = [sys.executable, sphinx_build] 258*819667bcSMauro Carvalho Chehab cmd += [f"-j{n_jobs}"] 259*819667bcSMauro Carvalho Chehab cmd += self.sphinxopts 260*819667bcSMauro Carvalho Chehab cmd += build_args 261*819667bcSMauro Carvalho Chehab 262*819667bcSMauro Carvalho Chehab if self.verbose: 263*819667bcSMauro Carvalho Chehab print(" ".join(cmd)) 264*819667bcSMauro Carvalho Chehab 265*819667bcSMauro Carvalho Chehab return subprocess.call(cmd, *args, **pwargs) 266*819667bcSMauro Carvalho Chehab 267*819667bcSMauro Carvalho Chehab def handle_html(self, css, output_dir): 268*819667bcSMauro Carvalho Chehab """ 269*819667bcSMauro Carvalho Chehab Extra steps for HTML and epub output. 270*819667bcSMauro Carvalho Chehab 271*819667bcSMauro Carvalho Chehab For such targets, we need to ensure that CSS will be properly 272*819667bcSMauro Carvalho Chehab copied to the output _static directory 273*819667bcSMauro Carvalho Chehab """ 274*819667bcSMauro Carvalho Chehab 275*819667bcSMauro Carvalho Chehab if not css: 276*819667bcSMauro Carvalho Chehab return 277*819667bcSMauro Carvalho Chehab 278*819667bcSMauro Carvalho Chehab css = os.path.expanduser(css) 279*819667bcSMauro Carvalho Chehab if not css.startswith("/"): 280*819667bcSMauro Carvalho Chehab css = os.path.join(self.srctree, css) 281*819667bcSMauro Carvalho Chehab 282*819667bcSMauro Carvalho Chehab static_dir = os.path.join(output_dir, "_static") 283*819667bcSMauro Carvalho Chehab os.makedirs(static_dir, exist_ok=True) 284*819667bcSMauro Carvalho Chehab 285*819667bcSMauro Carvalho Chehab try: 286*819667bcSMauro Carvalho Chehab shutil.copy2(css, static_dir) 287*819667bcSMauro Carvalho Chehab except (OSError, IOError) as e: 288*819667bcSMauro Carvalho Chehab print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr) 289*819667bcSMauro Carvalho Chehab 290*819667bcSMauro Carvalho Chehab def handle_pdf(self, output_dirs, deny_vf): 291*819667bcSMauro Carvalho Chehab """ 292*819667bcSMauro Carvalho Chehab Extra steps for PDF output. 293*819667bcSMauro Carvalho Chehab 294*819667bcSMauro Carvalho Chehab As PDF is handled via a LaTeX output, after building the .tex file, 295*819667bcSMauro Carvalho Chehab a new build is needed to create the PDF output from the latex 296*819667bcSMauro Carvalho Chehab directory. 297*819667bcSMauro Carvalho Chehab """ 298*819667bcSMauro Carvalho Chehab builds = {} 299*819667bcSMauro Carvalho Chehab max_len = 0 300*819667bcSMauro Carvalho Chehab 301*819667bcSMauro Carvalho Chehab # 302*819667bcSMauro Carvalho Chehab # Since early 2024, Fedora and openSUSE tumbleweed have started 303*819667bcSMauro Carvalho Chehab # deploying variable-font format of "Noto CJK", causing LaTeX 304*819667bcSMauro Carvalho Chehab # to break with CJK. Work around it, by denying the variable font 305*819667bcSMauro Carvalho Chehab # usage during xelatex build by passing the location of a config 306*819667bcSMauro Carvalho Chehab # file with a deny list. 307*819667bcSMauro Carvalho Chehab # 308*819667bcSMauro Carvalho Chehab # See tools/docs/lib/latex_fonts.py for more details. 309*819667bcSMauro Carvalho Chehab # 310*819667bcSMauro Carvalho Chehab if deny_vf: 311*819667bcSMauro Carvalho Chehab deny_vf = os.path.expanduser(deny_vf) 312*819667bcSMauro Carvalho Chehab if os.path.isdir(deny_vf): 313*819667bcSMauro Carvalho Chehab self.env["XDG_CONFIG_HOME"] = deny_vf 314*819667bcSMauro Carvalho Chehab 315*819667bcSMauro Carvalho Chehab for from_dir in output_dirs: 316*819667bcSMauro Carvalho Chehab pdf_dir = os.path.join(from_dir, "../pdf") 317*819667bcSMauro Carvalho Chehab os.makedirs(pdf_dir, exist_ok=True) 318*819667bcSMauro Carvalho Chehab 319*819667bcSMauro Carvalho Chehab if self.latexmk_cmd: 320*819667bcSMauro Carvalho Chehab latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"] 321*819667bcSMauro Carvalho Chehab else: 322*819667bcSMauro Carvalho Chehab latex_cmd = [self.pdflatex] 323*819667bcSMauro Carvalho Chehab 324*819667bcSMauro Carvalho Chehab latex_cmd.extend(shlex.split(self.latexopts)) 325*819667bcSMauro Carvalho Chehab 326*819667bcSMauro Carvalho Chehab tex_suffix = ".tex" 327*819667bcSMauro Carvalho Chehab 328*819667bcSMauro Carvalho Chehab # 329*819667bcSMauro Carvalho Chehab # Process each .tex file 330*819667bcSMauro Carvalho Chehab # 331*819667bcSMauro Carvalho Chehab 332*819667bcSMauro Carvalho Chehab has_tex = False 333*819667bcSMauro Carvalho Chehab build_failed = False 334*819667bcSMauro Carvalho Chehab with os.scandir(from_dir) as it: 335*819667bcSMauro Carvalho Chehab for entry in it: 336*819667bcSMauro Carvalho Chehab if not entry.name.endswith(tex_suffix): 337*819667bcSMauro Carvalho Chehab continue 338*819667bcSMauro Carvalho Chehab 339*819667bcSMauro Carvalho Chehab name = entry.name[:-len(tex_suffix)] 340*819667bcSMauro Carvalho Chehab has_tex = True 341*819667bcSMauro Carvalho Chehab 342*819667bcSMauro Carvalho Chehab # 343*819667bcSMauro Carvalho Chehab # LaTeX PDF error code is almost useless for us: 344*819667bcSMauro Carvalho Chehab # any warning makes it non-zero. For kernel doc builds it 345*819667bcSMauro Carvalho Chehab # always return non-zero even when build succeeds. 346*819667bcSMauro Carvalho Chehab # So, let's do the best next thing: check if all PDF 347*819667bcSMauro Carvalho Chehab # files were built. If they're, print a summary and 348*819667bcSMauro Carvalho Chehab # return 0 at the end of this function 349*819667bcSMauro Carvalho Chehab # 350*819667bcSMauro Carvalho Chehab try: 351*819667bcSMauro Carvalho Chehab subprocess.run(latex_cmd + [entry.path], 352*819667bcSMauro Carvalho Chehab cwd=from_dir, check=True, env=self.env) 353*819667bcSMauro Carvalho Chehab except subprocess.CalledProcessError: 354*819667bcSMauro Carvalho Chehab pass 355*819667bcSMauro Carvalho Chehab 356*819667bcSMauro Carvalho Chehab pdf_name = name + ".pdf" 357*819667bcSMauro Carvalho Chehab pdf_from = os.path.join(from_dir, pdf_name) 358*819667bcSMauro Carvalho Chehab pdf_to = os.path.join(pdf_dir, pdf_name) 359*819667bcSMauro Carvalho Chehab 360*819667bcSMauro Carvalho Chehab if os.path.exists(pdf_from): 361*819667bcSMauro Carvalho Chehab os.rename(pdf_from, pdf_to) 362*819667bcSMauro Carvalho Chehab builds[name] = os.path.relpath(pdf_to, self.builddir) 363*819667bcSMauro Carvalho Chehab else: 364*819667bcSMauro Carvalho Chehab builds[name] = "FAILED" 365*819667bcSMauro Carvalho Chehab build_failed = True 366*819667bcSMauro Carvalho Chehab 367*819667bcSMauro Carvalho Chehab name = entry.name.removesuffix(".tex") 368*819667bcSMauro Carvalho Chehab max_len = max(max_len, len(name)) 369*819667bcSMauro Carvalho Chehab 370*819667bcSMauro Carvalho Chehab if not has_tex: 371*819667bcSMauro Carvalho Chehab name = os.path.basename(from_dir) 372*819667bcSMauro Carvalho Chehab max_len = max(max_len, len(name)) 373*819667bcSMauro Carvalho Chehab builds[name] = "FAILED (no .tex)" 374*819667bcSMauro Carvalho Chehab build_failed = True 375*819667bcSMauro Carvalho Chehab 376*819667bcSMauro Carvalho Chehab msg = "Summary" 377*819667bcSMauro Carvalho Chehab msg += "\n" + "=" * len(msg) 378*819667bcSMauro Carvalho Chehab print() 379*819667bcSMauro Carvalho Chehab print(msg) 380*819667bcSMauro Carvalho Chehab 381*819667bcSMauro Carvalho Chehab for pdf_name, pdf_file in builds.items(): 382*819667bcSMauro Carvalho Chehab print(f"{pdf_name:<{max_len}}: {pdf_file}") 383*819667bcSMauro Carvalho Chehab 384*819667bcSMauro Carvalho Chehab print() 385*819667bcSMauro Carvalho Chehab 386*819667bcSMauro Carvalho Chehab if build_failed: 387*819667bcSMauro Carvalho Chehab msg = LatexFontChecker().check() 388*819667bcSMauro Carvalho Chehab if msg: 389*819667bcSMauro Carvalho Chehab print(msg) 390*819667bcSMauro Carvalho Chehab 391*819667bcSMauro Carvalho Chehab sys.exit("PDF build failed: not all PDF files were created.") 392*819667bcSMauro Carvalho Chehab else: 393*819667bcSMauro Carvalho Chehab print("All PDF files were built.") 394*819667bcSMauro Carvalho Chehab 395*819667bcSMauro Carvalho Chehab def handle_info(self, output_dirs): 396*819667bcSMauro Carvalho Chehab """ 397*819667bcSMauro Carvalho Chehab Extra steps for Info output. 398*819667bcSMauro Carvalho Chehab 399*819667bcSMauro Carvalho Chehab For texinfo generation, an additional make is needed from the 400*819667bcSMauro Carvalho Chehab texinfo directory. 401*819667bcSMauro Carvalho Chehab """ 402*819667bcSMauro Carvalho Chehab 403*819667bcSMauro Carvalho Chehab for output_dir in output_dirs: 404*819667bcSMauro Carvalho Chehab try: 405*819667bcSMauro Carvalho Chehab subprocess.run(["make", "info"], cwd=output_dir, check=True) 406*819667bcSMauro Carvalho Chehab except subprocess.CalledProcessError as e: 407*819667bcSMauro Carvalho Chehab sys.exit(f"Error generating info docs: {e}") 408*819667bcSMauro Carvalho Chehab 409*819667bcSMauro Carvalho Chehab def cleandocs(self, builder): # pylint: disable=W0613 410*819667bcSMauro Carvalho Chehab """Remove documentation output directory""" 411*819667bcSMauro Carvalho Chehab shutil.rmtree(self.builddir, ignore_errors=True) 412*819667bcSMauro Carvalho Chehab 413*819667bcSMauro Carvalho Chehab def build(self, target, sphinxdirs=None, conf="conf.py", 414*819667bcSMauro Carvalho Chehab theme=None, css=None, paper=None, deny_vf=None): 415*819667bcSMauro Carvalho Chehab """ 416*819667bcSMauro Carvalho Chehab Build documentation using Sphinx. This is the core function of this 417*819667bcSMauro Carvalho Chehab module. It prepares all arguments required by sphinx-build. 418*819667bcSMauro Carvalho Chehab """ 419*819667bcSMauro Carvalho Chehab 420*819667bcSMauro Carvalho Chehab builder = TARGETS[target]["builder"] 421*819667bcSMauro Carvalho Chehab out_dir = TARGETS[target].get("out_dir", "") 422*819667bcSMauro Carvalho Chehab 423*819667bcSMauro Carvalho Chehab # 424*819667bcSMauro Carvalho Chehab # Cleandocs doesn't require sphinx-build 425*819667bcSMauro Carvalho Chehab # 426*819667bcSMauro Carvalho Chehab if target == "cleandocs": 427*819667bcSMauro Carvalho Chehab self.cleandocs(builder) 428*819667bcSMauro Carvalho Chehab return 429*819667bcSMauro Carvalho Chehab 430*819667bcSMauro Carvalho Chehab if theme: 431*819667bcSMauro Carvalho Chehab os.environ["DOCS_THEME"] = theme 432*819667bcSMauro Carvalho Chehab 433*819667bcSMauro Carvalho Chehab # 434*819667bcSMauro Carvalho Chehab # Other targets require sphinx-build, so check if it exists 435*819667bcSMauro Carvalho Chehab # 436*819667bcSMauro Carvalho Chehab sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"]) 437*819667bcSMauro Carvalho Chehab if not sphinxbuild: 438*819667bcSMauro Carvalho Chehab sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n") 439*819667bcSMauro Carvalho Chehab 440*819667bcSMauro Carvalho Chehab if builder == "latex": 441*819667bcSMauro Carvalho Chehab if not self.pdflatex_cmd and not self.latexmk_cmd: 442*819667bcSMauro Carvalho Chehab sys.exit("Error: pdflatex or latexmk required for PDF generation") 443*819667bcSMauro Carvalho Chehab 444*819667bcSMauro Carvalho Chehab docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation")) 445*819667bcSMauro Carvalho Chehab 446*819667bcSMauro Carvalho Chehab # 447*819667bcSMauro Carvalho Chehab # Fill in base arguments for Sphinx build 448*819667bcSMauro Carvalho Chehab # 449*819667bcSMauro Carvalho Chehab kerneldoc = self.kerneldoc 450*819667bcSMauro Carvalho Chehab if kerneldoc.startswith(self.srctree): 451*819667bcSMauro Carvalho Chehab kerneldoc = os.path.relpath(kerneldoc, self.srctree) 452*819667bcSMauro Carvalho Chehab 453*819667bcSMauro Carvalho Chehab args = [ "-b", builder, "-c", docs_dir ] 454*819667bcSMauro Carvalho Chehab 455*819667bcSMauro Carvalho Chehab if builder == "latex": 456*819667bcSMauro Carvalho Chehab if not paper: 457*819667bcSMauro Carvalho Chehab paper = PAPER[1] 458*819667bcSMauro Carvalho Chehab 459*819667bcSMauro Carvalho Chehab args.extend(["-D", f"latex_elements.papersize={paper}paper"]) 460*819667bcSMauro Carvalho Chehab 461*819667bcSMauro Carvalho Chehab if self.config_rust: 462*819667bcSMauro Carvalho Chehab args.extend(["-t", "rustdoc"]) 463*819667bcSMauro Carvalho Chehab 464*819667bcSMauro Carvalho Chehab if conf: 465*819667bcSMauro Carvalho Chehab self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True) 466*819667bcSMauro Carvalho Chehab 467*819667bcSMauro Carvalho Chehab if not sphinxdirs: 468*819667bcSMauro Carvalho Chehab sphinxdirs = os.environ.get("SPHINXDIRS", ".") 469*819667bcSMauro Carvalho Chehab 470*819667bcSMauro Carvalho Chehab # 471*819667bcSMauro Carvalho Chehab # sphinxdirs can be a list or a whitespace-separated string 472*819667bcSMauro Carvalho Chehab # 473*819667bcSMauro Carvalho Chehab sphinxdirs_list = [] 474*819667bcSMauro Carvalho Chehab for sphinxdir in sphinxdirs: 475*819667bcSMauro Carvalho Chehab if isinstance(sphinxdir, list): 476*819667bcSMauro Carvalho Chehab sphinxdirs_list += sphinxdir 477*819667bcSMauro Carvalho Chehab else: 478*819667bcSMauro Carvalho Chehab sphinxdirs_list += sphinxdir.split() 479*819667bcSMauro Carvalho Chehab 480*819667bcSMauro Carvalho Chehab # 481*819667bcSMauro Carvalho Chehab # Step 1: Build each directory in separate. 482*819667bcSMauro Carvalho Chehab # 483*819667bcSMauro Carvalho Chehab # This is not the best way of handling it, as cross-references between 484*819667bcSMauro Carvalho Chehab # them will be broken, but this is what we've been doing since 485*819667bcSMauro Carvalho Chehab # the beginning. 486*819667bcSMauro Carvalho Chehab # 487*819667bcSMauro Carvalho Chehab output_dirs = [] 488*819667bcSMauro Carvalho Chehab for sphinxdir in sphinxdirs_list: 489*819667bcSMauro Carvalho Chehab src_dir = os.path.join(docs_dir, sphinxdir) 490*819667bcSMauro Carvalho Chehab doctree_dir = os.path.join(self.builddir, ".doctrees") 491*819667bcSMauro Carvalho Chehab output_dir = os.path.join(self.builddir, sphinxdir, out_dir) 492*819667bcSMauro Carvalho Chehab 493*819667bcSMauro Carvalho Chehab # 494*819667bcSMauro Carvalho Chehab # Make directory names canonical 495*819667bcSMauro Carvalho Chehab # 496*819667bcSMauro Carvalho Chehab src_dir = os.path.normpath(src_dir) 497*819667bcSMauro Carvalho Chehab doctree_dir = os.path.normpath(doctree_dir) 498*819667bcSMauro Carvalho Chehab output_dir = os.path.normpath(output_dir) 499*819667bcSMauro Carvalho Chehab 500*819667bcSMauro Carvalho Chehab os.makedirs(doctree_dir, exist_ok=True) 501*819667bcSMauro Carvalho Chehab os.makedirs(output_dir, exist_ok=True) 502*819667bcSMauro Carvalho Chehab 503*819667bcSMauro Carvalho Chehab output_dirs.append(output_dir) 504*819667bcSMauro Carvalho Chehab 505*819667bcSMauro Carvalho Chehab build_args = args + [ 506*819667bcSMauro Carvalho Chehab "-d", doctree_dir, 507*819667bcSMauro Carvalho Chehab "-D", f"kerneldoc_bin={kerneldoc}", 508*819667bcSMauro Carvalho Chehab "-D", f"version={self.kernelversion}", 509*819667bcSMauro Carvalho Chehab "-D", f"release={self.kernelrelease}", 510*819667bcSMauro Carvalho Chehab "-D", f"kerneldoc_srctree={self.srctree}", 511*819667bcSMauro Carvalho Chehab src_dir, 512*819667bcSMauro Carvalho Chehab output_dir, 513*819667bcSMauro Carvalho Chehab ] 514*819667bcSMauro Carvalho Chehab 515*819667bcSMauro Carvalho Chehab try: 516*819667bcSMauro Carvalho Chehab self.run_sphinx(sphinxbuild, build_args, env=self.env) 517*819667bcSMauro Carvalho Chehab except (OSError, ValueError, subprocess.SubprocessError) as e: 518*819667bcSMauro Carvalho Chehab sys.exit(f"Build failed: {repr(e)}") 519*819667bcSMauro Carvalho Chehab 520*819667bcSMauro Carvalho Chehab # 521*819667bcSMauro Carvalho Chehab # Ensure that each html/epub output will have needed static files 522*819667bcSMauro Carvalho Chehab # 523*819667bcSMauro Carvalho Chehab if target in ["htmldocs", "epubdocs"]: 524*819667bcSMauro Carvalho Chehab self.handle_html(css, output_dir) 525*819667bcSMauro Carvalho Chehab 526*819667bcSMauro Carvalho Chehab # 527*819667bcSMauro Carvalho Chehab # Step 2: Some targets (PDF and info) require an extra step once 528*819667bcSMauro Carvalho Chehab # sphinx-build finishes 529*819667bcSMauro Carvalho Chehab # 530*819667bcSMauro Carvalho Chehab if target == "pdfdocs": 531*819667bcSMauro Carvalho Chehab self.handle_pdf(output_dirs, deny_vf) 532*819667bcSMauro Carvalho Chehab elif target == "infodocs": 533*819667bcSMauro Carvalho Chehab self.handle_info(output_dirs) 534*819667bcSMauro Carvalho Chehab 535*819667bcSMauro Carvalho Chehabdef jobs_type(value): 536*819667bcSMauro Carvalho Chehab """ 537*819667bcSMauro Carvalho Chehab Handle valid values for -j. Accepts Sphinx "-jauto", plus a number 538*819667bcSMauro Carvalho Chehab equal or bigger than one. 539*819667bcSMauro Carvalho Chehab """ 540*819667bcSMauro Carvalho Chehab if value is None: 541*819667bcSMauro Carvalho Chehab return None 542*819667bcSMauro Carvalho Chehab 543*819667bcSMauro Carvalho Chehab if value.lower() == 'auto': 544*819667bcSMauro Carvalho Chehab return value.lower() 545*819667bcSMauro Carvalho Chehab 546*819667bcSMauro Carvalho Chehab try: 547*819667bcSMauro Carvalho Chehab if int(value) >= 1: 548*819667bcSMauro Carvalho Chehab return value 549*819667bcSMauro Carvalho Chehab 550*819667bcSMauro Carvalho Chehab raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}") 551*819667bcSMauro Carvalho Chehab except ValueError: 552*819667bcSMauro Carvalho Chehab raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}") # pylint: disable=W0707 553*819667bcSMauro Carvalho Chehab 554*819667bcSMauro Carvalho Chehabdef main(): 555*819667bcSMauro Carvalho Chehab """ 556*819667bcSMauro Carvalho Chehab Main function. The only mandatory argument is the target. If not 557*819667bcSMauro Carvalho Chehab specified, the other arguments will use default values if not 558*819667bcSMauro Carvalho Chehab specified at os.environ. 559*819667bcSMauro Carvalho Chehab """ 560*819667bcSMauro Carvalho Chehab parser = argparse.ArgumentParser(description="Kernel documentation builder") 561*819667bcSMauro Carvalho Chehab 562*819667bcSMauro Carvalho Chehab parser.add_argument("target", choices=list(TARGETS.keys()), 563*819667bcSMauro Carvalho Chehab help="Documentation target to build") 564*819667bcSMauro Carvalho Chehab parser.add_argument("--sphinxdirs", nargs="+", 565*819667bcSMauro Carvalho Chehab help="Specific directories to build") 566*819667bcSMauro Carvalho Chehab parser.add_argument("--conf", default="conf.py", 567*819667bcSMauro Carvalho Chehab help="Sphinx configuration file") 568*819667bcSMauro Carvalho Chehab parser.add_argument("--builddir", default="output", 569*819667bcSMauro Carvalho Chehab help="Sphinx configuration file") 570*819667bcSMauro Carvalho Chehab 571*819667bcSMauro Carvalho Chehab parser.add_argument("--theme", help="Sphinx theme to use") 572*819667bcSMauro Carvalho Chehab 573*819667bcSMauro Carvalho Chehab parser.add_argument("--css", help="Custom CSS file for HTML/EPUB") 574*819667bcSMauro Carvalho Chehab 575*819667bcSMauro Carvalho Chehab parser.add_argument("--paper", choices=PAPER, default=PAPER[0], 576*819667bcSMauro Carvalho Chehab help="Paper size for LaTeX/PDF output") 577*819667bcSMauro Carvalho Chehab 578*819667bcSMauro Carvalho Chehab parser.add_argument('--deny-vf', 579*819667bcSMauro Carvalho Chehab help="Configuration to deny variable fonts on pdf builds") 580*819667bcSMauro Carvalho Chehab 581*819667bcSMauro Carvalho Chehab parser.add_argument("-v", "--verbose", action='store_true', 582*819667bcSMauro Carvalho Chehab help="place build in verbose mode") 583*819667bcSMauro Carvalho Chehab 584*819667bcSMauro Carvalho Chehab parser.add_argument('-j', '--jobs', type=jobs_type, 585*819667bcSMauro Carvalho Chehab help="Sets number of jobs to use with sphinx-build") 586*819667bcSMauro Carvalho Chehab 587*819667bcSMauro Carvalho Chehab args = parser.parse_args() 588*819667bcSMauro Carvalho Chehab 589*819667bcSMauro Carvalho Chehab PythonVersion.check_python(MIN_PYTHON_VERSION) 590*819667bcSMauro Carvalho Chehab 591*819667bcSMauro Carvalho Chehab builder = SphinxBuilder(builddir=args.builddir, 592*819667bcSMauro Carvalho Chehab verbose=args.verbose, n_jobs=args.jobs) 593*819667bcSMauro Carvalho Chehab 594*819667bcSMauro Carvalho Chehab builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf, 595*819667bcSMauro Carvalho Chehab theme=args.theme, css=args.css, paper=args.paper, 596*819667bcSMauro Carvalho Chehab deny_vf=args.deny_vf) 597*819667bcSMauro Carvalho Chehab 598*819667bcSMauro Carvalho Chehabif __name__ == "__main__": 599*819667bcSMauro Carvalho Chehab main() 600