xref: /linux/tools/docs/sphinx-build-wrapper (revision 2118ba7da61acbcc93a7e5fee95a88a5ea7c5772)
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
4882c294d4SMauro Carvalho Chehabimport locale
49819667bcSMauro Carvalho Chehabimport os
507e8a8143SMauro Carvalho Chehabimport re
51819667bcSMauro Carvalho Chehabimport shlex
52819667bcSMauro Carvalho Chehabimport shutil
53819667bcSMauro Carvalho Chehabimport subprocess
54819667bcSMauro Carvalho Chehabimport sys
55819667bcSMauro Carvalho Chehab
5608e14bc1SMauro Carvalho Chehabfrom concurrent import futures
577e8a8143SMauro Carvalho Chehabfrom glob import glob
5808e14bc1SMauro Carvalho Chehab
59819667bcSMauro Carvalho Chehabfrom lib.python_version import PythonVersion
60819667bcSMauro Carvalho Chehabfrom lib.latex_fonts import LatexFontChecker
61819667bcSMauro Carvalho Chehab
62819667bcSMauro Carvalho ChehabLIB_DIR = "../../scripts/lib"
63819667bcSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__))
64819667bcSMauro Carvalho Chehab
65819667bcSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
66819667bcSMauro Carvalho Chehab
67819667bcSMauro Carvalho Chehabfrom jobserver import JobserverExec         # pylint: disable=C0413,C0411,E0401
68819667bcSMauro Carvalho Chehab
69819667bcSMauro Carvalho Chehab#
70819667bcSMauro Carvalho Chehab#  Some constants
71819667bcSMauro Carvalho Chehab#
72819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version
73819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"]
74819667bcSMauro Carvalho Chehab
75819667bcSMauro Carvalho ChehabTARGETS = {
76819667bcSMauro Carvalho Chehab    "cleandocs":     { "builder": "clean" },
77819667bcSMauro Carvalho Chehab    "linkcheckdocs": { "builder": "linkcheck" },
78819667bcSMauro Carvalho Chehab    "htmldocs":      { "builder": "html" },
79819667bcSMauro Carvalho Chehab    "epubdocs":      { "builder": "epub",    "out_dir": "epub" },
80819667bcSMauro Carvalho Chehab    "texinfodocs":   { "builder": "texinfo", "out_dir": "texinfo" },
81819667bcSMauro Carvalho Chehab    "infodocs":      { "builder": "texinfo", "out_dir": "texinfo" },
827e8a8143SMauro Carvalho Chehab    "mandocs":       { "builder": "man",     "out_dir": "man" },
83819667bcSMauro Carvalho Chehab    "latexdocs":     { "builder": "latex",   "out_dir": "latex" },
84819667bcSMauro Carvalho Chehab    "pdfdocs":       { "builder": "latex",   "out_dir": "latex" },
85819667bcSMauro Carvalho Chehab    "xmldocs":       { "builder": "xml",     "out_dir": "xml" },
86819667bcSMauro Carvalho Chehab}
87819667bcSMauro Carvalho Chehab
88819667bcSMauro Carvalho Chehab
89819667bcSMauro Carvalho Chehab#
90819667bcSMauro Carvalho Chehab# SphinxBuilder class
91819667bcSMauro Carvalho Chehab#
92819667bcSMauro Carvalho Chehab
93819667bcSMauro Carvalho Chehabclass SphinxBuilder:
94819667bcSMauro Carvalho Chehab    """
95819667bcSMauro Carvalho Chehab    Handles a sphinx-build target, adding needed arguments to build
96819667bcSMauro Carvalho Chehab    with the Kernel.
97819667bcSMauro Carvalho Chehab    """
98819667bcSMauro Carvalho Chehab
99819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
100819667bcSMauro Carvalho Chehab        """
101819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
102819667bcSMauro Carvalho Chehab
103819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
104819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
105819667bcSMauro Carvalho Chehab        """
106819667bcSMauro Carvalho Chehab
107819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
108819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
109819667bcSMauro Carvalho Chehab            if use_cwd:
110819667bcSMauro Carvalho Chehab                base = os.getcwd()
111819667bcSMauro Carvalho Chehab            else:
112819667bcSMauro Carvalho Chehab                base = self.srctree
113819667bcSMauro Carvalho Chehab
114819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
115819667bcSMauro Carvalho Chehab
116819667bcSMauro Carvalho Chehab        if abs_path:
117819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
118819667bcSMauro Carvalho Chehab
119819667bcSMauro Carvalho Chehab        return path
120819667bcSMauro Carvalho Chehab
121819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
122819667bcSMauro Carvalho Chehab        """
123819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
124819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
125819667bcSMauro Carvalho Chehab
126819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
127819667bcSMauro Carvalho Chehab
128819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
129819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
130819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
131819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
132819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
133819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
134819667bcSMauro Carvalho Chehab        """
135819667bcSMauro Carvalho Chehab
136819667bcSMauro Carvalho Chehab        #
137819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
138819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
139819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
140819667bcSMauro Carvalho Chehab        #
141819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
142819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
143819667bcSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', type=int)
144819667bcSMauro Carvalho Chehab
145819667bcSMauro Carvalho Chehab        #
146819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
147819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
148819667bcSMauro Carvalho Chehab        #
149819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
150819667bcSMauro Carvalho Chehab
151819667bcSMauro Carvalho Chehab        #
152819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
153819667bcSMauro Carvalho Chehab        #
154819667bcSMauro Carvalho Chehab
155819667bcSMauro Carvalho Chehab        verbose = self.verbose
156819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
157819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
158819667bcSMauro Carvalho Chehab            verbose = False
159819667bcSMauro Carvalho Chehab
160819667bcSMauro Carvalho Chehab        #
161819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
162819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
163819667bcSMauro Carvalho Chehab        #
164819667bcSMauro Carvalho Chehab        if n_jobs:
165819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
166819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
167819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
168819667bcSMauro Carvalho Chehab        else:
169819667bcSMauro Carvalho Chehab            self.n_jobs = None
170819667bcSMauro Carvalho Chehab
171819667bcSMauro Carvalho Chehab        if not verbose:
172819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
173819667bcSMauro Carvalho Chehab
1742f99b85eSMauro Carvalho Chehab    def __init__(self, builddir, verbose=False, n_jobs=None, interactive=None):
175819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
176819667bcSMauro Carvalho Chehab        self.verbose = None
177819667bcSMauro Carvalho Chehab
178819667bcSMauro Carvalho Chehab        #
179819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
180819667bcSMauro Carvalho Chehab        #
181819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
182819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
183819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
1842f99b85eSMauro Carvalho Chehab
1852f99b85eSMauro Carvalho Chehab        if not interactive:
186819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
1872f99b85eSMauro Carvalho Chehab        else:
1882f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
189819667bcSMauro Carvalho Chehab
190819667bcSMauro Carvalho Chehab        if not verbose:
191819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
192819667bcSMauro Carvalho Chehab
193819667bcSMauro Carvalho Chehab        if verbose is not None:
194819667bcSMauro Carvalho Chehab            self.verbose = verbose
195819667bcSMauro Carvalho Chehab
196819667bcSMauro Carvalho Chehab        #
197819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
198819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
199819667bcSMauro Carvalho Chehab        #
200819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
201819667bcSMauro Carvalho Chehab        if not self.srctree:
202819667bcSMauro Carvalho Chehab            self.srctree = "."
203819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
204819667bcSMauro Carvalho Chehab
205819667bcSMauro Carvalho Chehab        #
206819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
207819667bcSMauro Carvalho Chehab        #
208819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
209819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
210819667bcSMauro Carvalho Chehab                                                      "scripts/kernel-doc.py"))
211819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
212819667bcSMauro Carvalho Chehab
213819667bcSMauro Carvalho Chehab        #
214819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
215819667bcSMauro Carvalho Chehab        #
216819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
217819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
218819667bcSMauro Carvalho Chehab
219819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
220819667bcSMauro Carvalho Chehab
221819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
222819667bcSMauro Carvalho Chehab
223819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
224819667bcSMauro Carvalho Chehab        """
225819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
226819667bcSMauro Carvalho Chehab
227819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
228819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
229819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
230819667bcSMauro Carvalho Chehab        jobs.
231819667bcSMauro Carvalho Chehab
232819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
233819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
234819667bcSMauro Carvalho Chehab
235819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
236819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
237819667bcSMauro Carvalho Chehab        """
238819667bcSMauro Carvalho Chehab
239819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
240819667bcSMauro Carvalho Chehab            if jobserver.claim:
241819667bcSMauro Carvalho Chehab                #
242819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
243819667bcSMauro Carvalho Chehab                #
244819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
245819667bcSMauro Carvalho Chehab            else:
246819667bcSMauro Carvalho Chehab                #
247819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
248819667bcSMauro Carvalho Chehab                #
249819667bcSMauro Carvalho Chehab                n_jobs = "auto"
250819667bcSMauro Carvalho Chehab
251819667bcSMauro Carvalho Chehab            #
252819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
253819667bcSMauro Carvalho Chehab            #
254819667bcSMauro Carvalho Chehab            if self.n_jobs:
255819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
256819667bcSMauro Carvalho Chehab
257819667bcSMauro Carvalho Chehab            cmd = [sys.executable, sphinx_build]
258819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
259819667bcSMauro Carvalho Chehab            cmd += self.sphinxopts
260819667bcSMauro Carvalho Chehab            cmd += build_args
261819667bcSMauro Carvalho Chehab
262819667bcSMauro Carvalho Chehab            if self.verbose:
263819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
264819667bcSMauro Carvalho Chehab
265819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
266819667bcSMauro Carvalho Chehab
267*2118ba7dSMauro Carvalho Chehab    def handle_html(self, css, output_dir, rustdoc):
268819667bcSMauro Carvalho Chehab        """
269819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
270819667bcSMauro Carvalho Chehab
271819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
272819667bcSMauro Carvalho Chehab        copied to the output _static directory
273819667bcSMauro Carvalho Chehab        """
274819667bcSMauro Carvalho Chehab
275*2118ba7dSMauro Carvalho Chehab        if css:
276819667bcSMauro Carvalho Chehab            css = os.path.expanduser(css)
277819667bcSMauro Carvalho Chehab            if not css.startswith("/"):
278819667bcSMauro Carvalho Chehab                css = os.path.join(self.srctree, css)
279819667bcSMauro Carvalho Chehab
280819667bcSMauro Carvalho Chehab            static_dir = os.path.join(output_dir, "_static")
281819667bcSMauro Carvalho Chehab            os.makedirs(static_dir, exist_ok=True)
282819667bcSMauro Carvalho Chehab
283819667bcSMauro Carvalho Chehab            try:
284819667bcSMauro Carvalho Chehab                shutil.copy2(css, static_dir)
285819667bcSMauro Carvalho Chehab            except (OSError, IOError) as e:
286819667bcSMauro Carvalho Chehab                print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
287819667bcSMauro Carvalho Chehab
288*2118ba7dSMauro Carvalho Chehab        if rustdoc:
289*2118ba7dSMauro Carvalho Chehab            if "MAKE" in self.env:
290*2118ba7dSMauro Carvalho Chehab                cmd = [self.env["MAKE"]]
291*2118ba7dSMauro Carvalho Chehab            else:
292*2118ba7dSMauro Carvalho Chehab                cmd = ["make", "LLVM=1"]
293*2118ba7dSMauro Carvalho Chehab
294*2118ba7dSMauro Carvalho Chehab            cmd += [ "rustdoc"]
295*2118ba7dSMauro Carvalho Chehab            if self.verbose:
296*2118ba7dSMauro Carvalho Chehab                print(" ".join(cmd))
297*2118ba7dSMauro Carvalho Chehab
298*2118ba7dSMauro Carvalho Chehab            try:
299*2118ba7dSMauro Carvalho Chehab                subprocess.run(cmd, check=True)
300*2118ba7dSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
301*2118ba7dSMauro Carvalho Chehab                print(f"Ignored errors when building rustdoc: {e}. Is RUST enabled?",
302*2118ba7dSMauro Carvalho Chehab                      file=sys.stderr)
303*2118ba7dSMauro Carvalho Chehab
30408e14bc1SMauro Carvalho Chehab    def build_pdf_file(self, latex_cmd, from_dir, path):
30508e14bc1SMauro Carvalho Chehab        """Builds a single pdf file using latex_cmd"""
30608e14bc1SMauro Carvalho Chehab        try:
30708e14bc1SMauro Carvalho Chehab            subprocess.run(latex_cmd + [path],
30808e14bc1SMauro Carvalho Chehab                            cwd=from_dir, check=True, env=self.env)
30908e14bc1SMauro Carvalho Chehab
31008e14bc1SMauro Carvalho Chehab            return True
31108e14bc1SMauro Carvalho Chehab        except subprocess.CalledProcessError:
31208e14bc1SMauro Carvalho Chehab            return False
31308e14bc1SMauro Carvalho Chehab
31408e14bc1SMauro Carvalho Chehab    def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
31508e14bc1SMauro Carvalho Chehab        """Build PDF files in parallel if possible"""
31608e14bc1SMauro Carvalho Chehab        builds = {}
31708e14bc1SMauro Carvalho Chehab        build_failed = False
31808e14bc1SMauro Carvalho Chehab        max_len = 0
31908e14bc1SMauro Carvalho Chehab        has_tex = False
32008e14bc1SMauro Carvalho Chehab
32108e14bc1SMauro Carvalho Chehab        #
32208e14bc1SMauro Carvalho Chehab        # LaTeX PDF error code is almost useless for us:
32308e14bc1SMauro Carvalho Chehab        # any warning makes it non-zero. For kernel doc builds it always return
32408e14bc1SMauro Carvalho Chehab        # non-zero even when build succeeds. So, let's do the best next thing:
32508e14bc1SMauro Carvalho Chehab        # Ignore build errors. At the end, check if all PDF files were built,
32608e14bc1SMauro Carvalho Chehab        # printing a summary with the built ones and returning 0 if all of
32708e14bc1SMauro Carvalho Chehab        # them were actually built.
32808e14bc1SMauro Carvalho Chehab        #
32908e14bc1SMauro Carvalho Chehab        with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
33008e14bc1SMauro Carvalho Chehab            jobs = {}
33108e14bc1SMauro Carvalho Chehab
33208e14bc1SMauro Carvalho Chehab            for from_dir, pdf_dir, entry in tex_files:
33308e14bc1SMauro Carvalho Chehab                name = entry.name
33408e14bc1SMauro Carvalho Chehab
33508e14bc1SMauro Carvalho Chehab                if not name.endswith(tex_suffix):
33608e14bc1SMauro Carvalho Chehab                    continue
33708e14bc1SMauro Carvalho Chehab
33808e14bc1SMauro Carvalho Chehab                name = name[:-len(tex_suffix)]
33908e14bc1SMauro Carvalho Chehab                has_tex = True
34008e14bc1SMauro Carvalho Chehab
34108e14bc1SMauro Carvalho Chehab                future = executor.submit(self.build_pdf_file, latex_cmd,
34208e14bc1SMauro Carvalho Chehab                                         from_dir, entry.path)
34308e14bc1SMauro Carvalho Chehab                jobs[future] = (from_dir, pdf_dir, name)
34408e14bc1SMauro Carvalho Chehab
34508e14bc1SMauro Carvalho Chehab            for future in futures.as_completed(jobs):
34608e14bc1SMauro Carvalho Chehab                from_dir, pdf_dir, name = jobs[future]
34708e14bc1SMauro Carvalho Chehab
34808e14bc1SMauro Carvalho Chehab                pdf_name = name + ".pdf"
34908e14bc1SMauro Carvalho Chehab                pdf_from = os.path.join(from_dir, pdf_name)
3500d9abc76SMauro Carvalho Chehab                pdf_to = os.path.join(pdf_dir, pdf_name)
3510d9abc76SMauro Carvalho Chehab                out_name = os.path.relpath(pdf_to, self.builddir)
3520d9abc76SMauro Carvalho Chehab                max_len = max(max_len, len(out_name))
35308e14bc1SMauro Carvalho Chehab
35408e14bc1SMauro Carvalho Chehab                try:
35508e14bc1SMauro Carvalho Chehab                    success = future.result()
35608e14bc1SMauro Carvalho Chehab
35708e14bc1SMauro Carvalho Chehab                    if success and os.path.exists(pdf_from):
35808e14bc1SMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
35908e14bc1SMauro Carvalho Chehab
36008e14bc1SMauro Carvalho Chehab                        #
36108e14bc1SMauro Carvalho Chehab                        # if verbose, get the name of built PDF file
36208e14bc1SMauro Carvalho Chehab                        #
36308e14bc1SMauro Carvalho Chehab                        if self.verbose:
3640d9abc76SMauro Carvalho Chehab                           builds[out_name] = "SUCCESS"
36508e14bc1SMauro Carvalho Chehab                    else:
3660d9abc76SMauro Carvalho Chehab                        builds[out_name] = "FAILED"
36708e14bc1SMauro Carvalho Chehab                        build_failed = True
36808e14bc1SMauro Carvalho Chehab                except futures.Error as e:
3690d9abc76SMauro Carvalho Chehab                    builds[out_name] = f"FAILED ({repr(e)})"
37008e14bc1SMauro Carvalho Chehab                    build_failed = True
37108e14bc1SMauro Carvalho Chehab
37208e14bc1SMauro Carvalho Chehab        #
37308e14bc1SMauro Carvalho Chehab        # Handle case where no .tex files were found
37408e14bc1SMauro Carvalho Chehab        #
37508e14bc1SMauro Carvalho Chehab        if not has_tex:
3760d9abc76SMauro Carvalho Chehab            out_name = "LaTeX files"
3770d9abc76SMauro Carvalho Chehab            max_len = max(max_len, len(out_name))
3780d9abc76SMauro Carvalho Chehab            builds[out_name] = "FAILED: no .tex files were generated"
37908e14bc1SMauro Carvalho Chehab            build_failed = True
38008e14bc1SMauro Carvalho Chehab
38108e14bc1SMauro Carvalho Chehab        return builds, build_failed, max_len
38208e14bc1SMauro Carvalho Chehab
383819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
384819667bcSMauro Carvalho Chehab        """
385819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
386819667bcSMauro Carvalho Chehab
387819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
388819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
389819667bcSMauro Carvalho Chehab        directory.
390819667bcSMauro Carvalho Chehab        """
391819667bcSMauro Carvalho Chehab        builds = {}
392819667bcSMauro Carvalho Chehab        max_len = 0
39308e14bc1SMauro Carvalho Chehab        tex_suffix = ".tex"
39408e14bc1SMauro Carvalho Chehab        tex_files = []
395819667bcSMauro Carvalho Chehab
396819667bcSMauro Carvalho Chehab        #
397819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
398819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
399819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
400819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
401819667bcSMauro Carvalho Chehab        # file with a deny list.
402819667bcSMauro Carvalho Chehab        #
403819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
404819667bcSMauro Carvalho Chehab        #
405819667bcSMauro Carvalho Chehab        if deny_vf:
406819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
407819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
408819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
409819667bcSMauro Carvalho Chehab
410819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
411819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
412819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
413819667bcSMauro Carvalho Chehab
414819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
415819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
416819667bcSMauro Carvalho Chehab            else:
417819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
418819667bcSMauro Carvalho Chehab
419819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
420819667bcSMauro Carvalho Chehab
42108e14bc1SMauro Carvalho Chehab            # Get a list of tex files to process
422819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
423819667bcSMauro Carvalho Chehab                for entry in it:
42408e14bc1SMauro Carvalho Chehab                    if entry.name.endswith(tex_suffix):
42508e14bc1SMauro Carvalho Chehab                        tex_files.append((from_dir, pdf_dir, entry))
426819667bcSMauro Carvalho Chehab
427819667bcSMauro Carvalho Chehab        #
42808e14bc1SMauro Carvalho Chehab        # When using make, this won't be used, as the number of jobs comes
42908e14bc1SMauro Carvalho Chehab        # from POSIX jobserver. So, this covers the case where build comes
43008e14bc1SMauro Carvalho Chehab        # from command line. On such case, serialize by default, except if
43108e14bc1SMauro Carvalho Chehab        # the user explicitly sets the number of jobs.
432819667bcSMauro Carvalho Chehab        #
43308e14bc1SMauro Carvalho Chehab        n_jobs = 1
43408e14bc1SMauro Carvalho Chehab
43508e14bc1SMauro Carvalho Chehab        # n_jobs is either an integer or "auto". Only use it if it is a number
43608e14bc1SMauro Carvalho Chehab        if self.n_jobs:
437819667bcSMauro Carvalho Chehab            try:
43808e14bc1SMauro Carvalho Chehab                n_jobs = int(self.n_jobs)
43908e14bc1SMauro Carvalho Chehab            except ValueError:
440819667bcSMauro Carvalho Chehab                pass
441819667bcSMauro Carvalho Chehab
44208e14bc1SMauro Carvalho Chehab        #
44308e14bc1SMauro Carvalho Chehab        # When using make, jobserver.claim is the number of jobs that were
44408e14bc1SMauro Carvalho Chehab        # used with "-j" and that aren't used by other make targets
44508e14bc1SMauro Carvalho Chehab        #
44608e14bc1SMauro Carvalho Chehab        with JobserverExec() as jobserver:
44708e14bc1SMauro Carvalho Chehab            n_jobs = 1
448819667bcSMauro Carvalho Chehab
44908e14bc1SMauro Carvalho Chehab            #
45008e14bc1SMauro Carvalho Chehab            # Handle the case when a parameter is passed via command line,
45108e14bc1SMauro Carvalho Chehab            # using it as default, if jobserver doesn't claim anything
45208e14bc1SMauro Carvalho Chehab            #
45308e14bc1SMauro Carvalho Chehab            if self.n_jobs:
45408e14bc1SMauro Carvalho Chehab                try:
45508e14bc1SMauro Carvalho Chehab                    n_jobs = int(self.n_jobs)
45608e14bc1SMauro Carvalho Chehab                except ValueError:
45708e14bc1SMauro Carvalho Chehab                    pass
458819667bcSMauro Carvalho Chehab
45908e14bc1SMauro Carvalho Chehab            if jobserver.claim:
46008e14bc1SMauro Carvalho Chehab                n_jobs = jobserver.claim
461819667bcSMauro Carvalho Chehab
46208e14bc1SMauro Carvalho Chehab            builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
46308e14bc1SMauro Carvalho Chehab                                                                    latex_cmd,
46408e14bc1SMauro Carvalho Chehab                                                                    tex_files,
46508e14bc1SMauro Carvalho Chehab                                                                    n_jobs)
466819667bcSMauro Carvalho Chehab
46708e14bc1SMauro Carvalho Chehab        #
46808e14bc1SMauro Carvalho Chehab        # In verbose mode, print a summary with the build results per file.
46908e14bc1SMauro Carvalho Chehab        # Otherwise, print a single line with all failures, if any.
47008e14bc1SMauro Carvalho Chehab        # On both cases, return code 1 indicates build failures,
47108e14bc1SMauro Carvalho Chehab        #
47208e14bc1SMauro Carvalho Chehab        if self.verbose:
473819667bcSMauro Carvalho Chehab            msg = "Summary"
474819667bcSMauro Carvalho Chehab            msg += "\n" + "=" * len(msg)
475819667bcSMauro Carvalho Chehab            print()
476819667bcSMauro Carvalho Chehab            print(msg)
477819667bcSMauro Carvalho Chehab
478819667bcSMauro Carvalho Chehab            for pdf_name, pdf_file in builds.items():
479819667bcSMauro Carvalho Chehab                print(f"{pdf_name:<{max_len}}: {pdf_file}")
480819667bcSMauro Carvalho Chehab
481819667bcSMauro Carvalho Chehab            print()
482819667bcSMauro Carvalho Chehab            if build_failed:
483819667bcSMauro Carvalho Chehab                msg = LatexFontChecker().check()
484819667bcSMauro Carvalho Chehab                if msg:
485819667bcSMauro Carvalho Chehab                    print(msg)
486819667bcSMauro Carvalho Chehab
48708e14bc1SMauro Carvalho Chehab                sys.exit("Error: not all PDF files were created.")
48808e14bc1SMauro Carvalho Chehab
48908e14bc1SMauro Carvalho Chehab        elif build_failed:
49008e14bc1SMauro Carvalho Chehab            n_failures = len(builds)
49108e14bc1SMauro Carvalho Chehab            failures = ", ".join(builds.keys())
49208e14bc1SMauro Carvalho Chehab
49308e14bc1SMauro Carvalho Chehab            msg = LatexFontChecker().check()
49408e14bc1SMauro Carvalho Chehab            if msg:
49508e14bc1SMauro Carvalho Chehab                print(msg)
49608e14bc1SMauro Carvalho Chehab
49708e14bc1SMauro Carvalho Chehab            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
498819667bcSMauro Carvalho Chehab
499819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
500819667bcSMauro Carvalho Chehab        """
501819667bcSMauro Carvalho Chehab        Extra steps for Info output.
502819667bcSMauro Carvalho Chehab
503819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
504819667bcSMauro Carvalho Chehab        texinfo directory.
505819667bcSMauro Carvalho Chehab        """
506819667bcSMauro Carvalho Chehab
507819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
508819667bcSMauro Carvalho Chehab            try:
509819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
510819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
511819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
512819667bcSMauro Carvalho Chehab
5137e8a8143SMauro Carvalho Chehab    def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
5147e8a8143SMauro Carvalho Chehab        """
5157e8a8143SMauro Carvalho Chehab        Create man pages from kernel-doc output
5167e8a8143SMauro Carvalho Chehab        """
5177e8a8143SMauro Carvalho Chehab
5187e8a8143SMauro Carvalho Chehab        re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
5197e8a8143SMauro Carvalho Chehab        re_man = re.compile(r'^\.TH "[^"]*" (\d+) "([^"]*)"')
5207e8a8143SMauro Carvalho Chehab
5217e8a8143SMauro Carvalho Chehab        if docs_dir == src_dir:
5227e8a8143SMauro Carvalho Chehab            #
5237e8a8143SMauro Carvalho Chehab            # Pick the entire set of kernel-doc markups from the entire tree
5247e8a8143SMauro Carvalho Chehab            #
5257e8a8143SMauro Carvalho Chehab            kdoc_files = set([self.srctree])
5267e8a8143SMauro Carvalho Chehab        else:
5277e8a8143SMauro Carvalho Chehab            kdoc_files = set()
5287e8a8143SMauro Carvalho Chehab
5297e8a8143SMauro Carvalho Chehab            for fname in glob(os.path.join(src_dir, "**"), recursive=True):
5307e8a8143SMauro Carvalho Chehab                if os.path.isfile(fname) and fname.endswith(".rst"):
5317e8a8143SMauro Carvalho Chehab                    with open(fname, "r", encoding="utf-8") as in_fp:
5327e8a8143SMauro Carvalho Chehab                        data = in_fp.read()
5337e8a8143SMauro Carvalho Chehab
5347e8a8143SMauro Carvalho Chehab                    for line in data.split("\n"):
5357e8a8143SMauro Carvalho Chehab                        match = re_kernel_doc.match(line)
5367e8a8143SMauro Carvalho Chehab                        if match:
5377e8a8143SMauro Carvalho Chehab                            if os.path.isfile(match.group(1)):
5387e8a8143SMauro Carvalho Chehab                                kdoc_files.add(match.group(1))
5397e8a8143SMauro Carvalho Chehab
5407e8a8143SMauro Carvalho Chehab        if not kdoc_files:
5417e8a8143SMauro Carvalho Chehab                sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
5427e8a8143SMauro Carvalho Chehab
5437e8a8143SMauro Carvalho Chehab        cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
5447e8a8143SMauro Carvalho Chehab        try:
5457e8a8143SMauro Carvalho Chehab            if self.verbose:
5467e8a8143SMauro Carvalho Chehab                print(" ".join(cmd))
5477e8a8143SMauro Carvalho Chehab
5487e8a8143SMauro Carvalho Chehab            result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
5497e8a8143SMauro Carvalho Chehab
5507e8a8143SMauro Carvalho Chehab            if result.returncode:
5517e8a8143SMauro Carvalho Chehab                print(f"Warning: kernel-doc returned {result.returncode} warnings")
5527e8a8143SMauro Carvalho Chehab
5537e8a8143SMauro Carvalho Chehab        except (OSError, ValueError, subprocess.SubprocessError) as e:
5547e8a8143SMauro Carvalho Chehab            sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
5557e8a8143SMauro Carvalho Chehab
5567e8a8143SMauro Carvalho Chehab        fp = None
5577e8a8143SMauro Carvalho Chehab        try:
5587e8a8143SMauro Carvalho Chehab            for line in result.stdout.split("\n"):
5597e8a8143SMauro Carvalho Chehab                match = re_man.match(line)
5607e8a8143SMauro Carvalho Chehab                if not match:
5617e8a8143SMauro Carvalho Chehab                    if fp:
5627e8a8143SMauro Carvalho Chehab                        fp.write(line + '\n')
5637e8a8143SMauro Carvalho Chehab                    continue
5647e8a8143SMauro Carvalho Chehab
5657e8a8143SMauro Carvalho Chehab                if fp:
5667e8a8143SMauro Carvalho Chehab                    fp.close()
5677e8a8143SMauro Carvalho Chehab
5687e8a8143SMauro Carvalho Chehab                fname = f"{output_dir}/{match.group(2)}.{match.group(1)}"
5697e8a8143SMauro Carvalho Chehab
5707e8a8143SMauro Carvalho Chehab                if self.verbose:
5717e8a8143SMauro Carvalho Chehab                    print(f"Creating {fname}")
5727e8a8143SMauro Carvalho Chehab                fp = open(fname, "w", encoding="utf-8")
5737e8a8143SMauro Carvalho Chehab                fp.write(line + '\n')
5747e8a8143SMauro Carvalho Chehab        finally:
5757e8a8143SMauro Carvalho Chehab            if fp:
5767e8a8143SMauro Carvalho Chehab                fp.close()
5777e8a8143SMauro Carvalho Chehab
578819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
579819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
580819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
581819667bcSMauro Carvalho Chehab
582819667bcSMauro Carvalho Chehab    def build(self, target, sphinxdirs=None, conf="conf.py",
583*2118ba7dSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None, rustdoc=False):
584819667bcSMauro Carvalho Chehab        """
585819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
586819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
587819667bcSMauro Carvalho Chehab        """
588819667bcSMauro Carvalho Chehab
589819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
590819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
591819667bcSMauro Carvalho Chehab
592819667bcSMauro Carvalho Chehab        #
593819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
594819667bcSMauro Carvalho Chehab        #
595819667bcSMauro Carvalho Chehab        if target == "cleandocs":
596819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
597819667bcSMauro Carvalho Chehab            return
598819667bcSMauro Carvalho Chehab
599819667bcSMauro Carvalho Chehab        if theme:
600819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
601819667bcSMauro Carvalho Chehab
602819667bcSMauro Carvalho Chehab        #
603819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
604819667bcSMauro Carvalho Chehab        #
605819667bcSMauro Carvalho Chehab        sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
6067e8a8143SMauro Carvalho Chehab        if not sphinxbuild and target != "mandocs":
607819667bcSMauro Carvalho Chehab            sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
608819667bcSMauro Carvalho Chehab
609819667bcSMauro Carvalho Chehab        if builder == "latex":
610819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
611819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
612819667bcSMauro Carvalho Chehab
613819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
614819667bcSMauro Carvalho Chehab
615819667bcSMauro Carvalho Chehab        #
616819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
617819667bcSMauro Carvalho Chehab        #
618819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
619819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
620819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
621819667bcSMauro Carvalho Chehab
622819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
623819667bcSMauro Carvalho Chehab
624819667bcSMauro Carvalho Chehab        if builder == "latex":
625819667bcSMauro Carvalho Chehab            if not paper:
626819667bcSMauro Carvalho Chehab                paper = PAPER[1]
627819667bcSMauro Carvalho Chehab
628819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
629819667bcSMauro Carvalho Chehab
630*2118ba7dSMauro Carvalho Chehab        if rustdoc:
631819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
632819667bcSMauro Carvalho Chehab
633819667bcSMauro Carvalho Chehab        if conf:
634819667bcSMauro Carvalho Chehab            self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True)
635819667bcSMauro Carvalho Chehab
636819667bcSMauro Carvalho Chehab        if not sphinxdirs:
637819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
638819667bcSMauro Carvalho Chehab
639819667bcSMauro Carvalho Chehab        #
64082c294d4SMauro Carvalho Chehab        # The sphinx-build tool has a bug: internally, it tries to set
64182c294d4SMauro Carvalho Chehab        # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
64282c294d4SMauro Carvalho Chehab        # crash if language is not set. Detect and fix it.
64382c294d4SMauro Carvalho Chehab        #
64482c294d4SMauro Carvalho Chehab        try:
64582c294d4SMauro Carvalho Chehab            locale.setlocale(locale.LC_ALL, '')
64682c294d4SMauro Carvalho Chehab        except locale.Error:
64782c294d4SMauro Carvalho Chehab            self.env["LC_ALL"] = "C"
64882c294d4SMauro Carvalho Chehab
64982c294d4SMauro Carvalho Chehab        #
650819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
651819667bcSMauro Carvalho Chehab        #
652819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
653819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
654819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
655819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
656819667bcSMauro Carvalho Chehab            else:
657819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
658819667bcSMauro Carvalho Chehab
659819667bcSMauro Carvalho Chehab        #
660819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
661819667bcSMauro Carvalho Chehab        #
662819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
663819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
664819667bcSMauro Carvalho Chehab        # the beginning.
665819667bcSMauro Carvalho Chehab        #
666819667bcSMauro Carvalho Chehab        output_dirs = []
667819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
668819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
669819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
670819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
671819667bcSMauro Carvalho Chehab
672819667bcSMauro Carvalho Chehab            #
673819667bcSMauro Carvalho Chehab            # Make directory names canonical
674819667bcSMauro Carvalho Chehab            #
675819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
676819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
677819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
678819667bcSMauro Carvalho Chehab
679819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
680819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
681819667bcSMauro Carvalho Chehab
682819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
683819667bcSMauro Carvalho Chehab
684819667bcSMauro Carvalho Chehab            build_args = args + [
685819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
686819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_bin={kerneldoc}",
687819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
688819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
689819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
690819667bcSMauro Carvalho Chehab                src_dir,
691819667bcSMauro Carvalho Chehab                output_dir,
692819667bcSMauro Carvalho Chehab            ]
693819667bcSMauro Carvalho Chehab
6947e8a8143SMauro Carvalho Chehab            if target == "mandocs":
6957e8a8143SMauro Carvalho Chehab                self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
6967e8a8143SMauro Carvalho Chehab            else:
697819667bcSMauro Carvalho Chehab                try:
698819667bcSMauro Carvalho Chehab                    self.run_sphinx(sphinxbuild, build_args, env=self.env)
699819667bcSMauro Carvalho Chehab                except (OSError, ValueError, subprocess.SubprocessError) as e:
700819667bcSMauro Carvalho Chehab                    sys.exit(f"Build failed: {repr(e)}")
701819667bcSMauro Carvalho Chehab
702819667bcSMauro Carvalho Chehab            #
703819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
704819667bcSMauro Carvalho Chehab            #
705819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
706*2118ba7dSMauro Carvalho Chehab                self.handle_html(css, output_dir, rustdoc)
707819667bcSMauro Carvalho Chehab
708819667bcSMauro Carvalho Chehab        #
709819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
710819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
711819667bcSMauro Carvalho Chehab        #
712819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
713819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
714819667bcSMauro Carvalho Chehab        elif target == "infodocs":
715819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
716819667bcSMauro Carvalho Chehab
717819667bcSMauro Carvalho Chehabdef jobs_type(value):
718819667bcSMauro Carvalho Chehab    """
719819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
720819667bcSMauro Carvalho Chehab    equal or bigger than one.
721819667bcSMauro Carvalho Chehab    """
722819667bcSMauro Carvalho Chehab    if value is None:
723819667bcSMauro Carvalho Chehab        return None
724819667bcSMauro Carvalho Chehab
725819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
726819667bcSMauro Carvalho Chehab        return value.lower()
727819667bcSMauro Carvalho Chehab
728819667bcSMauro Carvalho Chehab    try:
729819667bcSMauro Carvalho Chehab        if int(value) >= 1:
730819667bcSMauro Carvalho Chehab            return value
731819667bcSMauro Carvalho Chehab
732819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
733819667bcSMauro Carvalho Chehab    except ValueError:
734819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
735819667bcSMauro Carvalho Chehab
736819667bcSMauro Carvalho Chehabdef main():
737819667bcSMauro Carvalho Chehab    """
738819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
739819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
740819667bcSMauro Carvalho Chehab    specified at os.environ.
741819667bcSMauro Carvalho Chehab    """
742819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
743819667bcSMauro Carvalho Chehab
744819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
745819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
746819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
747819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
748819667bcSMauro Carvalho Chehab    parser.add_argument("--conf", default="conf.py",
749819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
750819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
751819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
752819667bcSMauro Carvalho Chehab
753819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
754819667bcSMauro Carvalho Chehab
755819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
756819667bcSMauro Carvalho Chehab
757819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
758819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
759819667bcSMauro Carvalho Chehab
760819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
761819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
762819667bcSMauro Carvalho Chehab
763*2118ba7dSMauro Carvalho Chehab    parser.add_argument('--rustdoc', action="store_true",
764*2118ba7dSMauro Carvalho Chehab                        help="Enable rustdoc build. Requires CONFIG_RUST")
765*2118ba7dSMauro Carvalho Chehab
766819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
767819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
768819667bcSMauro Carvalho Chehab
769819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
770819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
771819667bcSMauro Carvalho Chehab
7722f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
7732f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
7742f99b85eSMauro Carvalho Chehab
775819667bcSMauro Carvalho Chehab    args = parser.parse_args()
776819667bcSMauro Carvalho Chehab
777819667bcSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION)
778819667bcSMauro Carvalho Chehab
779819667bcSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir,
7802f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
7812f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
782819667bcSMauro Carvalho Chehab
783819667bcSMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf,
784819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
785*2118ba7dSMauro Carvalho Chehab                  rustdoc=args.rustdoc, deny_vf=args.deny_vf)
786819667bcSMauro Carvalho Chehab
787819667bcSMauro Carvalho Chehabif __name__ == "__main__":
788819667bcSMauro Carvalho Chehab    main()
789