xref: /linux/tools/docs/sphinx-build-wrapper (revision 5094f7d5ff2318edfe6f2a9632b31f0ddefd6ee4)
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 Chehab
60778b8ebeSJonathan CorbetLIB_DIR = "../lib/python"
61819667bcSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__))
62819667bcSMauro Carvalho Chehab
63819667bcSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
64819667bcSMauro Carvalho Chehab
65992a9df4SJonathan Corbetfrom kdoc.python_version import PythonVersion
66992a9df4SJonathan Corbetfrom kdoc.latex_fonts import LatexFontChecker
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#
7242180adaSMauro Carvalho ChehabVENV_DEFAULT = "sphinx_latest"
73819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version
74819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"]
75819667bcSMauro Carvalho Chehab
76819667bcSMauro Carvalho ChehabTARGETS = {
77819667bcSMauro Carvalho Chehab    "cleandocs":     { "builder": "clean" },
78819667bcSMauro Carvalho Chehab    "linkcheckdocs": { "builder": "linkcheck" },
79819667bcSMauro Carvalho Chehab    "htmldocs":      { "builder": "html" },
80819667bcSMauro Carvalho Chehab    "epubdocs":      { "builder": "epub",    "out_dir": "epub" },
81819667bcSMauro Carvalho Chehab    "texinfodocs":   { "builder": "texinfo", "out_dir": "texinfo" },
82819667bcSMauro Carvalho Chehab    "infodocs":      { "builder": "texinfo", "out_dir": "texinfo" },
837e8a8143SMauro Carvalho Chehab    "mandocs":       { "builder": "man",     "out_dir": "man" },
84819667bcSMauro Carvalho Chehab    "latexdocs":     { "builder": "latex",   "out_dir": "latex" },
85819667bcSMauro Carvalho Chehab    "pdfdocs":       { "builder": "latex",   "out_dir": "latex" },
86819667bcSMauro Carvalho Chehab    "xmldocs":       { "builder": "xml",     "out_dir": "xml" },
87819667bcSMauro Carvalho Chehab}
88819667bcSMauro Carvalho Chehab
89819667bcSMauro Carvalho Chehab
90819667bcSMauro Carvalho Chehab#
91819667bcSMauro Carvalho Chehab# SphinxBuilder class
92819667bcSMauro Carvalho Chehab#
93819667bcSMauro Carvalho Chehab
94819667bcSMauro Carvalho Chehabclass SphinxBuilder:
95819667bcSMauro Carvalho Chehab    """
96819667bcSMauro Carvalho Chehab    Handles a sphinx-build target, adding needed arguments to build
97819667bcSMauro Carvalho Chehab    with the Kernel.
98819667bcSMauro Carvalho Chehab    """
99819667bcSMauro Carvalho Chehab
100819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
101819667bcSMauro Carvalho Chehab        """
102819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
103819667bcSMauro Carvalho Chehab
104819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
105819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
106819667bcSMauro Carvalho Chehab        """
107819667bcSMauro Carvalho Chehab
108819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
109819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
110819667bcSMauro Carvalho Chehab            if use_cwd:
111819667bcSMauro Carvalho Chehab                base = os.getcwd()
112819667bcSMauro Carvalho Chehab            else:
113819667bcSMauro Carvalho Chehab                base = self.srctree
114819667bcSMauro Carvalho Chehab
115819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
116819667bcSMauro Carvalho Chehab
117819667bcSMauro Carvalho Chehab        if abs_path:
118819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
119819667bcSMauro Carvalho Chehab
120819667bcSMauro Carvalho Chehab        return path
121819667bcSMauro Carvalho Chehab
122464257baSMauro Carvalho Chehab    def check_rust(self):
123464257baSMauro Carvalho Chehab        """
124464257baSMauro Carvalho Chehab        Checks if Rust is enabled
125464257baSMauro Carvalho Chehab        """
126464257baSMauro Carvalho Chehab        self.rustdoc = False
127464257baSMauro Carvalho Chehab
128464257baSMauro Carvalho Chehab        config = os.path.join(self.srctree, ".config")
129464257baSMauro Carvalho Chehab
130464257baSMauro Carvalho Chehab        if not os.path.isfile(config):
131464257baSMauro Carvalho Chehab            return
132464257baSMauro Carvalho Chehab
133464257baSMauro Carvalho Chehab        re_rust = re.compile(r"CONFIG_RUST=(m|y)")
134464257baSMauro Carvalho Chehab
135464257baSMauro Carvalho Chehab        try:
136464257baSMauro Carvalho Chehab            with open(config, "r", encoding="utf-8") as fp:
137464257baSMauro Carvalho Chehab                for line in fp:
138464257baSMauro Carvalho Chehab                    if re_rust.match(line):
139464257baSMauro Carvalho Chehab                        self.rustdoc = True
140464257baSMauro Carvalho Chehab                        return
141464257baSMauro Carvalho Chehab
142464257baSMauro Carvalho Chehab        except OSError as e:
143464257baSMauro Carvalho Chehab            print(f"Failed to open {config}", file=sys.stderr)
144464257baSMauro Carvalho Chehab
145819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
146819667bcSMauro Carvalho Chehab        """
147819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
148819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
149819667bcSMauro Carvalho Chehab
150819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
151819667bcSMauro Carvalho Chehab
152819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
153819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
154819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
155819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
156819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
157819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
158819667bcSMauro Carvalho Chehab        """
159819667bcSMauro Carvalho Chehab
160819667bcSMauro Carvalho Chehab        #
161819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
162819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
163819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
164819667bcSMauro Carvalho Chehab        #
165819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
166819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
167e123e00aSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', action='store_true')
168819667bcSMauro Carvalho Chehab
169819667bcSMauro Carvalho Chehab        #
170819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
171819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
172819667bcSMauro Carvalho Chehab        #
173819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
174819667bcSMauro Carvalho Chehab
175819667bcSMauro Carvalho Chehab        #
176819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
177819667bcSMauro Carvalho Chehab        #
178819667bcSMauro Carvalho Chehab
179819667bcSMauro Carvalho Chehab        verbose = self.verbose
180819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
181819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
182819667bcSMauro Carvalho Chehab            verbose = False
183819667bcSMauro Carvalho Chehab
184819667bcSMauro Carvalho Chehab        #
185819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
186819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
187819667bcSMauro Carvalho Chehab        #
188819667bcSMauro Carvalho Chehab        if n_jobs:
189819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
190819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
191819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
192819667bcSMauro Carvalho Chehab        else:
193819667bcSMauro Carvalho Chehab            self.n_jobs = None
194819667bcSMauro Carvalho Chehab
195819667bcSMauro Carvalho Chehab        if not verbose:
196819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
197819667bcSMauro Carvalho Chehab
19842180adaSMauro Carvalho Chehab    def __init__(self, builddir, venv=None, verbose=False, n_jobs=None,
19942180adaSMauro Carvalho Chehab                 interactive=None):
200819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
20142180adaSMauro Carvalho Chehab        self.venv = venv
202819667bcSMauro Carvalho Chehab        self.verbose = None
203819667bcSMauro Carvalho Chehab
204819667bcSMauro Carvalho Chehab        #
205819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
206819667bcSMauro Carvalho Chehab        #
207819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
208819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
209819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
2102f99b85eSMauro Carvalho Chehab
21135b9d338SMauro Carvalho Chehab        #
21235b9d338SMauro Carvalho Chehab        # Kernel main Makefile defines a PYTHON3 variable whose default is
21335b9d338SMauro Carvalho Chehab        # "python3". When set to a different value, it allows running a
21435b9d338SMauro Carvalho Chehab        # diferent version than the default official python3 package.
21535b9d338SMauro Carvalho Chehab        # Several distros package python3xx-sphinx packages with newer
21635b9d338SMauro Carvalho Chehab        # versions of Python and sphinx-build.
21735b9d338SMauro Carvalho Chehab        #
21835b9d338SMauro Carvalho Chehab        # Honor such variable different than default
21935b9d338SMauro Carvalho Chehab        #
22035b9d338SMauro Carvalho Chehab        self.python = os.environ.get("PYTHON3")
22135b9d338SMauro Carvalho Chehab        if self.python == "python3":
22235b9d338SMauro Carvalho Chehab            self.python = None
22335b9d338SMauro Carvalho Chehab
2242f99b85eSMauro Carvalho Chehab        if not interactive:
225819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
2262f99b85eSMauro Carvalho Chehab        else:
2272f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
228819667bcSMauro Carvalho Chehab
229819667bcSMauro Carvalho Chehab        if not verbose:
230819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
231819667bcSMauro Carvalho Chehab
232819667bcSMauro Carvalho Chehab        if verbose is not None:
233819667bcSMauro Carvalho Chehab            self.verbose = verbose
234819667bcSMauro Carvalho Chehab
235819667bcSMauro Carvalho Chehab        #
236819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
237819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
238819667bcSMauro Carvalho Chehab        #
239819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
240819667bcSMauro Carvalho Chehab        if not self.srctree:
241819667bcSMauro Carvalho Chehab            self.srctree = "."
242819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
243819667bcSMauro Carvalho Chehab
244819667bcSMauro Carvalho Chehab        #
245819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
246819667bcSMauro Carvalho Chehab        #
247819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
248819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
249eba6ffd1SJonathan Corbet                                                      "tools/docs/kernel-doc"))
250819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
251819667bcSMauro Carvalho Chehab
252819667bcSMauro Carvalho Chehab        #
253819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
254819667bcSMauro Carvalho Chehab        #
255819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
256819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
257819667bcSMauro Carvalho Chehab
258819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
259819667bcSMauro Carvalho Chehab
260819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
261819667bcSMauro Carvalho Chehab
262464257baSMauro Carvalho Chehab        self.check_rust()
263464257baSMauro Carvalho Chehab
26442180adaSMauro Carvalho Chehab        #
26542180adaSMauro Carvalho Chehab        # If venv command line argument is specified, run Sphinx from venv
26642180adaSMauro Carvalho Chehab        #
26742180adaSMauro Carvalho Chehab        if venv:
26842180adaSMauro Carvalho Chehab            bin_dir = os.path.join(venv, "bin")
26942180adaSMauro Carvalho Chehab            if not os.path.isfile(os.path.join(bin_dir, "activate")):
27042180adaSMauro Carvalho Chehab                sys.exit(f"Venv {venv} not found.")
27142180adaSMauro Carvalho Chehab
27242180adaSMauro Carvalho Chehab            # "activate" virtual env
27342180adaSMauro Carvalho Chehab            self.env["PATH"] = bin_dir + ":" + self.env["PATH"]
27442180adaSMauro Carvalho Chehab            self.env["VIRTUAL_ENV"] = venv
27542180adaSMauro Carvalho Chehab            if "PYTHONHOME" in self.env:
27642180adaSMauro Carvalho Chehab                del self.env["PYTHONHOME"]
27742180adaSMauro Carvalho Chehab            print(f"Setting venv to {venv}")
27842180adaSMauro Carvalho Chehab
279819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
280819667bcSMauro Carvalho Chehab        """
281819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
282819667bcSMauro Carvalho Chehab
283819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
284819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
285819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
286819667bcSMauro Carvalho Chehab        jobs.
287819667bcSMauro Carvalho Chehab
288819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
289819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
290819667bcSMauro Carvalho Chehab
291819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
292819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
293819667bcSMauro Carvalho Chehab        """
294819667bcSMauro Carvalho Chehab
295819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
296819667bcSMauro Carvalho Chehab            if jobserver.claim:
297819667bcSMauro Carvalho Chehab                #
298819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
299819667bcSMauro Carvalho Chehab                #
300819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
301819667bcSMauro Carvalho Chehab            else:
302819667bcSMauro Carvalho Chehab                #
303819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
304819667bcSMauro Carvalho Chehab                #
305819667bcSMauro Carvalho Chehab                n_jobs = "auto"
306819667bcSMauro Carvalho Chehab
307819667bcSMauro Carvalho Chehab            #
308819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
309819667bcSMauro Carvalho Chehab            #
310819667bcSMauro Carvalho Chehab            if self.n_jobs:
311819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
312819667bcSMauro Carvalho Chehab
31335b9d338SMauro Carvalho Chehab            #
31435b9d338SMauro Carvalho Chehab            # We can't simply call python3 sphinx-build, as OpenSUSE
31535b9d338SMauro Carvalho Chehab            # Tumbleweed uses an ELF binary file (/usr/bin/alts) to switch
31635b9d338SMauro Carvalho Chehab            # between different versions of sphinx-build. So, only call it
31735b9d338SMauro Carvalho Chehab            # prepending "python3.xx" when PYTHON3 variable is not default.
31835b9d338SMauro Carvalho Chehab            #
31935b9d338SMauro Carvalho Chehab            if self.python:
32035b9d338SMauro Carvalho Chehab                cmd = [self.python]
32142180adaSMauro Carvalho Chehab            else:
32235b9d338SMauro Carvalho Chehab                cmd = []
32342180adaSMauro Carvalho Chehab
32442180adaSMauro Carvalho Chehab            cmd += [sphinx_build]
325819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
326819667bcSMauro Carvalho Chehab            cmd += build_args
3271f6e3f21SAkira Yokosawa            cmd += self.sphinxopts
328819667bcSMauro Carvalho Chehab
329819667bcSMauro Carvalho Chehab            if self.verbose:
330819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
331819667bcSMauro Carvalho Chehab
332819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
333819667bcSMauro Carvalho Chehab
334464257baSMauro Carvalho Chehab    def handle_html(self, css, output_dir):
335819667bcSMauro Carvalho Chehab        """
336819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
337819667bcSMauro Carvalho Chehab
338819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
339819667bcSMauro Carvalho Chehab        copied to the output _static directory
340819667bcSMauro Carvalho Chehab        """
341819667bcSMauro Carvalho Chehab
3422118ba7dSMauro Carvalho Chehab        if css:
343819667bcSMauro Carvalho Chehab            css = os.path.expanduser(css)
344819667bcSMauro Carvalho Chehab            if not css.startswith("/"):
345819667bcSMauro Carvalho Chehab                css = os.path.join(self.srctree, css)
346819667bcSMauro Carvalho Chehab
347819667bcSMauro Carvalho Chehab            static_dir = os.path.join(output_dir, "_static")
348819667bcSMauro Carvalho Chehab            os.makedirs(static_dir, exist_ok=True)
349819667bcSMauro Carvalho Chehab
350819667bcSMauro Carvalho Chehab            try:
351819667bcSMauro Carvalho Chehab                shutil.copy2(css, static_dir)
352819667bcSMauro Carvalho Chehab            except (OSError, IOError) as e:
353819667bcSMauro Carvalho Chehab                print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
354819667bcSMauro Carvalho Chehab
35508e14bc1SMauro Carvalho Chehab    def build_pdf_file(self, latex_cmd, from_dir, path):
35608e14bc1SMauro Carvalho Chehab        """Builds a single pdf file using latex_cmd"""
35708e14bc1SMauro Carvalho Chehab        try:
35808e14bc1SMauro Carvalho Chehab            subprocess.run(latex_cmd + [path],
35908e14bc1SMauro Carvalho Chehab                            cwd=from_dir, check=True, env=self.env)
36008e14bc1SMauro Carvalho Chehab
36108e14bc1SMauro Carvalho Chehab            return True
36208e14bc1SMauro Carvalho Chehab        except subprocess.CalledProcessError:
36308e14bc1SMauro Carvalho Chehab            return False
36408e14bc1SMauro Carvalho Chehab
36508e14bc1SMauro Carvalho Chehab    def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
36608e14bc1SMauro Carvalho Chehab        """Build PDF files in parallel if possible"""
36708e14bc1SMauro Carvalho Chehab        builds = {}
36808e14bc1SMauro Carvalho Chehab        build_failed = False
36908e14bc1SMauro Carvalho Chehab        max_len = 0
37008e14bc1SMauro Carvalho Chehab        has_tex = False
37108e14bc1SMauro Carvalho Chehab
37208e14bc1SMauro Carvalho Chehab        #
37308e14bc1SMauro Carvalho Chehab        # LaTeX PDF error code is almost useless for us:
37408e14bc1SMauro Carvalho Chehab        # any warning makes it non-zero. For kernel doc builds it always return
37508e14bc1SMauro Carvalho Chehab        # non-zero even when build succeeds. So, let's do the best next thing:
37608e14bc1SMauro Carvalho Chehab        # Ignore build errors. At the end, check if all PDF files were built,
37708e14bc1SMauro Carvalho Chehab        # printing a summary with the built ones and returning 0 if all of
37808e14bc1SMauro Carvalho Chehab        # them were actually built.
37908e14bc1SMauro Carvalho Chehab        #
38008e14bc1SMauro Carvalho Chehab        with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
38108e14bc1SMauro Carvalho Chehab            jobs = {}
38208e14bc1SMauro Carvalho Chehab
38308e14bc1SMauro Carvalho Chehab            for from_dir, pdf_dir, entry in tex_files:
38408e14bc1SMauro Carvalho Chehab                name = entry.name
38508e14bc1SMauro Carvalho Chehab
38608e14bc1SMauro Carvalho Chehab                if not name.endswith(tex_suffix):
38708e14bc1SMauro Carvalho Chehab                    continue
38808e14bc1SMauro Carvalho Chehab
38908e14bc1SMauro Carvalho Chehab                name = name[:-len(tex_suffix)]
39008e14bc1SMauro Carvalho Chehab                has_tex = True
39108e14bc1SMauro Carvalho Chehab
39208e14bc1SMauro Carvalho Chehab                future = executor.submit(self.build_pdf_file, latex_cmd,
39308e14bc1SMauro Carvalho Chehab                                         from_dir, entry.path)
39408e14bc1SMauro Carvalho Chehab                jobs[future] = (from_dir, pdf_dir, name)
39508e14bc1SMauro Carvalho Chehab
39608e14bc1SMauro Carvalho Chehab            for future in futures.as_completed(jobs):
39708e14bc1SMauro Carvalho Chehab                from_dir, pdf_dir, name = jobs[future]
39808e14bc1SMauro Carvalho Chehab
39908e14bc1SMauro Carvalho Chehab                pdf_name = name + ".pdf"
40008e14bc1SMauro Carvalho Chehab                pdf_from = os.path.join(from_dir, pdf_name)
4010d9abc76SMauro Carvalho Chehab                pdf_to = os.path.join(pdf_dir, pdf_name)
4020d9abc76SMauro Carvalho Chehab                out_name = os.path.relpath(pdf_to, self.builddir)
4030d9abc76SMauro Carvalho Chehab                max_len = max(max_len, len(out_name))
40408e14bc1SMauro Carvalho Chehab
40508e14bc1SMauro Carvalho Chehab                try:
40608e14bc1SMauro Carvalho Chehab                    success = future.result()
40708e14bc1SMauro Carvalho Chehab
40808e14bc1SMauro Carvalho Chehab                    if success and os.path.exists(pdf_from):
40908e14bc1SMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
41008e14bc1SMauro Carvalho Chehab
41108e14bc1SMauro Carvalho Chehab                        #
41208e14bc1SMauro Carvalho Chehab                        # if verbose, get the name of built PDF file
41308e14bc1SMauro Carvalho Chehab                        #
41408e14bc1SMauro Carvalho Chehab                        if self.verbose:
4150d9abc76SMauro Carvalho Chehab                           builds[out_name] = "SUCCESS"
41608e14bc1SMauro Carvalho Chehab                    else:
4170d9abc76SMauro Carvalho Chehab                        builds[out_name] = "FAILED"
41808e14bc1SMauro Carvalho Chehab                        build_failed = True
41908e14bc1SMauro Carvalho Chehab                except futures.Error as e:
4200d9abc76SMauro Carvalho Chehab                    builds[out_name] = f"FAILED ({repr(e)})"
42108e14bc1SMauro Carvalho Chehab                    build_failed = True
42208e14bc1SMauro Carvalho Chehab
42308e14bc1SMauro Carvalho Chehab        #
42408e14bc1SMauro Carvalho Chehab        # Handle case where no .tex files were found
42508e14bc1SMauro Carvalho Chehab        #
42608e14bc1SMauro Carvalho Chehab        if not has_tex:
4270d9abc76SMauro Carvalho Chehab            out_name = "LaTeX files"
4280d9abc76SMauro Carvalho Chehab            max_len = max(max_len, len(out_name))
4290d9abc76SMauro Carvalho Chehab            builds[out_name] = "FAILED: no .tex files were generated"
43008e14bc1SMauro Carvalho Chehab            build_failed = True
43108e14bc1SMauro Carvalho Chehab
43208e14bc1SMauro Carvalho Chehab        return builds, build_failed, max_len
43308e14bc1SMauro Carvalho Chehab
434819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
435819667bcSMauro Carvalho Chehab        """
436819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
437819667bcSMauro Carvalho Chehab
438819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
439819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
440819667bcSMauro Carvalho Chehab        directory.
441819667bcSMauro Carvalho Chehab        """
442819667bcSMauro Carvalho Chehab        builds = {}
443819667bcSMauro Carvalho Chehab        max_len = 0
44408e14bc1SMauro Carvalho Chehab        tex_suffix = ".tex"
44508e14bc1SMauro Carvalho Chehab        tex_files = []
446819667bcSMauro Carvalho Chehab
447819667bcSMauro Carvalho Chehab        #
448819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
449819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
450819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
451819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
452819667bcSMauro Carvalho Chehab        # file with a deny list.
453819667bcSMauro Carvalho Chehab        #
454819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
455819667bcSMauro Carvalho Chehab        #
456819667bcSMauro Carvalho Chehab        if deny_vf:
457819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
458819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
459819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
460819667bcSMauro Carvalho Chehab
461819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
462819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
463819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
464819667bcSMauro Carvalho Chehab
465819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
466819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
467819667bcSMauro Carvalho Chehab            else:
468819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
469819667bcSMauro Carvalho Chehab
470819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
471819667bcSMauro Carvalho Chehab
47208e14bc1SMauro Carvalho Chehab            # Get a list of tex files to process
473819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
474819667bcSMauro Carvalho Chehab                for entry in it:
47508e14bc1SMauro Carvalho Chehab                    if entry.name.endswith(tex_suffix):
47608e14bc1SMauro Carvalho Chehab                        tex_files.append((from_dir, pdf_dir, entry))
477819667bcSMauro Carvalho Chehab
478819667bcSMauro Carvalho Chehab        #
47908e14bc1SMauro Carvalho Chehab        # When using make, this won't be used, as the number of jobs comes
48008e14bc1SMauro Carvalho Chehab        # from POSIX jobserver. So, this covers the case where build comes
48108e14bc1SMauro Carvalho Chehab        # from command line. On such case, serialize by default, except if
48208e14bc1SMauro Carvalho Chehab        # the user explicitly sets the number of jobs.
483819667bcSMauro Carvalho Chehab        #
48408e14bc1SMauro Carvalho Chehab        n_jobs = 1
48508e14bc1SMauro Carvalho Chehab
48608e14bc1SMauro Carvalho Chehab        # n_jobs is either an integer or "auto". Only use it if it is a number
48708e14bc1SMauro Carvalho Chehab        if self.n_jobs:
488819667bcSMauro Carvalho Chehab            try:
48908e14bc1SMauro Carvalho Chehab                n_jobs = int(self.n_jobs)
49008e14bc1SMauro Carvalho Chehab            except ValueError:
491819667bcSMauro Carvalho Chehab                pass
492819667bcSMauro Carvalho Chehab
49308e14bc1SMauro Carvalho Chehab        #
49408e14bc1SMauro Carvalho Chehab        # When using make, jobserver.claim is the number of jobs that were
49508e14bc1SMauro Carvalho Chehab        # used with "-j" and that aren't used by other make targets
49608e14bc1SMauro Carvalho Chehab        #
49708e14bc1SMauro Carvalho Chehab        with JobserverExec() as jobserver:
49808e14bc1SMauro Carvalho Chehab            n_jobs = 1
499819667bcSMauro Carvalho Chehab
50008e14bc1SMauro Carvalho Chehab            #
50108e14bc1SMauro Carvalho Chehab            # Handle the case when a parameter is passed via command line,
50208e14bc1SMauro Carvalho Chehab            # using it as default, if jobserver doesn't claim anything
50308e14bc1SMauro Carvalho Chehab            #
50408e14bc1SMauro Carvalho Chehab            if self.n_jobs:
50508e14bc1SMauro Carvalho Chehab                try:
50608e14bc1SMauro Carvalho Chehab                    n_jobs = int(self.n_jobs)
50708e14bc1SMauro Carvalho Chehab                except ValueError:
50808e14bc1SMauro Carvalho Chehab                    pass
509819667bcSMauro Carvalho Chehab
51008e14bc1SMauro Carvalho Chehab            if jobserver.claim:
51108e14bc1SMauro Carvalho Chehab                n_jobs = jobserver.claim
512819667bcSMauro Carvalho Chehab
51308e14bc1SMauro Carvalho Chehab            builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
51408e14bc1SMauro Carvalho Chehab                                                                    latex_cmd,
51508e14bc1SMauro Carvalho Chehab                                                                    tex_files,
51608e14bc1SMauro Carvalho Chehab                                                                    n_jobs)
517819667bcSMauro Carvalho Chehab
51808e14bc1SMauro Carvalho Chehab        #
51908e14bc1SMauro Carvalho Chehab        # In verbose mode, print a summary with the build results per file.
52008e14bc1SMauro Carvalho Chehab        # Otherwise, print a single line with all failures, if any.
52108e14bc1SMauro Carvalho Chehab        # On both cases, return code 1 indicates build failures,
52208e14bc1SMauro Carvalho Chehab        #
52308e14bc1SMauro Carvalho Chehab        if self.verbose:
524819667bcSMauro Carvalho Chehab            msg = "Summary"
525819667bcSMauro Carvalho Chehab            msg += "\n" + "=" * len(msg)
526819667bcSMauro Carvalho Chehab            print()
527819667bcSMauro Carvalho Chehab            print(msg)
528819667bcSMauro Carvalho Chehab
529819667bcSMauro Carvalho Chehab            for pdf_name, pdf_file in builds.items():
530819667bcSMauro Carvalho Chehab                print(f"{pdf_name:<{max_len}}: {pdf_file}")
531819667bcSMauro Carvalho Chehab
532819667bcSMauro Carvalho Chehab            print()
533819667bcSMauro Carvalho Chehab            if build_failed:
534819667bcSMauro Carvalho Chehab                msg = LatexFontChecker().check()
535819667bcSMauro Carvalho Chehab                if msg:
536819667bcSMauro Carvalho Chehab                    print(msg)
537819667bcSMauro Carvalho Chehab
53808e14bc1SMauro Carvalho Chehab                sys.exit("Error: not all PDF files were created.")
53908e14bc1SMauro Carvalho Chehab
54008e14bc1SMauro Carvalho Chehab        elif build_failed:
54108e14bc1SMauro Carvalho Chehab            n_failures = len(builds)
54208e14bc1SMauro Carvalho Chehab            failures = ", ".join(builds.keys())
54308e14bc1SMauro Carvalho Chehab
54408e14bc1SMauro Carvalho Chehab            msg = LatexFontChecker().check()
54508e14bc1SMauro Carvalho Chehab            if msg:
54608e14bc1SMauro Carvalho Chehab                print(msg)
54708e14bc1SMauro Carvalho Chehab
54808e14bc1SMauro Carvalho Chehab            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
549819667bcSMauro Carvalho Chehab
550819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
551819667bcSMauro Carvalho Chehab        """
552819667bcSMauro Carvalho Chehab        Extra steps for Info output.
553819667bcSMauro Carvalho Chehab
554819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
555819667bcSMauro Carvalho Chehab        texinfo directory.
556819667bcSMauro Carvalho Chehab        """
557819667bcSMauro Carvalho Chehab
558819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
559819667bcSMauro Carvalho Chehab            try:
560819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
561819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
562819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
563819667bcSMauro Carvalho Chehab
5647e8a8143SMauro Carvalho Chehab    def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
5657e8a8143SMauro Carvalho Chehab        """
5667e8a8143SMauro Carvalho Chehab        Create man pages from kernel-doc output
5677e8a8143SMauro Carvalho Chehab        """
5687e8a8143SMauro Carvalho Chehab
5697e8a8143SMauro Carvalho Chehab        re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
5707e8a8143SMauro Carvalho Chehab        re_man = re.compile(r'^\.TH "[^"]*" (\d+) "([^"]*)"')
5717e8a8143SMauro Carvalho Chehab
5727e8a8143SMauro Carvalho Chehab        if docs_dir == src_dir:
5737e8a8143SMauro Carvalho Chehab            #
5747e8a8143SMauro Carvalho Chehab            # Pick the entire set of kernel-doc markups from the entire tree
5757e8a8143SMauro Carvalho Chehab            #
5767e8a8143SMauro Carvalho Chehab            kdoc_files = set([self.srctree])
5777e8a8143SMauro Carvalho Chehab        else:
5787e8a8143SMauro Carvalho Chehab            kdoc_files = set()
5797e8a8143SMauro Carvalho Chehab
5807e8a8143SMauro Carvalho Chehab            for fname in glob(os.path.join(src_dir, "**"), recursive=True):
5817e8a8143SMauro Carvalho Chehab                if os.path.isfile(fname) and fname.endswith(".rst"):
5827e8a8143SMauro Carvalho Chehab                    with open(fname, "r", encoding="utf-8") as in_fp:
5837e8a8143SMauro Carvalho Chehab                        data = in_fp.read()
5847e8a8143SMauro Carvalho Chehab
5857e8a8143SMauro Carvalho Chehab                    for line in data.split("\n"):
5867e8a8143SMauro Carvalho Chehab                        match = re_kernel_doc.match(line)
5877e8a8143SMauro Carvalho Chehab                        if match:
5887e8a8143SMauro Carvalho Chehab                            if os.path.isfile(match.group(1)):
5897e8a8143SMauro Carvalho Chehab                                kdoc_files.add(match.group(1))
5907e8a8143SMauro Carvalho Chehab
5917e8a8143SMauro Carvalho Chehab        if not kdoc_files:
5927e8a8143SMauro Carvalho Chehab                sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
5937e8a8143SMauro Carvalho Chehab
5947e8a8143SMauro Carvalho Chehab        cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
5957e8a8143SMauro Carvalho Chehab        try:
5967e8a8143SMauro Carvalho Chehab            if self.verbose:
5977e8a8143SMauro Carvalho Chehab                print(" ".join(cmd))
5987e8a8143SMauro Carvalho Chehab
5997e8a8143SMauro Carvalho Chehab            result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
6007e8a8143SMauro Carvalho Chehab
6017e8a8143SMauro Carvalho Chehab            if result.returncode:
6027e8a8143SMauro Carvalho Chehab                print(f"Warning: kernel-doc returned {result.returncode} warnings")
6037e8a8143SMauro Carvalho Chehab
6047e8a8143SMauro Carvalho Chehab        except (OSError, ValueError, subprocess.SubprocessError) as e:
6057e8a8143SMauro Carvalho Chehab            sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
6067e8a8143SMauro Carvalho Chehab
6077e8a8143SMauro Carvalho Chehab        fp = None
6087e8a8143SMauro Carvalho Chehab        try:
6097e8a8143SMauro Carvalho Chehab            for line in result.stdout.split("\n"):
6107e8a8143SMauro Carvalho Chehab                match = re_man.match(line)
6117e8a8143SMauro Carvalho Chehab                if not match:
6127e8a8143SMauro Carvalho Chehab                    if fp:
6137e8a8143SMauro Carvalho Chehab                        fp.write(line + '\n')
6147e8a8143SMauro Carvalho Chehab                    continue
6157e8a8143SMauro Carvalho Chehab
6167e8a8143SMauro Carvalho Chehab                if fp:
6177e8a8143SMauro Carvalho Chehab                    fp.close()
6187e8a8143SMauro Carvalho Chehab
6197e8a8143SMauro Carvalho Chehab                fname = f"{output_dir}/{match.group(2)}.{match.group(1)}"
6207e8a8143SMauro Carvalho Chehab
6217e8a8143SMauro Carvalho Chehab                if self.verbose:
6227e8a8143SMauro Carvalho Chehab                    print(f"Creating {fname}")
6237e8a8143SMauro Carvalho Chehab                fp = open(fname, "w", encoding="utf-8")
6247e8a8143SMauro Carvalho Chehab                fp.write(line + '\n')
6257e8a8143SMauro Carvalho Chehab        finally:
6267e8a8143SMauro Carvalho Chehab            if fp:
6277e8a8143SMauro Carvalho Chehab                fp.close()
6287e8a8143SMauro Carvalho Chehab
629819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
630819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
631819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
632819667bcSMauro Carvalho Chehab
63372603d73SMauro Carvalho Chehab    def build(self, target, sphinxdirs=None,
634464257baSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None,
6354c6ece91SMauro Carvalho Chehab              skip_sphinx=False):
636819667bcSMauro Carvalho Chehab        """
637819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
638819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
639819667bcSMauro Carvalho Chehab        """
640819667bcSMauro Carvalho Chehab
641819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
642819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
643819667bcSMauro Carvalho Chehab
644819667bcSMauro Carvalho Chehab        #
645819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
646819667bcSMauro Carvalho Chehab        #
647819667bcSMauro Carvalho Chehab        if target == "cleandocs":
648819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
649819667bcSMauro Carvalho Chehab            return
650819667bcSMauro Carvalho Chehab
651819667bcSMauro Carvalho Chehab        if theme:
652819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
653819667bcSMauro Carvalho Chehab
654819667bcSMauro Carvalho Chehab        #
655819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
656819667bcSMauro Carvalho Chehab        #
6574c6ece91SMauro Carvalho Chehab        if not skip_sphinx:
658819667bcSMauro Carvalho Chehab            sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
6597e8a8143SMauro Carvalho Chehab            if not sphinxbuild and target != "mandocs":
660819667bcSMauro Carvalho Chehab                sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
661819667bcSMauro Carvalho Chehab
6625401f971SMauro Carvalho Chehab        if target == "pdfdocs":
663819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
664819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
665819667bcSMauro Carvalho Chehab
666819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
667819667bcSMauro Carvalho Chehab
668819667bcSMauro Carvalho Chehab        #
669819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
670819667bcSMauro Carvalho Chehab        #
671819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
672819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
673819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
674819667bcSMauro Carvalho Chehab
675819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
676819667bcSMauro Carvalho Chehab
677819667bcSMauro Carvalho Chehab        if builder == "latex":
678819667bcSMauro Carvalho Chehab            if not paper:
679819667bcSMauro Carvalho Chehab                paper = PAPER[1]
680819667bcSMauro Carvalho Chehab
681819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
682819667bcSMauro Carvalho Chehab
683464257baSMauro Carvalho Chehab        if self.rustdoc:
684819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
685819667bcSMauro Carvalho Chehab
686819667bcSMauro Carvalho Chehab        if not sphinxdirs:
687819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
688819667bcSMauro Carvalho Chehab
689819667bcSMauro Carvalho Chehab        #
69082c294d4SMauro Carvalho Chehab        # The sphinx-build tool has a bug: internally, it tries to set
69182c294d4SMauro Carvalho Chehab        # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
69282c294d4SMauro Carvalho Chehab        # crash if language is not set. Detect and fix it.
69382c294d4SMauro Carvalho Chehab        #
69482c294d4SMauro Carvalho Chehab        try:
69582c294d4SMauro Carvalho Chehab            locale.setlocale(locale.LC_ALL, '')
69682c294d4SMauro Carvalho Chehab        except locale.Error:
69782c294d4SMauro Carvalho Chehab            self.env["LC_ALL"] = "C"
69882c294d4SMauro Carvalho Chehab
69982c294d4SMauro Carvalho Chehab        #
700819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
701819667bcSMauro Carvalho Chehab        #
702819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
703819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
704819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
705819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
706819667bcSMauro Carvalho Chehab            else:
707819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
708819667bcSMauro Carvalho Chehab
709819667bcSMauro Carvalho Chehab        #
710819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
711819667bcSMauro Carvalho Chehab        #
712819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
713819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
714819667bcSMauro Carvalho Chehab        # the beginning.
715819667bcSMauro Carvalho Chehab        #
716819667bcSMauro Carvalho Chehab        output_dirs = []
717819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
718819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
719819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
720819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
721819667bcSMauro Carvalho Chehab
722819667bcSMauro Carvalho Chehab            #
723819667bcSMauro Carvalho Chehab            # Make directory names canonical
724819667bcSMauro Carvalho Chehab            #
725819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
726819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
727819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
728819667bcSMauro Carvalho Chehab
729819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
730819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
731819667bcSMauro Carvalho Chehab
732819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
733819667bcSMauro Carvalho Chehab
734819667bcSMauro Carvalho Chehab            build_args = args + [
735819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
736819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
737819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
738819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
739819667bcSMauro Carvalho Chehab                src_dir,
740819667bcSMauro Carvalho Chehab                output_dir,
741819667bcSMauro Carvalho Chehab            ]
742819667bcSMauro Carvalho Chehab
7437e8a8143SMauro Carvalho Chehab            if target == "mandocs":
7447e8a8143SMauro Carvalho Chehab                self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
7454c6ece91SMauro Carvalho Chehab            elif not skip_sphinx:
746819667bcSMauro Carvalho Chehab                try:
7470aa9c039SMauro Carvalho Chehab                    result = self.run_sphinx(sphinxbuild, build_args,
7480aa9c039SMauro Carvalho Chehab                                             env=self.env)
7490aa9c039SMauro Carvalho Chehab
7500aa9c039SMauro Carvalho Chehab                    if result:
7510aa9c039SMauro Carvalho Chehab                        sys.exit(f"Build failed: return code: {result}")
7520aa9c039SMauro Carvalho Chehab
753819667bcSMauro Carvalho Chehab                except (OSError, ValueError, subprocess.SubprocessError) as e:
754819667bcSMauro Carvalho Chehab                    sys.exit(f"Build failed: {repr(e)}")
755819667bcSMauro Carvalho Chehab
756819667bcSMauro Carvalho Chehab            #
757819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
758819667bcSMauro Carvalho Chehab            #
759819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
760464257baSMauro Carvalho Chehab                self.handle_html(css, output_dir)
761819667bcSMauro Carvalho Chehab
762819667bcSMauro Carvalho Chehab        #
763819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
764819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
765819667bcSMauro Carvalho Chehab        #
766819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
767819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
768819667bcSMauro Carvalho Chehab        elif target == "infodocs":
769819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
770819667bcSMauro Carvalho Chehab
771*5094f7d5SThomas Weißschuh        if self.rustdoc and target in ["htmldocs", "epubdocs"]:
772*5094f7d5SThomas Weißschuh            print("Building rust docs")
773*5094f7d5SThomas Weißschuh            if "MAKE" in self.env:
774*5094f7d5SThomas Weißschuh                cmd = [self.env["MAKE"]]
775*5094f7d5SThomas Weißschuh            else:
776*5094f7d5SThomas Weißschuh                cmd = ["make", "LLVM=1"]
777*5094f7d5SThomas Weißschuh
778*5094f7d5SThomas Weißschuh            cmd += [ "rustdoc"]
779*5094f7d5SThomas Weißschuh            if self.verbose:
780*5094f7d5SThomas Weißschuh                print(" ".join(cmd))
781*5094f7d5SThomas Weißschuh
782*5094f7d5SThomas Weißschuh            try:
783*5094f7d5SThomas Weißschuh                subprocess.run(cmd, check=True)
784*5094f7d5SThomas Weißschuh            except subprocess.CalledProcessError as e:
785*5094f7d5SThomas Weißschuh                print(f"Ignored errors when building rustdoc: {e}. Is RUST enabled?",
786*5094f7d5SThomas Weißschuh                      file=sys.stderr)
787*5094f7d5SThomas Weißschuh
788819667bcSMauro Carvalho Chehabdef jobs_type(value):
789819667bcSMauro Carvalho Chehab    """
790819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
791819667bcSMauro Carvalho Chehab    equal or bigger than one.
792819667bcSMauro Carvalho Chehab    """
793819667bcSMauro Carvalho Chehab    if value is None:
794819667bcSMauro Carvalho Chehab        return None
795819667bcSMauro Carvalho Chehab
796819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
797819667bcSMauro Carvalho Chehab        return value.lower()
798819667bcSMauro Carvalho Chehab
799819667bcSMauro Carvalho Chehab    try:
800819667bcSMauro Carvalho Chehab        if int(value) >= 1:
801819667bcSMauro Carvalho Chehab            return value
802819667bcSMauro Carvalho Chehab
803819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
804819667bcSMauro Carvalho Chehab    except ValueError:
805819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
806819667bcSMauro Carvalho Chehab
807819667bcSMauro Carvalho Chehabdef main():
808819667bcSMauro Carvalho Chehab    """
809819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
810819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
811819667bcSMauro Carvalho Chehab    specified at os.environ.
812819667bcSMauro Carvalho Chehab    """
813819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
814819667bcSMauro Carvalho Chehab
815819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
816819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
817819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
818819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
819819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
820819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
821819667bcSMauro Carvalho Chehab
822819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
823819667bcSMauro Carvalho Chehab
824819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
825819667bcSMauro Carvalho Chehab
826819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
827819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
828819667bcSMauro Carvalho Chehab
829819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
830819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
831819667bcSMauro Carvalho Chehab
832819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
833819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
834819667bcSMauro Carvalho Chehab
835819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
836819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
837819667bcSMauro Carvalho Chehab
8382f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
8392f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
8402f99b85eSMauro Carvalho Chehab
8414c6ece91SMauro Carvalho Chehab    parser.add_argument('-s', '--skip-sphinx-build', action='store_true',
8424c6ece91SMauro Carvalho Chehab                        help="Skip sphinx-build step")
8434c6ece91SMauro Carvalho Chehab
84442180adaSMauro Carvalho Chehab    parser.add_argument("-V", "--venv", nargs='?', const=f'{VENV_DEFAULT}',
84542180adaSMauro Carvalho Chehab                        default=None,
84642180adaSMauro Carvalho Chehab                        help=f'If used, run Sphinx from a venv dir (default dir: {VENV_DEFAULT})')
84742180adaSMauro Carvalho Chehab
848819667bcSMauro Carvalho Chehab    args = parser.parse_args()
849819667bcSMauro Carvalho Chehab
85062ea383bSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION, show_alternatives=True,
85162ea383bSMauro Carvalho Chehab                               bail_out=True)
852819667bcSMauro Carvalho Chehab
85342180adaSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir, venv=args.venv,
8542f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
8552f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
856819667bcSMauro Carvalho Chehab
85772603d73SMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs,
858819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
859464257baSMauro Carvalho Chehab                  deny_vf=args.deny_vf,
8604c6ece91SMauro Carvalho Chehab                  skip_sphinx=args.skip_sphinx_build)
861819667bcSMauro Carvalho Chehab
862819667bcSMauro Carvalho Chehabif __name__ == "__main__":
863819667bcSMauro Carvalho Chehab    main()
864