xref: /linux/tools/docs/sphinx-build-wrapper (revision 2f99b85e22b918b9659d072d6cc8baf51b4922f3)
1819667bcSMauro Carvalho Chehab#!/usr/bin/env python3
2819667bcSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
3819667bcSMauro Carvalho Chehab# Copyright (C) 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
4819667bcSMauro Carvalho Chehab#
5819667bcSMauro Carvalho Chehab# pylint: disable=R0902, R0912, R0913, R0914, R0915, R0917, C0103
6819667bcSMauro Carvalho Chehab#
7819667bcSMauro Carvalho Chehab# Converted from docs Makefile and parallel-wrapper.sh, both under
8819667bcSMauro Carvalho Chehab# GPLv2, copyrighted since 2008 by the following authors:
9819667bcSMauro Carvalho Chehab#
10819667bcSMauro Carvalho Chehab#    Akira Yokosawa <akiyks@gmail.com>
11819667bcSMauro Carvalho Chehab#    Arnd Bergmann <arnd@arndb.de>
12819667bcSMauro Carvalho Chehab#    Breno Leitao <leitao@debian.org>
13819667bcSMauro Carvalho Chehab#    Carlos Bilbao <carlos.bilbao@amd.com>
14819667bcSMauro Carvalho Chehab#    Dave Young <dyoung@redhat.com>
15819667bcSMauro Carvalho Chehab#    Donald Hunter <donald.hunter@gmail.com>
16819667bcSMauro Carvalho Chehab#    Geert Uytterhoeven <geert+renesas@glider.be>
17819667bcSMauro Carvalho Chehab#    Jani Nikula <jani.nikula@intel.com>
18819667bcSMauro Carvalho Chehab#    Jan Stancek <jstancek@redhat.com>
19819667bcSMauro Carvalho Chehab#    Jonathan Corbet <corbet@lwn.net>
20819667bcSMauro Carvalho Chehab#    Joshua Clayton <stillcompiling@gmail.com>
21819667bcSMauro Carvalho Chehab#    Kees Cook <keescook@chromium.org>
22819667bcSMauro Carvalho Chehab#    Linus Torvalds <torvalds@linux-foundation.org>
23819667bcSMauro Carvalho Chehab#    Magnus Damm <damm+renesas@opensource.se>
24819667bcSMauro Carvalho Chehab#    Masahiro Yamada <masahiroy@kernel.org>
25819667bcSMauro Carvalho Chehab#    Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
26819667bcSMauro Carvalho Chehab#    Maxim Cournoyer <maxim.cournoyer@gmail.com>
27819667bcSMauro Carvalho Chehab#    Peter Foley <pefoley2@pefoley.com>
28819667bcSMauro Carvalho Chehab#    Randy Dunlap <rdunlap@infradead.org>
29819667bcSMauro Carvalho Chehab#    Rob Herring <robh@kernel.org>
30819667bcSMauro Carvalho Chehab#    Shuah Khan <shuahkh@osg.samsung.com>
31819667bcSMauro Carvalho Chehab#    Thorsten Blum <thorsten.blum@toblux.com>
32819667bcSMauro Carvalho Chehab#    Tomas Winkler <tomas.winkler@intel.com>
33819667bcSMauro Carvalho Chehab
34819667bcSMauro Carvalho Chehab
35819667bcSMauro Carvalho Chehab"""
36819667bcSMauro Carvalho ChehabSphinx build wrapper that handles Kernel-specific business rules:
37819667bcSMauro Carvalho Chehab
38819667bcSMauro Carvalho Chehab- it gets the Kernel build environment vars;
39819667bcSMauro Carvalho Chehab- it determines what's the best parallelism;
40819667bcSMauro Carvalho Chehab- it handles SPHINXDIRS
41819667bcSMauro Carvalho Chehab
42819667bcSMauro Carvalho ChehabThis tool ensures that MIN_PYTHON_VERSION is satisfied. If version is
43819667bcSMauro Carvalho Chehabbelow that, it seeks for a new Python version. If found, it re-runs using
44819667bcSMauro Carvalho Chehabthe newer version.
45819667bcSMauro Carvalho Chehab"""
46819667bcSMauro Carvalho Chehab
47819667bcSMauro Carvalho Chehabimport argparse
48819667bcSMauro Carvalho Chehabimport os
49819667bcSMauro Carvalho Chehabimport shlex
50819667bcSMauro Carvalho Chehabimport shutil
51819667bcSMauro Carvalho Chehabimport subprocess
52819667bcSMauro Carvalho Chehabimport sys
53819667bcSMauro Carvalho Chehab
54819667bcSMauro Carvalho Chehabfrom lib.python_version import PythonVersion
55819667bcSMauro Carvalho Chehabfrom lib.latex_fonts import LatexFontChecker
56819667bcSMauro Carvalho Chehab
57819667bcSMauro Carvalho ChehabLIB_DIR = "../../scripts/lib"
58819667bcSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__))
59819667bcSMauro Carvalho Chehab
60819667bcSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
61819667bcSMauro Carvalho Chehab
62819667bcSMauro Carvalho Chehabfrom jobserver import JobserverExec         # pylint: disable=C0413,C0411,E0401
63819667bcSMauro Carvalho Chehab
64819667bcSMauro Carvalho Chehab#
65819667bcSMauro Carvalho Chehab#  Some constants
66819667bcSMauro Carvalho Chehab#
67819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version
68819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"]
69819667bcSMauro Carvalho Chehab
70819667bcSMauro Carvalho ChehabTARGETS = {
71819667bcSMauro Carvalho Chehab    "cleandocs":     { "builder": "clean" },
72819667bcSMauro Carvalho Chehab    "linkcheckdocs": { "builder": "linkcheck" },
73819667bcSMauro Carvalho Chehab    "htmldocs":      { "builder": "html" },
74819667bcSMauro Carvalho Chehab    "epubdocs":      { "builder": "epub",    "out_dir": "epub" },
75819667bcSMauro Carvalho Chehab    "texinfodocs":   { "builder": "texinfo", "out_dir": "texinfo" },
76819667bcSMauro Carvalho Chehab    "infodocs":      { "builder": "texinfo", "out_dir": "texinfo" },
77819667bcSMauro Carvalho Chehab    "latexdocs":     { "builder": "latex",   "out_dir": "latex" },
78819667bcSMauro Carvalho Chehab    "pdfdocs":       { "builder": "latex",   "out_dir": "latex" },
79819667bcSMauro Carvalho Chehab    "xmldocs":       { "builder": "xml",     "out_dir": "xml" },
80819667bcSMauro Carvalho Chehab}
81819667bcSMauro Carvalho Chehab
82819667bcSMauro Carvalho Chehab
83819667bcSMauro Carvalho Chehab#
84819667bcSMauro Carvalho Chehab# SphinxBuilder class
85819667bcSMauro Carvalho Chehab#
86819667bcSMauro Carvalho Chehab
87819667bcSMauro Carvalho Chehabclass SphinxBuilder:
88819667bcSMauro Carvalho Chehab    """
89819667bcSMauro Carvalho Chehab    Handles a sphinx-build target, adding needed arguments to build
90819667bcSMauro Carvalho Chehab    with the Kernel.
91819667bcSMauro Carvalho Chehab    """
92819667bcSMauro Carvalho Chehab
93819667bcSMauro Carvalho Chehab    def is_rust_enabled(self):
94819667bcSMauro Carvalho Chehab        """Check if rust is enabled at .config"""
95819667bcSMauro Carvalho Chehab        config_path = os.path.join(self.srctree, ".config")
96819667bcSMauro Carvalho Chehab        if os.path.isfile(config_path):
97819667bcSMauro Carvalho Chehab            with open(config_path, "r", encoding="utf-8") as f:
98819667bcSMauro Carvalho Chehab                return "CONFIG_RUST=y" in f.read()
99819667bcSMauro Carvalho Chehab        return False
100819667bcSMauro Carvalho Chehab
101819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
102819667bcSMauro Carvalho Chehab        """
103819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
104819667bcSMauro Carvalho Chehab
105819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
106819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
107819667bcSMauro Carvalho Chehab        """
108819667bcSMauro Carvalho Chehab
109819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
110819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
111819667bcSMauro Carvalho Chehab            if use_cwd:
112819667bcSMauro Carvalho Chehab                base = os.getcwd()
113819667bcSMauro Carvalho Chehab            else:
114819667bcSMauro Carvalho Chehab                base = self.srctree
115819667bcSMauro Carvalho Chehab
116819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
117819667bcSMauro Carvalho Chehab
118819667bcSMauro Carvalho Chehab        if abs_path:
119819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
120819667bcSMauro Carvalho Chehab
121819667bcSMauro Carvalho Chehab        return path
122819667bcSMauro Carvalho Chehab
123819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
124819667bcSMauro Carvalho Chehab        """
125819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
126819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
127819667bcSMauro Carvalho Chehab
128819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
129819667bcSMauro Carvalho Chehab
130819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
131819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
132819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
133819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
134819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
135819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
136819667bcSMauro Carvalho Chehab        """
137819667bcSMauro Carvalho Chehab
138819667bcSMauro Carvalho Chehab        #
139819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
140819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
141819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
142819667bcSMauro Carvalho Chehab        #
143819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
144819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
145819667bcSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', type=int)
146819667bcSMauro Carvalho Chehab
147819667bcSMauro Carvalho Chehab        #
148819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
149819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
150819667bcSMauro Carvalho Chehab        #
151819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
152819667bcSMauro Carvalho Chehab
153819667bcSMauro Carvalho Chehab        #
154819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
155819667bcSMauro Carvalho Chehab        #
156819667bcSMauro Carvalho Chehab
157819667bcSMauro Carvalho Chehab        verbose = self.verbose
158819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
159819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
160819667bcSMauro Carvalho Chehab            verbose = False
161819667bcSMauro Carvalho Chehab
162819667bcSMauro Carvalho Chehab        #
163819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
164819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
165819667bcSMauro Carvalho Chehab        #
166819667bcSMauro Carvalho Chehab        if n_jobs:
167819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
168819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
169819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
170819667bcSMauro Carvalho Chehab        else:
171819667bcSMauro Carvalho Chehab            self.n_jobs = None
172819667bcSMauro Carvalho Chehab
173819667bcSMauro Carvalho Chehab        if not verbose:
174819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
175819667bcSMauro Carvalho Chehab
176*2f99b85eSMauro Carvalho Chehab    def __init__(self, builddir, verbose=False, n_jobs=None, interactive=None):
177819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
178819667bcSMauro Carvalho Chehab        self.verbose = None
179819667bcSMauro Carvalho Chehab
180819667bcSMauro Carvalho Chehab        #
181819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
182819667bcSMauro Carvalho Chehab        #
183819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
184819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
185819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
186*2f99b85eSMauro Carvalho Chehab
187*2f99b85eSMauro Carvalho Chehab        if not interactive:
188819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
189*2f99b85eSMauro Carvalho Chehab        else:
190*2f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
191819667bcSMauro Carvalho Chehab
192819667bcSMauro Carvalho Chehab        if not verbose:
193819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
194819667bcSMauro Carvalho Chehab
195819667bcSMauro Carvalho Chehab        if verbose is not None:
196819667bcSMauro Carvalho Chehab            self.verbose = verbose
197819667bcSMauro Carvalho Chehab
198819667bcSMauro Carvalho Chehab        #
199819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
200819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
201819667bcSMauro Carvalho Chehab        #
202819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
203819667bcSMauro Carvalho Chehab        if not self.srctree:
204819667bcSMauro Carvalho Chehab            self.srctree = "."
205819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
206819667bcSMauro Carvalho Chehab
207819667bcSMauro Carvalho Chehab        #
208819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
209819667bcSMauro Carvalho Chehab        #
210819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
211819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
212819667bcSMauro Carvalho Chehab                                                      "scripts/kernel-doc.py"))
213819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
214819667bcSMauro Carvalho Chehab
215819667bcSMauro Carvalho Chehab        self.config_rust = self.is_rust_enabled()
216819667bcSMauro Carvalho Chehab
217819667bcSMauro Carvalho Chehab        #
218819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
219819667bcSMauro Carvalho Chehab        #
220819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
221819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
222819667bcSMauro Carvalho Chehab
223819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
224819667bcSMauro Carvalho Chehab
225819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
226819667bcSMauro Carvalho Chehab
227819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
228819667bcSMauro Carvalho Chehab        """
229819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
230819667bcSMauro Carvalho Chehab
231819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
232819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
233819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
234819667bcSMauro Carvalho Chehab        jobs.
235819667bcSMauro Carvalho Chehab
236819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
237819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
238819667bcSMauro Carvalho Chehab
239819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
240819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
241819667bcSMauro Carvalho Chehab        """
242819667bcSMauro Carvalho Chehab
243819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
244819667bcSMauro Carvalho Chehab            if jobserver.claim:
245819667bcSMauro Carvalho Chehab                #
246819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
247819667bcSMauro Carvalho Chehab                #
248819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
249819667bcSMauro Carvalho Chehab            else:
250819667bcSMauro Carvalho Chehab                #
251819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
252819667bcSMauro Carvalho Chehab                #
253819667bcSMauro Carvalho Chehab                n_jobs = "auto"
254819667bcSMauro Carvalho Chehab
255819667bcSMauro Carvalho Chehab            #
256819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
257819667bcSMauro Carvalho Chehab            #
258819667bcSMauro Carvalho Chehab            if self.n_jobs:
259819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
260819667bcSMauro Carvalho Chehab
261819667bcSMauro Carvalho Chehab            cmd = [sys.executable, sphinx_build]
262819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
263819667bcSMauro Carvalho Chehab            cmd += self.sphinxopts
264819667bcSMauro Carvalho Chehab            cmd += build_args
265819667bcSMauro Carvalho Chehab
266819667bcSMauro Carvalho Chehab            if self.verbose:
267819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
268819667bcSMauro Carvalho Chehab
269819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
270819667bcSMauro Carvalho Chehab
271819667bcSMauro Carvalho Chehab    def handle_html(self, css, output_dir):
272819667bcSMauro Carvalho Chehab        """
273819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
274819667bcSMauro Carvalho Chehab
275819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
276819667bcSMauro Carvalho Chehab        copied to the output _static directory
277819667bcSMauro Carvalho Chehab        """
278819667bcSMauro Carvalho Chehab
279819667bcSMauro Carvalho Chehab        if not css:
280819667bcSMauro Carvalho Chehab            return
281819667bcSMauro Carvalho Chehab
282819667bcSMauro Carvalho Chehab        css = os.path.expanduser(css)
283819667bcSMauro Carvalho Chehab        if not css.startswith("/"):
284819667bcSMauro Carvalho Chehab            css = os.path.join(self.srctree, css)
285819667bcSMauro Carvalho Chehab
286819667bcSMauro Carvalho Chehab        static_dir = os.path.join(output_dir, "_static")
287819667bcSMauro Carvalho Chehab        os.makedirs(static_dir, exist_ok=True)
288819667bcSMauro Carvalho Chehab
289819667bcSMauro Carvalho Chehab        try:
290819667bcSMauro Carvalho Chehab            shutil.copy2(css, static_dir)
291819667bcSMauro Carvalho Chehab        except (OSError, IOError) as e:
292819667bcSMauro Carvalho Chehab            print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
293819667bcSMauro Carvalho Chehab
294819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
295819667bcSMauro Carvalho Chehab        """
296819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
297819667bcSMauro Carvalho Chehab
298819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
299819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
300819667bcSMauro Carvalho Chehab        directory.
301819667bcSMauro Carvalho Chehab        """
302819667bcSMauro Carvalho Chehab        builds = {}
303819667bcSMauro Carvalho Chehab        max_len = 0
304819667bcSMauro Carvalho Chehab
305819667bcSMauro Carvalho Chehab        #
306819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
307819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
308819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
309819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
310819667bcSMauro Carvalho Chehab        # file with a deny list.
311819667bcSMauro Carvalho Chehab        #
312819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
313819667bcSMauro Carvalho Chehab        #
314819667bcSMauro Carvalho Chehab        if deny_vf:
315819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
316819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
317819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
318819667bcSMauro Carvalho Chehab
319819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
320819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
321819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
322819667bcSMauro Carvalho Chehab
323819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
324819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
325819667bcSMauro Carvalho Chehab            else:
326819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
327819667bcSMauro Carvalho Chehab
328819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
329819667bcSMauro Carvalho Chehab
330819667bcSMauro Carvalho Chehab            tex_suffix = ".tex"
331819667bcSMauro Carvalho Chehab
332819667bcSMauro Carvalho Chehab            #
333819667bcSMauro Carvalho Chehab            # Process each .tex file
334819667bcSMauro Carvalho Chehab            #
335819667bcSMauro Carvalho Chehab
336819667bcSMauro Carvalho Chehab            has_tex = False
337819667bcSMauro Carvalho Chehab            build_failed = False
338819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
339819667bcSMauro Carvalho Chehab                for entry in it:
340819667bcSMauro Carvalho Chehab                    if not entry.name.endswith(tex_suffix):
341819667bcSMauro Carvalho Chehab                        continue
342819667bcSMauro Carvalho Chehab
343819667bcSMauro Carvalho Chehab                    name = entry.name[:-len(tex_suffix)]
344819667bcSMauro Carvalho Chehab                    has_tex = True
345819667bcSMauro Carvalho Chehab
346819667bcSMauro Carvalho Chehab                    #
347819667bcSMauro Carvalho Chehab                    # LaTeX PDF error code is almost useless for us:
348819667bcSMauro Carvalho Chehab                    # any warning makes it non-zero. For kernel doc builds it
349819667bcSMauro Carvalho Chehab                    # always return non-zero even when build succeeds.
350819667bcSMauro Carvalho Chehab                    # So, let's do the best next thing: check if all PDF
351819667bcSMauro Carvalho Chehab                    # files were built. If they're, print a summary and
352819667bcSMauro Carvalho Chehab                    # return 0 at the end of this function
353819667bcSMauro Carvalho Chehab                    #
354819667bcSMauro Carvalho Chehab                    try:
355819667bcSMauro Carvalho Chehab                        subprocess.run(latex_cmd + [entry.path],
356819667bcSMauro Carvalho Chehab                                       cwd=from_dir, check=True, env=self.env)
357819667bcSMauro Carvalho Chehab                    except subprocess.CalledProcessError:
358819667bcSMauro Carvalho Chehab                        pass
359819667bcSMauro Carvalho Chehab
360819667bcSMauro Carvalho Chehab                    pdf_name = name + ".pdf"
361819667bcSMauro Carvalho Chehab                    pdf_from = os.path.join(from_dir, pdf_name)
362819667bcSMauro Carvalho Chehab                    pdf_to = os.path.join(pdf_dir, pdf_name)
363819667bcSMauro Carvalho Chehab
364819667bcSMauro Carvalho Chehab                    if os.path.exists(pdf_from):
365819667bcSMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
366819667bcSMauro Carvalho Chehab                        builds[name] = os.path.relpath(pdf_to, self.builddir)
367819667bcSMauro Carvalho Chehab                    else:
368819667bcSMauro Carvalho Chehab                        builds[name] = "FAILED"
369819667bcSMauro Carvalho Chehab                        build_failed = True
370819667bcSMauro Carvalho Chehab
371819667bcSMauro Carvalho Chehab                    name = entry.name.removesuffix(".tex")
372819667bcSMauro Carvalho Chehab                    max_len = max(max_len, len(name))
373819667bcSMauro Carvalho Chehab
374819667bcSMauro Carvalho Chehab            if not has_tex:
375819667bcSMauro Carvalho Chehab                name = os.path.basename(from_dir)
376819667bcSMauro Carvalho Chehab                max_len = max(max_len, len(name))
377819667bcSMauro Carvalho Chehab                builds[name] = "FAILED (no .tex)"
378819667bcSMauro Carvalho Chehab                build_failed = True
379819667bcSMauro Carvalho Chehab
380819667bcSMauro Carvalho Chehab        msg = "Summary"
381819667bcSMauro Carvalho Chehab        msg += "\n" + "=" * len(msg)
382819667bcSMauro Carvalho Chehab        print()
383819667bcSMauro Carvalho Chehab        print(msg)
384819667bcSMauro Carvalho Chehab
385819667bcSMauro Carvalho Chehab        for pdf_name, pdf_file in builds.items():
386819667bcSMauro Carvalho Chehab            print(f"{pdf_name:<{max_len}}: {pdf_file}")
387819667bcSMauro Carvalho Chehab
388819667bcSMauro Carvalho Chehab        print()
389819667bcSMauro Carvalho Chehab
390819667bcSMauro Carvalho Chehab        if build_failed:
391819667bcSMauro Carvalho Chehab            msg = LatexFontChecker().check()
392819667bcSMauro Carvalho Chehab            if msg:
393819667bcSMauro Carvalho Chehab                print(msg)
394819667bcSMauro Carvalho Chehab
395819667bcSMauro Carvalho Chehab            sys.exit("PDF build failed: not all PDF files were created.")
396819667bcSMauro Carvalho Chehab        else:
397819667bcSMauro Carvalho Chehab            print("All PDF files were built.")
398819667bcSMauro Carvalho Chehab
399819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
400819667bcSMauro Carvalho Chehab        """
401819667bcSMauro Carvalho Chehab        Extra steps for Info output.
402819667bcSMauro Carvalho Chehab
403819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
404819667bcSMauro Carvalho Chehab        texinfo directory.
405819667bcSMauro Carvalho Chehab        """
406819667bcSMauro Carvalho Chehab
407819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
408819667bcSMauro Carvalho Chehab            try:
409819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
410819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
411819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
412819667bcSMauro Carvalho Chehab
413819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
414819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
415819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
416819667bcSMauro Carvalho Chehab
417819667bcSMauro Carvalho Chehab    def build(self, target, sphinxdirs=None, conf="conf.py",
418819667bcSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None):
419819667bcSMauro Carvalho Chehab        """
420819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
421819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
422819667bcSMauro Carvalho Chehab        """
423819667bcSMauro Carvalho Chehab
424819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
425819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
426819667bcSMauro Carvalho Chehab
427819667bcSMauro Carvalho Chehab        #
428819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
429819667bcSMauro Carvalho Chehab        #
430819667bcSMauro Carvalho Chehab        if target == "cleandocs":
431819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
432819667bcSMauro Carvalho Chehab            return
433819667bcSMauro Carvalho Chehab
434819667bcSMauro Carvalho Chehab        if theme:
435819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
436819667bcSMauro Carvalho Chehab
437819667bcSMauro Carvalho Chehab        #
438819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
439819667bcSMauro Carvalho Chehab        #
440819667bcSMauro Carvalho Chehab        sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
441819667bcSMauro Carvalho Chehab        if not sphinxbuild:
442819667bcSMauro Carvalho Chehab            sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
443819667bcSMauro Carvalho Chehab
444819667bcSMauro Carvalho Chehab        if builder == "latex":
445819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
446819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
447819667bcSMauro Carvalho Chehab
448819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
449819667bcSMauro Carvalho Chehab
450819667bcSMauro Carvalho Chehab        #
451819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
452819667bcSMauro Carvalho Chehab        #
453819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
454819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
455819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
456819667bcSMauro Carvalho Chehab
457819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
458819667bcSMauro Carvalho Chehab
459819667bcSMauro Carvalho Chehab        if builder == "latex":
460819667bcSMauro Carvalho Chehab            if not paper:
461819667bcSMauro Carvalho Chehab                paper = PAPER[1]
462819667bcSMauro Carvalho Chehab
463819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
464819667bcSMauro Carvalho Chehab
465819667bcSMauro Carvalho Chehab        if self.config_rust:
466819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
467819667bcSMauro Carvalho Chehab
468819667bcSMauro Carvalho Chehab        if conf:
469819667bcSMauro Carvalho Chehab            self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True)
470819667bcSMauro Carvalho Chehab
471819667bcSMauro Carvalho Chehab        if not sphinxdirs:
472819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
473819667bcSMauro Carvalho Chehab
474819667bcSMauro Carvalho Chehab        #
475819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
476819667bcSMauro Carvalho Chehab        #
477819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
478819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
479819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
480819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
481819667bcSMauro Carvalho Chehab            else:
482819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
483819667bcSMauro Carvalho Chehab
484819667bcSMauro Carvalho Chehab        #
485819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
486819667bcSMauro Carvalho Chehab        #
487819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
488819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
489819667bcSMauro Carvalho Chehab        # the beginning.
490819667bcSMauro Carvalho Chehab        #
491819667bcSMauro Carvalho Chehab        output_dirs = []
492819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
493819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
494819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
495819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
496819667bcSMauro Carvalho Chehab
497819667bcSMauro Carvalho Chehab            #
498819667bcSMauro Carvalho Chehab            # Make directory names canonical
499819667bcSMauro Carvalho Chehab            #
500819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
501819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
502819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
503819667bcSMauro Carvalho Chehab
504819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
505819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
506819667bcSMauro Carvalho Chehab
507819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
508819667bcSMauro Carvalho Chehab
509819667bcSMauro Carvalho Chehab            build_args = args + [
510819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
511819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_bin={kerneldoc}",
512819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
513819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
514819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
515819667bcSMauro Carvalho Chehab                src_dir,
516819667bcSMauro Carvalho Chehab                output_dir,
517819667bcSMauro Carvalho Chehab            ]
518819667bcSMauro Carvalho Chehab
519819667bcSMauro Carvalho Chehab            try:
520819667bcSMauro Carvalho Chehab                self.run_sphinx(sphinxbuild, build_args, env=self.env)
521819667bcSMauro Carvalho Chehab            except (OSError, ValueError, subprocess.SubprocessError) as e:
522819667bcSMauro Carvalho Chehab                sys.exit(f"Build failed: {repr(e)}")
523819667bcSMauro Carvalho Chehab
524819667bcSMauro Carvalho Chehab            #
525819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
526819667bcSMauro Carvalho Chehab            #
527819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
528819667bcSMauro Carvalho Chehab                self.handle_html(css, output_dir)
529819667bcSMauro Carvalho Chehab
530819667bcSMauro Carvalho Chehab        #
531819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
532819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
533819667bcSMauro Carvalho Chehab        #
534819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
535819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
536819667bcSMauro Carvalho Chehab        elif target == "infodocs":
537819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
538819667bcSMauro Carvalho Chehab
539819667bcSMauro Carvalho Chehabdef jobs_type(value):
540819667bcSMauro Carvalho Chehab    """
541819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
542819667bcSMauro Carvalho Chehab    equal or bigger than one.
543819667bcSMauro Carvalho Chehab    """
544819667bcSMauro Carvalho Chehab    if value is None:
545819667bcSMauro Carvalho Chehab        return None
546819667bcSMauro Carvalho Chehab
547819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
548819667bcSMauro Carvalho Chehab        return value.lower()
549819667bcSMauro Carvalho Chehab
550819667bcSMauro Carvalho Chehab    try:
551819667bcSMauro Carvalho Chehab        if int(value) >= 1:
552819667bcSMauro Carvalho Chehab            return value
553819667bcSMauro Carvalho Chehab
554819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
555819667bcSMauro Carvalho Chehab    except ValueError:
556819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
557819667bcSMauro Carvalho Chehab
558819667bcSMauro Carvalho Chehabdef main():
559819667bcSMauro Carvalho Chehab    """
560819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
561819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
562819667bcSMauro Carvalho Chehab    specified at os.environ.
563819667bcSMauro Carvalho Chehab    """
564819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
565819667bcSMauro Carvalho Chehab
566819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
567819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
568819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
569819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
570819667bcSMauro Carvalho Chehab    parser.add_argument("--conf", default="conf.py",
571819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
572819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
573819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
574819667bcSMauro Carvalho Chehab
575819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
576819667bcSMauro Carvalho Chehab
577819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
578819667bcSMauro Carvalho Chehab
579819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
580819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
581819667bcSMauro Carvalho Chehab
582819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
583819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
584819667bcSMauro Carvalho Chehab
585819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
586819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
587819667bcSMauro Carvalho Chehab
588819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
589819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
590819667bcSMauro Carvalho Chehab
591*2f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
592*2f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
593*2f99b85eSMauro Carvalho Chehab
594819667bcSMauro Carvalho Chehab    args = parser.parse_args()
595819667bcSMauro Carvalho Chehab
596819667bcSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION)
597819667bcSMauro Carvalho Chehab
598819667bcSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir,
599*2f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
600*2f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
601819667bcSMauro Carvalho Chehab
602819667bcSMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf,
603819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
604819667bcSMauro Carvalho Chehab                  deny_vf=args.deny_vf)
605819667bcSMauro Carvalho Chehab
606819667bcSMauro Carvalho Chehabif __name__ == "__main__":
607819667bcSMauro Carvalho Chehab    main()
608