xref: /linux/tools/docs/sphinx-build-wrapper (revision 7e8a8143ecc3940dbc3664b24b132ec7420d1053)
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
50*7e8a8143SMauro 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
57*7e8a8143SMauro 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" },
82*7e8a8143SMauro 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 is_rust_enabled(self):
100819667bcSMauro Carvalho Chehab        """Check if rust is enabled at .config"""
101819667bcSMauro Carvalho Chehab        config_path = os.path.join(self.srctree, ".config")
102819667bcSMauro Carvalho Chehab        if os.path.isfile(config_path):
103819667bcSMauro Carvalho Chehab            with open(config_path, "r", encoding="utf-8") as f:
104819667bcSMauro Carvalho Chehab                return "CONFIG_RUST=y" in f.read()
105819667bcSMauro Carvalho Chehab        return False
106819667bcSMauro Carvalho Chehab
107819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
108819667bcSMauro Carvalho Chehab        """
109819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
110819667bcSMauro Carvalho Chehab
111819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
112819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
113819667bcSMauro Carvalho Chehab        """
114819667bcSMauro Carvalho Chehab
115819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
116819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
117819667bcSMauro Carvalho Chehab            if use_cwd:
118819667bcSMauro Carvalho Chehab                base = os.getcwd()
119819667bcSMauro Carvalho Chehab            else:
120819667bcSMauro Carvalho Chehab                base = self.srctree
121819667bcSMauro Carvalho Chehab
122819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
123819667bcSMauro Carvalho Chehab
124819667bcSMauro Carvalho Chehab        if abs_path:
125819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
126819667bcSMauro Carvalho Chehab
127819667bcSMauro Carvalho Chehab        return path
128819667bcSMauro Carvalho Chehab
129819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
130819667bcSMauro Carvalho Chehab        """
131819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
132819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
133819667bcSMauro Carvalho Chehab
134819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
135819667bcSMauro Carvalho Chehab
136819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
137819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
138819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
139819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
140819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
141819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
142819667bcSMauro Carvalho Chehab        """
143819667bcSMauro Carvalho Chehab
144819667bcSMauro Carvalho Chehab        #
145819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
146819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
147819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
148819667bcSMauro Carvalho Chehab        #
149819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
150819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
151819667bcSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', type=int)
152819667bcSMauro Carvalho Chehab
153819667bcSMauro Carvalho Chehab        #
154819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
155819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
156819667bcSMauro Carvalho Chehab        #
157819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
158819667bcSMauro Carvalho Chehab
159819667bcSMauro Carvalho Chehab        #
160819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
161819667bcSMauro Carvalho Chehab        #
162819667bcSMauro Carvalho Chehab
163819667bcSMauro Carvalho Chehab        verbose = self.verbose
164819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
165819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
166819667bcSMauro Carvalho Chehab            verbose = False
167819667bcSMauro Carvalho Chehab
168819667bcSMauro Carvalho Chehab        #
169819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
170819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
171819667bcSMauro Carvalho Chehab        #
172819667bcSMauro Carvalho Chehab        if n_jobs:
173819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
174819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
175819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
176819667bcSMauro Carvalho Chehab        else:
177819667bcSMauro Carvalho Chehab            self.n_jobs = None
178819667bcSMauro Carvalho Chehab
179819667bcSMauro Carvalho Chehab        if not verbose:
180819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
181819667bcSMauro Carvalho Chehab
1822f99b85eSMauro Carvalho Chehab    def __init__(self, builddir, verbose=False, n_jobs=None, interactive=None):
183819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
184819667bcSMauro Carvalho Chehab        self.verbose = None
185819667bcSMauro Carvalho Chehab
186819667bcSMauro Carvalho Chehab        #
187819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
188819667bcSMauro Carvalho Chehab        #
189819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
190819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
191819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
1922f99b85eSMauro Carvalho Chehab
1932f99b85eSMauro Carvalho Chehab        if not interactive:
194819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
1952f99b85eSMauro Carvalho Chehab        else:
1962f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
197819667bcSMauro Carvalho Chehab
198819667bcSMauro Carvalho Chehab        if not verbose:
199819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
200819667bcSMauro Carvalho Chehab
201819667bcSMauro Carvalho Chehab        if verbose is not None:
202819667bcSMauro Carvalho Chehab            self.verbose = verbose
203819667bcSMauro Carvalho Chehab
204819667bcSMauro Carvalho Chehab        #
205819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
206819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
207819667bcSMauro Carvalho Chehab        #
208819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
209819667bcSMauro Carvalho Chehab        if not self.srctree:
210819667bcSMauro Carvalho Chehab            self.srctree = "."
211819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
212819667bcSMauro Carvalho Chehab
213819667bcSMauro Carvalho Chehab        #
214819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
215819667bcSMauro Carvalho Chehab        #
216819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
217819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
218819667bcSMauro Carvalho Chehab                                                      "scripts/kernel-doc.py"))
219819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
220819667bcSMauro Carvalho Chehab
221819667bcSMauro Carvalho Chehab        self.config_rust = self.is_rust_enabled()
222819667bcSMauro Carvalho Chehab
223819667bcSMauro Carvalho Chehab        #
224819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
225819667bcSMauro Carvalho Chehab        #
226819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
227819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
228819667bcSMauro Carvalho Chehab
229819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
230819667bcSMauro Carvalho Chehab
231819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
232819667bcSMauro Carvalho Chehab
233819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
234819667bcSMauro Carvalho Chehab        """
235819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
236819667bcSMauro Carvalho Chehab
237819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
238819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
239819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
240819667bcSMauro Carvalho Chehab        jobs.
241819667bcSMauro Carvalho Chehab
242819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
243819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
244819667bcSMauro Carvalho Chehab
245819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
246819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
247819667bcSMauro Carvalho Chehab        """
248819667bcSMauro Carvalho Chehab
249819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
250819667bcSMauro Carvalho Chehab            if jobserver.claim:
251819667bcSMauro Carvalho Chehab                #
252819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
253819667bcSMauro Carvalho Chehab                #
254819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
255819667bcSMauro Carvalho Chehab            else:
256819667bcSMauro Carvalho Chehab                #
257819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
258819667bcSMauro Carvalho Chehab                #
259819667bcSMauro Carvalho Chehab                n_jobs = "auto"
260819667bcSMauro Carvalho Chehab
261819667bcSMauro Carvalho Chehab            #
262819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
263819667bcSMauro Carvalho Chehab            #
264819667bcSMauro Carvalho Chehab            if self.n_jobs:
265819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
266819667bcSMauro Carvalho Chehab
267819667bcSMauro Carvalho Chehab            cmd = [sys.executable, sphinx_build]
268819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
269819667bcSMauro Carvalho Chehab            cmd += self.sphinxopts
270819667bcSMauro Carvalho Chehab            cmd += build_args
271819667bcSMauro Carvalho Chehab
272819667bcSMauro Carvalho Chehab            if self.verbose:
273819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
274819667bcSMauro Carvalho Chehab
275819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
276819667bcSMauro Carvalho Chehab
277819667bcSMauro Carvalho Chehab    def handle_html(self, css, output_dir):
278819667bcSMauro Carvalho Chehab        """
279819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
280819667bcSMauro Carvalho Chehab
281819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
282819667bcSMauro Carvalho Chehab        copied to the output _static directory
283819667bcSMauro Carvalho Chehab        """
284819667bcSMauro Carvalho Chehab
285819667bcSMauro Carvalho Chehab        if not css:
286819667bcSMauro Carvalho Chehab            return
287819667bcSMauro Carvalho Chehab
288819667bcSMauro Carvalho Chehab        css = os.path.expanduser(css)
289819667bcSMauro Carvalho Chehab        if not css.startswith("/"):
290819667bcSMauro Carvalho Chehab            css = os.path.join(self.srctree, css)
291819667bcSMauro Carvalho Chehab
292819667bcSMauro Carvalho Chehab        static_dir = os.path.join(output_dir, "_static")
293819667bcSMauro Carvalho Chehab        os.makedirs(static_dir, exist_ok=True)
294819667bcSMauro Carvalho Chehab
295819667bcSMauro Carvalho Chehab        try:
296819667bcSMauro Carvalho Chehab            shutil.copy2(css, static_dir)
297819667bcSMauro Carvalho Chehab        except (OSError, IOError) as e:
298819667bcSMauro Carvalho Chehab            print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
299819667bcSMauro Carvalho Chehab
30008e14bc1SMauro Carvalho Chehab    def build_pdf_file(self, latex_cmd, from_dir, path):
30108e14bc1SMauro Carvalho Chehab        """Builds a single pdf file using latex_cmd"""
30208e14bc1SMauro Carvalho Chehab        try:
30308e14bc1SMauro Carvalho Chehab            subprocess.run(latex_cmd + [path],
30408e14bc1SMauro Carvalho Chehab                            cwd=from_dir, check=True, env=self.env)
30508e14bc1SMauro Carvalho Chehab
30608e14bc1SMauro Carvalho Chehab            return True
30708e14bc1SMauro Carvalho Chehab        except subprocess.CalledProcessError:
30808e14bc1SMauro Carvalho Chehab            return False
30908e14bc1SMauro Carvalho Chehab
31008e14bc1SMauro Carvalho Chehab    def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
31108e14bc1SMauro Carvalho Chehab        """Build PDF files in parallel if possible"""
31208e14bc1SMauro Carvalho Chehab        builds = {}
31308e14bc1SMauro Carvalho Chehab        build_failed = False
31408e14bc1SMauro Carvalho Chehab        max_len = 0
31508e14bc1SMauro Carvalho Chehab        has_tex = False
31608e14bc1SMauro Carvalho Chehab
31708e14bc1SMauro Carvalho Chehab        #
31808e14bc1SMauro Carvalho Chehab        # LaTeX PDF error code is almost useless for us:
31908e14bc1SMauro Carvalho Chehab        # any warning makes it non-zero. For kernel doc builds it always return
32008e14bc1SMauro Carvalho Chehab        # non-zero even when build succeeds. So, let's do the best next thing:
32108e14bc1SMauro Carvalho Chehab        # Ignore build errors. At the end, check if all PDF files were built,
32208e14bc1SMauro Carvalho Chehab        # printing a summary with the built ones and returning 0 if all of
32308e14bc1SMauro Carvalho Chehab        # them were actually built.
32408e14bc1SMauro Carvalho Chehab        #
32508e14bc1SMauro Carvalho Chehab        with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
32608e14bc1SMauro Carvalho Chehab            jobs = {}
32708e14bc1SMauro Carvalho Chehab
32808e14bc1SMauro Carvalho Chehab            for from_dir, pdf_dir, entry in tex_files:
32908e14bc1SMauro Carvalho Chehab                name = entry.name
33008e14bc1SMauro Carvalho Chehab
33108e14bc1SMauro Carvalho Chehab                if not name.endswith(tex_suffix):
33208e14bc1SMauro Carvalho Chehab                    continue
33308e14bc1SMauro Carvalho Chehab
33408e14bc1SMauro Carvalho Chehab                name = name[:-len(tex_suffix)]
33508e14bc1SMauro Carvalho Chehab                has_tex = True
33608e14bc1SMauro Carvalho Chehab
33708e14bc1SMauro Carvalho Chehab                future = executor.submit(self.build_pdf_file, latex_cmd,
33808e14bc1SMauro Carvalho Chehab                                         from_dir, entry.path)
33908e14bc1SMauro Carvalho Chehab                jobs[future] = (from_dir, pdf_dir, name)
34008e14bc1SMauro Carvalho Chehab
34108e14bc1SMauro Carvalho Chehab            for future in futures.as_completed(jobs):
34208e14bc1SMauro Carvalho Chehab                from_dir, pdf_dir, name = jobs[future]
34308e14bc1SMauro Carvalho Chehab
34408e14bc1SMauro Carvalho Chehab                pdf_name = name + ".pdf"
34508e14bc1SMauro Carvalho Chehab                pdf_from = os.path.join(from_dir, pdf_name)
3460d9abc76SMauro Carvalho Chehab                pdf_to = os.path.join(pdf_dir, pdf_name)
3470d9abc76SMauro Carvalho Chehab                out_name = os.path.relpath(pdf_to, self.builddir)
3480d9abc76SMauro Carvalho Chehab                max_len = max(max_len, len(out_name))
34908e14bc1SMauro Carvalho Chehab
35008e14bc1SMauro Carvalho Chehab                try:
35108e14bc1SMauro Carvalho Chehab                    success = future.result()
35208e14bc1SMauro Carvalho Chehab
35308e14bc1SMauro Carvalho Chehab                    if success and os.path.exists(pdf_from):
35408e14bc1SMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
35508e14bc1SMauro Carvalho Chehab
35608e14bc1SMauro Carvalho Chehab                        #
35708e14bc1SMauro Carvalho Chehab                        # if verbose, get the name of built PDF file
35808e14bc1SMauro Carvalho Chehab                        #
35908e14bc1SMauro Carvalho Chehab                        if self.verbose:
3600d9abc76SMauro Carvalho Chehab                           builds[out_name] = "SUCCESS"
36108e14bc1SMauro Carvalho Chehab                    else:
3620d9abc76SMauro Carvalho Chehab                        builds[out_name] = "FAILED"
36308e14bc1SMauro Carvalho Chehab                        build_failed = True
36408e14bc1SMauro Carvalho Chehab                except futures.Error as e:
3650d9abc76SMauro Carvalho Chehab                    builds[out_name] = f"FAILED ({repr(e)})"
36608e14bc1SMauro Carvalho Chehab                    build_failed = True
36708e14bc1SMauro Carvalho Chehab
36808e14bc1SMauro Carvalho Chehab        #
36908e14bc1SMauro Carvalho Chehab        # Handle case where no .tex files were found
37008e14bc1SMauro Carvalho Chehab        #
37108e14bc1SMauro Carvalho Chehab        if not has_tex:
3720d9abc76SMauro Carvalho Chehab            out_name = "LaTeX files"
3730d9abc76SMauro Carvalho Chehab            max_len = max(max_len, len(out_name))
3740d9abc76SMauro Carvalho Chehab            builds[out_name] = "FAILED: no .tex files were generated"
37508e14bc1SMauro Carvalho Chehab            build_failed = True
37608e14bc1SMauro Carvalho Chehab
37708e14bc1SMauro Carvalho Chehab        return builds, build_failed, max_len
37808e14bc1SMauro Carvalho Chehab
379819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
380819667bcSMauro Carvalho Chehab        """
381819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
382819667bcSMauro Carvalho Chehab
383819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
384819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
385819667bcSMauro Carvalho Chehab        directory.
386819667bcSMauro Carvalho Chehab        """
387819667bcSMauro Carvalho Chehab        builds = {}
388819667bcSMauro Carvalho Chehab        max_len = 0
38908e14bc1SMauro Carvalho Chehab        tex_suffix = ".tex"
39008e14bc1SMauro Carvalho Chehab        tex_files = []
391819667bcSMauro Carvalho Chehab
392819667bcSMauro Carvalho Chehab        #
393819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
394819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
395819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
396819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
397819667bcSMauro Carvalho Chehab        # file with a deny list.
398819667bcSMauro Carvalho Chehab        #
399819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
400819667bcSMauro Carvalho Chehab        #
401819667bcSMauro Carvalho Chehab        if deny_vf:
402819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
403819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
404819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
405819667bcSMauro Carvalho Chehab
406819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
407819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
408819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
409819667bcSMauro Carvalho Chehab
410819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
411819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
412819667bcSMauro Carvalho Chehab            else:
413819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
414819667bcSMauro Carvalho Chehab
415819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
416819667bcSMauro Carvalho Chehab
41708e14bc1SMauro Carvalho Chehab            # Get a list of tex files to process
418819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
419819667bcSMauro Carvalho Chehab                for entry in it:
42008e14bc1SMauro Carvalho Chehab                    if entry.name.endswith(tex_suffix):
42108e14bc1SMauro Carvalho Chehab                        tex_files.append((from_dir, pdf_dir, entry))
422819667bcSMauro Carvalho Chehab
423819667bcSMauro Carvalho Chehab        #
42408e14bc1SMauro Carvalho Chehab        # When using make, this won't be used, as the number of jobs comes
42508e14bc1SMauro Carvalho Chehab        # from POSIX jobserver. So, this covers the case where build comes
42608e14bc1SMauro Carvalho Chehab        # from command line. On such case, serialize by default, except if
42708e14bc1SMauro Carvalho Chehab        # the user explicitly sets the number of jobs.
428819667bcSMauro Carvalho Chehab        #
42908e14bc1SMauro Carvalho Chehab        n_jobs = 1
43008e14bc1SMauro Carvalho Chehab
43108e14bc1SMauro Carvalho Chehab        # n_jobs is either an integer or "auto". Only use it if it is a number
43208e14bc1SMauro Carvalho Chehab        if self.n_jobs:
433819667bcSMauro Carvalho Chehab            try:
43408e14bc1SMauro Carvalho Chehab                n_jobs = int(self.n_jobs)
43508e14bc1SMauro Carvalho Chehab            except ValueError:
436819667bcSMauro Carvalho Chehab                pass
437819667bcSMauro Carvalho Chehab
43808e14bc1SMauro Carvalho Chehab        #
43908e14bc1SMauro Carvalho Chehab        # When using make, jobserver.claim is the number of jobs that were
44008e14bc1SMauro Carvalho Chehab        # used with "-j" and that aren't used by other make targets
44108e14bc1SMauro Carvalho Chehab        #
44208e14bc1SMauro Carvalho Chehab        with JobserverExec() as jobserver:
44308e14bc1SMauro Carvalho Chehab            n_jobs = 1
444819667bcSMauro Carvalho Chehab
44508e14bc1SMauro Carvalho Chehab            #
44608e14bc1SMauro Carvalho Chehab            # Handle the case when a parameter is passed via command line,
44708e14bc1SMauro Carvalho Chehab            # using it as default, if jobserver doesn't claim anything
44808e14bc1SMauro Carvalho Chehab            #
44908e14bc1SMauro Carvalho Chehab            if self.n_jobs:
45008e14bc1SMauro Carvalho Chehab                try:
45108e14bc1SMauro Carvalho Chehab                    n_jobs = int(self.n_jobs)
45208e14bc1SMauro Carvalho Chehab                except ValueError:
45308e14bc1SMauro Carvalho Chehab                    pass
454819667bcSMauro Carvalho Chehab
45508e14bc1SMauro Carvalho Chehab            if jobserver.claim:
45608e14bc1SMauro Carvalho Chehab                n_jobs = jobserver.claim
457819667bcSMauro Carvalho Chehab
45808e14bc1SMauro Carvalho Chehab            builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
45908e14bc1SMauro Carvalho Chehab                                                                    latex_cmd,
46008e14bc1SMauro Carvalho Chehab                                                                    tex_files,
46108e14bc1SMauro Carvalho Chehab                                                                    n_jobs)
462819667bcSMauro Carvalho Chehab
46308e14bc1SMauro Carvalho Chehab        #
46408e14bc1SMauro Carvalho Chehab        # In verbose mode, print a summary with the build results per file.
46508e14bc1SMauro Carvalho Chehab        # Otherwise, print a single line with all failures, if any.
46608e14bc1SMauro Carvalho Chehab        # On both cases, return code 1 indicates build failures,
46708e14bc1SMauro Carvalho Chehab        #
46808e14bc1SMauro Carvalho Chehab        if self.verbose:
469819667bcSMauro Carvalho Chehab            msg = "Summary"
470819667bcSMauro Carvalho Chehab            msg += "\n" + "=" * len(msg)
471819667bcSMauro Carvalho Chehab            print()
472819667bcSMauro Carvalho Chehab            print(msg)
473819667bcSMauro Carvalho Chehab
474819667bcSMauro Carvalho Chehab            for pdf_name, pdf_file in builds.items():
475819667bcSMauro Carvalho Chehab                print(f"{pdf_name:<{max_len}}: {pdf_file}")
476819667bcSMauro Carvalho Chehab
477819667bcSMauro Carvalho Chehab            print()
478819667bcSMauro Carvalho Chehab            if build_failed:
479819667bcSMauro Carvalho Chehab                msg = LatexFontChecker().check()
480819667bcSMauro Carvalho Chehab                if msg:
481819667bcSMauro Carvalho Chehab                    print(msg)
482819667bcSMauro Carvalho Chehab
48308e14bc1SMauro Carvalho Chehab                sys.exit("Error: not all PDF files were created.")
48408e14bc1SMauro Carvalho Chehab
48508e14bc1SMauro Carvalho Chehab        elif build_failed:
48608e14bc1SMauro Carvalho Chehab            n_failures = len(builds)
48708e14bc1SMauro Carvalho Chehab            failures = ", ".join(builds.keys())
48808e14bc1SMauro Carvalho Chehab
48908e14bc1SMauro Carvalho Chehab            msg = LatexFontChecker().check()
49008e14bc1SMauro Carvalho Chehab            if msg:
49108e14bc1SMauro Carvalho Chehab                print(msg)
49208e14bc1SMauro Carvalho Chehab
49308e14bc1SMauro Carvalho Chehab            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
494819667bcSMauro Carvalho Chehab
495819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
496819667bcSMauro Carvalho Chehab        """
497819667bcSMauro Carvalho Chehab        Extra steps for Info output.
498819667bcSMauro Carvalho Chehab
499819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
500819667bcSMauro Carvalho Chehab        texinfo directory.
501819667bcSMauro Carvalho Chehab        """
502819667bcSMauro Carvalho Chehab
503819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
504819667bcSMauro Carvalho Chehab            try:
505819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
506819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
507819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
508819667bcSMauro Carvalho Chehab
509*7e8a8143SMauro Carvalho Chehab    def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
510*7e8a8143SMauro Carvalho Chehab        """
511*7e8a8143SMauro Carvalho Chehab        Create man pages from kernel-doc output
512*7e8a8143SMauro Carvalho Chehab        """
513*7e8a8143SMauro Carvalho Chehab
514*7e8a8143SMauro Carvalho Chehab        re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
515*7e8a8143SMauro Carvalho Chehab        re_man = re.compile(r'^\.TH "[^"]*" (\d+) "([^"]*)"')
516*7e8a8143SMauro Carvalho Chehab
517*7e8a8143SMauro Carvalho Chehab        if docs_dir == src_dir:
518*7e8a8143SMauro Carvalho Chehab            #
519*7e8a8143SMauro Carvalho Chehab            # Pick the entire set of kernel-doc markups from the entire tree
520*7e8a8143SMauro Carvalho Chehab            #
521*7e8a8143SMauro Carvalho Chehab            kdoc_files = set([self.srctree])
522*7e8a8143SMauro Carvalho Chehab        else:
523*7e8a8143SMauro Carvalho Chehab            kdoc_files = set()
524*7e8a8143SMauro Carvalho Chehab
525*7e8a8143SMauro Carvalho Chehab            for fname in glob(os.path.join(src_dir, "**"), recursive=True):
526*7e8a8143SMauro Carvalho Chehab                if os.path.isfile(fname) and fname.endswith(".rst"):
527*7e8a8143SMauro Carvalho Chehab                    with open(fname, "r", encoding="utf-8") as in_fp:
528*7e8a8143SMauro Carvalho Chehab                        data = in_fp.read()
529*7e8a8143SMauro Carvalho Chehab
530*7e8a8143SMauro Carvalho Chehab                    for line in data.split("\n"):
531*7e8a8143SMauro Carvalho Chehab                        match = re_kernel_doc.match(line)
532*7e8a8143SMauro Carvalho Chehab                        if match:
533*7e8a8143SMauro Carvalho Chehab                            if os.path.isfile(match.group(1)):
534*7e8a8143SMauro Carvalho Chehab                                kdoc_files.add(match.group(1))
535*7e8a8143SMauro Carvalho Chehab
536*7e8a8143SMauro Carvalho Chehab        if not kdoc_files:
537*7e8a8143SMauro Carvalho Chehab                sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
538*7e8a8143SMauro Carvalho Chehab
539*7e8a8143SMauro Carvalho Chehab        cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
540*7e8a8143SMauro Carvalho Chehab        try:
541*7e8a8143SMauro Carvalho Chehab            if self.verbose:
542*7e8a8143SMauro Carvalho Chehab                print(" ".join(cmd))
543*7e8a8143SMauro Carvalho Chehab
544*7e8a8143SMauro Carvalho Chehab            result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
545*7e8a8143SMauro Carvalho Chehab
546*7e8a8143SMauro Carvalho Chehab            if result.returncode:
547*7e8a8143SMauro Carvalho Chehab                print(f"Warning: kernel-doc returned {result.returncode} warnings")
548*7e8a8143SMauro Carvalho Chehab
549*7e8a8143SMauro Carvalho Chehab        except (OSError, ValueError, subprocess.SubprocessError) as e:
550*7e8a8143SMauro Carvalho Chehab            sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
551*7e8a8143SMauro Carvalho Chehab
552*7e8a8143SMauro Carvalho Chehab        fp = None
553*7e8a8143SMauro Carvalho Chehab        try:
554*7e8a8143SMauro Carvalho Chehab            for line in result.stdout.split("\n"):
555*7e8a8143SMauro Carvalho Chehab                match = re_man.match(line)
556*7e8a8143SMauro Carvalho Chehab                if not match:
557*7e8a8143SMauro Carvalho Chehab                    if fp:
558*7e8a8143SMauro Carvalho Chehab                        fp.write(line + '\n')
559*7e8a8143SMauro Carvalho Chehab                    continue
560*7e8a8143SMauro Carvalho Chehab
561*7e8a8143SMauro Carvalho Chehab                if fp:
562*7e8a8143SMauro Carvalho Chehab                    fp.close()
563*7e8a8143SMauro Carvalho Chehab
564*7e8a8143SMauro Carvalho Chehab                fname = f"{output_dir}/{match.group(2)}.{match.group(1)}"
565*7e8a8143SMauro Carvalho Chehab
566*7e8a8143SMauro Carvalho Chehab                if self.verbose:
567*7e8a8143SMauro Carvalho Chehab                    print(f"Creating {fname}")
568*7e8a8143SMauro Carvalho Chehab                fp = open(fname, "w", encoding="utf-8")
569*7e8a8143SMauro Carvalho Chehab                fp.write(line + '\n')
570*7e8a8143SMauro Carvalho Chehab        finally:
571*7e8a8143SMauro Carvalho Chehab            if fp:
572*7e8a8143SMauro Carvalho Chehab                fp.close()
573*7e8a8143SMauro Carvalho Chehab
574819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
575819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
576819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
577819667bcSMauro Carvalho Chehab
578819667bcSMauro Carvalho Chehab    def build(self, target, sphinxdirs=None, conf="conf.py",
579819667bcSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None):
580819667bcSMauro Carvalho Chehab        """
581819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
582819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
583819667bcSMauro Carvalho Chehab        """
584819667bcSMauro Carvalho Chehab
585819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
586819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
587819667bcSMauro Carvalho Chehab
588819667bcSMauro Carvalho Chehab        #
589819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
590819667bcSMauro Carvalho Chehab        #
591819667bcSMauro Carvalho Chehab        if target == "cleandocs":
592819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
593819667bcSMauro Carvalho Chehab            return
594819667bcSMauro Carvalho Chehab
595819667bcSMauro Carvalho Chehab        if theme:
596819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
597819667bcSMauro Carvalho Chehab
598819667bcSMauro Carvalho Chehab        #
599819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
600819667bcSMauro Carvalho Chehab        #
601819667bcSMauro Carvalho Chehab        sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
602*7e8a8143SMauro Carvalho Chehab        if not sphinxbuild and target != "mandocs":
603819667bcSMauro Carvalho Chehab            sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
604819667bcSMauro Carvalho Chehab
605819667bcSMauro Carvalho Chehab        if builder == "latex":
606819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
607819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
608819667bcSMauro Carvalho Chehab
609819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
610819667bcSMauro Carvalho Chehab
611819667bcSMauro Carvalho Chehab        #
612819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
613819667bcSMauro Carvalho Chehab        #
614819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
615819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
616819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
617819667bcSMauro Carvalho Chehab
618819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
619819667bcSMauro Carvalho Chehab
620819667bcSMauro Carvalho Chehab        if builder == "latex":
621819667bcSMauro Carvalho Chehab            if not paper:
622819667bcSMauro Carvalho Chehab                paper = PAPER[1]
623819667bcSMauro Carvalho Chehab
624819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
625819667bcSMauro Carvalho Chehab
626819667bcSMauro Carvalho Chehab        if self.config_rust:
627819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
628819667bcSMauro Carvalho Chehab
629819667bcSMauro Carvalho Chehab        if conf:
630819667bcSMauro Carvalho Chehab            self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True)
631819667bcSMauro Carvalho Chehab
632819667bcSMauro Carvalho Chehab        if not sphinxdirs:
633819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
634819667bcSMauro Carvalho Chehab
635819667bcSMauro Carvalho Chehab        #
63682c294d4SMauro Carvalho Chehab        # The sphinx-build tool has a bug: internally, it tries to set
63782c294d4SMauro Carvalho Chehab        # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
63882c294d4SMauro Carvalho Chehab        # crash if language is not set. Detect and fix it.
63982c294d4SMauro Carvalho Chehab        #
64082c294d4SMauro Carvalho Chehab        try:
64182c294d4SMauro Carvalho Chehab            locale.setlocale(locale.LC_ALL, '')
64282c294d4SMauro Carvalho Chehab        except locale.Error:
64382c294d4SMauro Carvalho Chehab            self.env["LC_ALL"] = "C"
64482c294d4SMauro Carvalho Chehab
64582c294d4SMauro Carvalho Chehab        #
646819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
647819667bcSMauro Carvalho Chehab        #
648819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
649819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
650819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
651819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
652819667bcSMauro Carvalho Chehab            else:
653819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
654819667bcSMauro Carvalho Chehab
655819667bcSMauro Carvalho Chehab        #
656819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
657819667bcSMauro Carvalho Chehab        #
658819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
659819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
660819667bcSMauro Carvalho Chehab        # the beginning.
661819667bcSMauro Carvalho Chehab        #
662819667bcSMauro Carvalho Chehab        output_dirs = []
663819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
664819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
665819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
666819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
667819667bcSMauro Carvalho Chehab
668819667bcSMauro Carvalho Chehab            #
669819667bcSMauro Carvalho Chehab            # Make directory names canonical
670819667bcSMauro Carvalho Chehab            #
671819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
672819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
673819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
674819667bcSMauro Carvalho Chehab
675819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
676819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
677819667bcSMauro Carvalho Chehab
678819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
679819667bcSMauro Carvalho Chehab
680819667bcSMauro Carvalho Chehab            build_args = args + [
681819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
682819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_bin={kerneldoc}",
683819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
684819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
685819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
686819667bcSMauro Carvalho Chehab                src_dir,
687819667bcSMauro Carvalho Chehab                output_dir,
688819667bcSMauro Carvalho Chehab            ]
689819667bcSMauro Carvalho Chehab
690*7e8a8143SMauro Carvalho Chehab            if target == "mandocs":
691*7e8a8143SMauro Carvalho Chehab                self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
692*7e8a8143SMauro Carvalho Chehab            else:
693819667bcSMauro Carvalho Chehab                try:
694819667bcSMauro Carvalho Chehab                    self.run_sphinx(sphinxbuild, build_args, env=self.env)
695819667bcSMauro Carvalho Chehab                except (OSError, ValueError, subprocess.SubprocessError) as e:
696819667bcSMauro Carvalho Chehab                    sys.exit(f"Build failed: {repr(e)}")
697819667bcSMauro Carvalho Chehab
698819667bcSMauro Carvalho Chehab            #
699819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
700819667bcSMauro Carvalho Chehab            #
701819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
702819667bcSMauro Carvalho Chehab                self.handle_html(css, output_dir)
703819667bcSMauro Carvalho Chehab
704819667bcSMauro Carvalho Chehab        #
705819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
706819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
707819667bcSMauro Carvalho Chehab        #
708819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
709819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
710819667bcSMauro Carvalho Chehab        elif target == "infodocs":
711819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
712819667bcSMauro Carvalho Chehab
713819667bcSMauro Carvalho Chehabdef jobs_type(value):
714819667bcSMauro Carvalho Chehab    """
715819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
716819667bcSMauro Carvalho Chehab    equal or bigger than one.
717819667bcSMauro Carvalho Chehab    """
718819667bcSMauro Carvalho Chehab    if value is None:
719819667bcSMauro Carvalho Chehab        return None
720819667bcSMauro Carvalho Chehab
721819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
722819667bcSMauro Carvalho Chehab        return value.lower()
723819667bcSMauro Carvalho Chehab
724819667bcSMauro Carvalho Chehab    try:
725819667bcSMauro Carvalho Chehab        if int(value) >= 1:
726819667bcSMauro Carvalho Chehab            return value
727819667bcSMauro Carvalho Chehab
728819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
729819667bcSMauro Carvalho Chehab    except ValueError:
730819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
731819667bcSMauro Carvalho Chehab
732819667bcSMauro Carvalho Chehabdef main():
733819667bcSMauro Carvalho Chehab    """
734819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
735819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
736819667bcSMauro Carvalho Chehab    specified at os.environ.
737819667bcSMauro Carvalho Chehab    """
738819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
739819667bcSMauro Carvalho Chehab
740819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
741819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
742819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
743819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
744819667bcSMauro Carvalho Chehab    parser.add_argument("--conf", default="conf.py",
745819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
746819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
747819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
748819667bcSMauro Carvalho Chehab
749819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
750819667bcSMauro Carvalho Chehab
751819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
752819667bcSMauro Carvalho Chehab
753819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
754819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
755819667bcSMauro Carvalho Chehab
756819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
757819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
758819667bcSMauro Carvalho Chehab
759819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
760819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
761819667bcSMauro Carvalho Chehab
762819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
763819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
764819667bcSMauro Carvalho Chehab
7652f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
7662f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
7672f99b85eSMauro Carvalho Chehab
768819667bcSMauro Carvalho Chehab    args = parser.parse_args()
769819667bcSMauro Carvalho Chehab
770819667bcSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION)
771819667bcSMauro Carvalho Chehab
772819667bcSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir,
7732f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
7742f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
775819667bcSMauro Carvalho Chehab
776819667bcSMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf,
777819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
778819667bcSMauro Carvalho Chehab                  deny_vf=args.deny_vf)
779819667bcSMauro Carvalho Chehab
780819667bcSMauro Carvalho Chehabif __name__ == "__main__":
781819667bcSMauro Carvalho Chehab    main()
782