xref: /linux/tools/docs/sphinx-build-wrapper (revision 22a42ee5dad307d4236839e75929c86346cf42e3)
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
122ffb569d5SThomas Weißschuh    def check_rust(self, sphinxdirs):
123464257baSMauro Carvalho Chehab        """
124464257baSMauro Carvalho Chehab        Checks if Rust is enabled
125464257baSMauro Carvalho Chehab        """
126464257baSMauro Carvalho Chehab        config = os.path.join(self.srctree, ".config")
127464257baSMauro Carvalho Chehab
128ffb569d5SThomas Weißschuh        if not {'.', 'rust'}.intersection(sphinxdirs):
129ffb569d5SThomas Weißschuh            return False
130ffb569d5SThomas Weißschuh
131464257baSMauro Carvalho Chehab        if not os.path.isfile(config):
1322d652135SThomas Weißschuh            return False
133464257baSMauro Carvalho Chehab
134464257baSMauro Carvalho Chehab        re_rust = re.compile(r"CONFIG_RUST=(m|y)")
135464257baSMauro Carvalho Chehab
136464257baSMauro Carvalho Chehab        try:
137464257baSMauro Carvalho Chehab            with open(config, "r", encoding="utf-8") as fp:
138464257baSMauro Carvalho Chehab                for line in fp:
139464257baSMauro Carvalho Chehab                    if re_rust.match(line):
1402d652135SThomas Weißschuh                        return True
141464257baSMauro Carvalho Chehab
142464257baSMauro Carvalho Chehab        except OSError as e:
143464257baSMauro Carvalho Chehab            print(f"Failed to open {config}", file=sys.stderr)
1442d652135SThomas Weißschuh            return False
1452d652135SThomas Weißschuh
1462d652135SThomas Weißschuh        return False
147464257baSMauro Carvalho Chehab
148819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
149819667bcSMauro Carvalho Chehab        """
150819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
151819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
152819667bcSMauro Carvalho Chehab
153819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
154819667bcSMauro Carvalho Chehab
155819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
156819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
157819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
158819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
159819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
160819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
161819667bcSMauro Carvalho Chehab        """
162819667bcSMauro Carvalho Chehab
163819667bcSMauro Carvalho Chehab        #
164819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
165819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
166819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
167819667bcSMauro Carvalho Chehab        #
168819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
169819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
170e123e00aSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', action='store_true')
171b09cc1ddSMauro Carvalho Chehab        parser.add_argument('-v', '--verbose', default=0, action='count')
172819667bcSMauro Carvalho Chehab
173819667bcSMauro Carvalho Chehab        #
174819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
175819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
176819667bcSMauro Carvalho Chehab        #
177819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
178819667bcSMauro Carvalho Chehab
179819667bcSMauro Carvalho Chehab        #
180819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
181819667bcSMauro Carvalho Chehab        #
182819667bcSMauro Carvalho Chehab
183819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
184b09cc1ddSMauro Carvalho Chehab
185b09cc1ddSMauro Carvalho Chehab        verbose = sphinx_args.verbose
186b09cc1ddSMauro Carvalho Chehab        if self.verbose:
187b09cc1ddSMauro Carvalho Chehab            verbose += 1
188b09cc1ddSMauro Carvalho Chehab
189819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
190b09cc1ddSMauro Carvalho Chehab            verbose = 0
191819667bcSMauro Carvalho Chehab
192819667bcSMauro Carvalho Chehab        #
193819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
194819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
195819667bcSMauro Carvalho Chehab        #
196819667bcSMauro Carvalho Chehab        if n_jobs:
197819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
198819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
199819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
200819667bcSMauro Carvalho Chehab        else:
201819667bcSMauro Carvalho Chehab            self.n_jobs = None
202819667bcSMauro Carvalho Chehab
203b09cc1ddSMauro Carvalho Chehab        if verbose < 1:
204819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
205b09cc1ddSMauro Carvalho Chehab        else:
206b09cc1ddSMauro Carvalho Chehab            for i in range(1, sphinx_args.verbose):
207b09cc1ddSMauro Carvalho Chehab                self.sphinxopts += ["-v"]
208819667bcSMauro Carvalho Chehab
20942180adaSMauro Carvalho Chehab    def __init__(self, builddir, venv=None, verbose=False, n_jobs=None,
21042180adaSMauro Carvalho Chehab                 interactive=None):
211819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
21242180adaSMauro Carvalho Chehab        self.venv = venv
213819667bcSMauro Carvalho Chehab        self.verbose = None
214819667bcSMauro Carvalho Chehab
215819667bcSMauro Carvalho Chehab        #
216819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
217819667bcSMauro Carvalho Chehab        #
218819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
219819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
220819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
2212f99b85eSMauro Carvalho Chehab
22235b9d338SMauro Carvalho Chehab        #
223*22a42ee5SRandy Dunlap        # Add localversion* to kernelversion if present
224*22a42ee5SRandy Dunlap        #
225*22a42ee5SRandy Dunlap        for file in glob(os.environ["srctree"] + "/localversion*"):
226*22a42ee5SRandy Dunlap            if not file.endswith(".orig"):
227*22a42ee5SRandy Dunlap                with open(file, 'r', encoding='utf-8') as f:
228*22a42ee5SRandy Dunlap                    text = f.read()
229*22a42ee5SRandy Dunlap                    self.kernelversion += text
230*22a42ee5SRandy Dunlap
231*22a42ee5SRandy Dunlap        #
23235b9d338SMauro Carvalho Chehab        # Kernel main Makefile defines a PYTHON3 variable whose default is
23335b9d338SMauro Carvalho Chehab        # "python3". When set to a different value, it allows running a
23435b9d338SMauro Carvalho Chehab        # diferent version than the default official python3 package.
23535b9d338SMauro Carvalho Chehab        # Several distros package python3xx-sphinx packages with newer
23635b9d338SMauro Carvalho Chehab        # versions of Python and sphinx-build.
23735b9d338SMauro Carvalho Chehab        #
23835b9d338SMauro Carvalho Chehab        # Honor such variable different than default
23935b9d338SMauro Carvalho Chehab        #
24035b9d338SMauro Carvalho Chehab        self.python = os.environ.get("PYTHON3")
24135b9d338SMauro Carvalho Chehab        if self.python == "python3":
24235b9d338SMauro Carvalho Chehab            self.python = None
24335b9d338SMauro Carvalho Chehab
2442f99b85eSMauro Carvalho Chehab        if not interactive:
245819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
2462f99b85eSMauro Carvalho Chehab        else:
2472f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
248819667bcSMauro Carvalho Chehab
249819667bcSMauro Carvalho Chehab        if not verbose:
250d642acfdSMauro Carvalho Chehab            try:
251d642acfdSMauro Carvalho Chehab                verbose = bool(int(os.environ.get("KBUILD_VERBOSE", 0)))
252d642acfdSMauro Carvalho Chehab            except ValueError:
253d642acfdSMauro Carvalho Chehab                # Handles an eventual case where verbosity is not a number
254d642acfdSMauro Carvalho Chehab                # like KBUILD_VERBOSE=""
255d642acfdSMauro Carvalho Chehab                verbose = False
256819667bcSMauro Carvalho Chehab
257819667bcSMauro Carvalho Chehab        if verbose is not None:
258819667bcSMauro Carvalho Chehab            self.verbose = verbose
259819667bcSMauro Carvalho Chehab
260819667bcSMauro Carvalho Chehab        #
261819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
262819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
263819667bcSMauro Carvalho Chehab        #
264819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
265819667bcSMauro Carvalho Chehab        if not self.srctree:
266819667bcSMauro Carvalho Chehab            self.srctree = "."
267819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
268819667bcSMauro Carvalho Chehab
269819667bcSMauro Carvalho Chehab        #
270819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
271819667bcSMauro Carvalho Chehab        #
272819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
273819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
274eba6ffd1SJonathan Corbet                                                      "tools/docs/kernel-doc"))
275819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
276819667bcSMauro Carvalho Chehab
277819667bcSMauro Carvalho Chehab        #
278819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
279819667bcSMauro Carvalho Chehab        #
280819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
281819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
282819667bcSMauro Carvalho Chehab
283819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
284819667bcSMauro Carvalho Chehab
285819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
286819667bcSMauro Carvalho Chehab
28742180adaSMauro Carvalho Chehab        #
28842180adaSMauro Carvalho Chehab        # If venv command line argument is specified, run Sphinx from venv
28942180adaSMauro Carvalho Chehab        #
29042180adaSMauro Carvalho Chehab        if venv:
29142180adaSMauro Carvalho Chehab            bin_dir = os.path.join(venv, "bin")
29242180adaSMauro Carvalho Chehab            if not os.path.isfile(os.path.join(bin_dir, "activate")):
29342180adaSMauro Carvalho Chehab                sys.exit(f"Venv {venv} not found.")
29442180adaSMauro Carvalho Chehab
29542180adaSMauro Carvalho Chehab            # "activate" virtual env
29642180adaSMauro Carvalho Chehab            self.env["PATH"] = bin_dir + ":" + self.env["PATH"]
29742180adaSMauro Carvalho Chehab            self.env["VIRTUAL_ENV"] = venv
29842180adaSMauro Carvalho Chehab            if "PYTHONHOME" in self.env:
29942180adaSMauro Carvalho Chehab                del self.env["PYTHONHOME"]
30042180adaSMauro Carvalho Chehab            print(f"Setting venv to {venv}")
30142180adaSMauro Carvalho Chehab
302819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
303819667bcSMauro Carvalho Chehab        """
304819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
305819667bcSMauro Carvalho Chehab
306819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
307819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
308819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
309819667bcSMauro Carvalho Chehab        jobs.
310819667bcSMauro Carvalho Chehab
311819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
312819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
313819667bcSMauro Carvalho Chehab
314819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
315819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
316819667bcSMauro Carvalho Chehab        """
317819667bcSMauro Carvalho Chehab
318819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
319819667bcSMauro Carvalho Chehab            if jobserver.claim:
320819667bcSMauro Carvalho Chehab                #
321819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
322819667bcSMauro Carvalho Chehab                #
323819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
324819667bcSMauro Carvalho Chehab            else:
325819667bcSMauro Carvalho Chehab                #
326819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
327819667bcSMauro Carvalho Chehab                #
328819667bcSMauro Carvalho Chehab                n_jobs = "auto"
329819667bcSMauro Carvalho Chehab
330819667bcSMauro Carvalho Chehab            #
331819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
332819667bcSMauro Carvalho Chehab            #
333819667bcSMauro Carvalho Chehab            if self.n_jobs:
334819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
335819667bcSMauro Carvalho Chehab
33635b9d338SMauro Carvalho Chehab            #
33735b9d338SMauro Carvalho Chehab            # We can't simply call python3 sphinx-build, as OpenSUSE
33835b9d338SMauro Carvalho Chehab            # Tumbleweed uses an ELF binary file (/usr/bin/alts) to switch
33935b9d338SMauro Carvalho Chehab            # between different versions of sphinx-build. So, only call it
34035b9d338SMauro Carvalho Chehab            # prepending "python3.xx" when PYTHON3 variable is not default.
34135b9d338SMauro Carvalho Chehab            #
34235b9d338SMauro Carvalho Chehab            if self.python:
34335b9d338SMauro Carvalho Chehab                cmd = [self.python]
34442180adaSMauro Carvalho Chehab            else:
34535b9d338SMauro Carvalho Chehab                cmd = []
34642180adaSMauro Carvalho Chehab
34742180adaSMauro Carvalho Chehab            cmd += [sphinx_build]
348819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
349819667bcSMauro Carvalho Chehab            cmd += build_args
3501f6e3f21SAkira Yokosawa            cmd += self.sphinxopts
351819667bcSMauro Carvalho Chehab
352819667bcSMauro Carvalho Chehab            if self.verbose:
353819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
354819667bcSMauro Carvalho Chehab
355819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
356819667bcSMauro Carvalho Chehab
357464257baSMauro Carvalho Chehab    def handle_html(self, css, output_dir):
358819667bcSMauro Carvalho Chehab        """
359819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
360819667bcSMauro Carvalho Chehab
361819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
362819667bcSMauro Carvalho Chehab        copied to the output _static directory
363819667bcSMauro Carvalho Chehab        """
364819667bcSMauro Carvalho Chehab
3652118ba7dSMauro Carvalho Chehab        if css:
366819667bcSMauro Carvalho Chehab            css = os.path.expanduser(css)
367819667bcSMauro Carvalho Chehab            if not css.startswith("/"):
368819667bcSMauro Carvalho Chehab                css = os.path.join(self.srctree, css)
369819667bcSMauro Carvalho Chehab
370819667bcSMauro Carvalho Chehab            static_dir = os.path.join(output_dir, "_static")
371819667bcSMauro Carvalho Chehab            os.makedirs(static_dir, exist_ok=True)
372819667bcSMauro Carvalho Chehab
373819667bcSMauro Carvalho Chehab            try:
374819667bcSMauro Carvalho Chehab                shutil.copy2(css, static_dir)
375819667bcSMauro Carvalho Chehab            except (OSError, IOError) as e:
376819667bcSMauro Carvalho Chehab                print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
377819667bcSMauro Carvalho Chehab
37808e14bc1SMauro Carvalho Chehab    def build_pdf_file(self, latex_cmd, from_dir, path):
37908e14bc1SMauro Carvalho Chehab        """Builds a single pdf file using latex_cmd"""
38008e14bc1SMauro Carvalho Chehab        try:
38108e14bc1SMauro Carvalho Chehab            subprocess.run(latex_cmd + [path],
38208e14bc1SMauro Carvalho Chehab                            cwd=from_dir, check=True, env=self.env)
38308e14bc1SMauro Carvalho Chehab
38408e14bc1SMauro Carvalho Chehab            return True
38508e14bc1SMauro Carvalho Chehab        except subprocess.CalledProcessError:
38608e14bc1SMauro Carvalho Chehab            return False
38708e14bc1SMauro Carvalho Chehab
38808e14bc1SMauro Carvalho Chehab    def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
38908e14bc1SMauro Carvalho Chehab        """Build PDF files in parallel if possible"""
39008e14bc1SMauro Carvalho Chehab        builds = {}
39108e14bc1SMauro Carvalho Chehab        build_failed = False
39208e14bc1SMauro Carvalho Chehab        max_len = 0
39308e14bc1SMauro Carvalho Chehab        has_tex = False
39408e14bc1SMauro Carvalho Chehab
39508e14bc1SMauro Carvalho Chehab        #
39608e14bc1SMauro Carvalho Chehab        # LaTeX PDF error code is almost useless for us:
39708e14bc1SMauro Carvalho Chehab        # any warning makes it non-zero. For kernel doc builds it always return
39808e14bc1SMauro Carvalho Chehab        # non-zero even when build succeeds. So, let's do the best next thing:
39908e14bc1SMauro Carvalho Chehab        # Ignore build errors. At the end, check if all PDF files were built,
40008e14bc1SMauro Carvalho Chehab        # printing a summary with the built ones and returning 0 if all of
40108e14bc1SMauro Carvalho Chehab        # them were actually built.
40208e14bc1SMauro Carvalho Chehab        #
40308e14bc1SMauro Carvalho Chehab        with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
40408e14bc1SMauro Carvalho Chehab            jobs = {}
40508e14bc1SMauro Carvalho Chehab
40608e14bc1SMauro Carvalho Chehab            for from_dir, pdf_dir, entry in tex_files:
40708e14bc1SMauro Carvalho Chehab                name = entry.name
40808e14bc1SMauro Carvalho Chehab
40908e14bc1SMauro Carvalho Chehab                if not name.endswith(tex_suffix):
41008e14bc1SMauro Carvalho Chehab                    continue
41108e14bc1SMauro Carvalho Chehab
41208e14bc1SMauro Carvalho Chehab                name = name[:-len(tex_suffix)]
41308e14bc1SMauro Carvalho Chehab                has_tex = True
41408e14bc1SMauro Carvalho Chehab
41508e14bc1SMauro Carvalho Chehab                future = executor.submit(self.build_pdf_file, latex_cmd,
41608e14bc1SMauro Carvalho Chehab                                         from_dir, entry.path)
41708e14bc1SMauro Carvalho Chehab                jobs[future] = (from_dir, pdf_dir, name)
41808e14bc1SMauro Carvalho Chehab
41908e14bc1SMauro Carvalho Chehab            for future in futures.as_completed(jobs):
42008e14bc1SMauro Carvalho Chehab                from_dir, pdf_dir, name = jobs[future]
42108e14bc1SMauro Carvalho Chehab
42208e14bc1SMauro Carvalho Chehab                pdf_name = name + ".pdf"
42308e14bc1SMauro Carvalho Chehab                pdf_from = os.path.join(from_dir, pdf_name)
4240d9abc76SMauro Carvalho Chehab                pdf_to = os.path.join(pdf_dir, pdf_name)
4250d9abc76SMauro Carvalho Chehab                out_name = os.path.relpath(pdf_to, self.builddir)
4260d9abc76SMauro Carvalho Chehab                max_len = max(max_len, len(out_name))
42708e14bc1SMauro Carvalho Chehab
42808e14bc1SMauro Carvalho Chehab                try:
42908e14bc1SMauro Carvalho Chehab                    success = future.result()
43008e14bc1SMauro Carvalho Chehab
43108e14bc1SMauro Carvalho Chehab                    if success and os.path.exists(pdf_from):
43208e14bc1SMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
43308e14bc1SMauro Carvalho Chehab
43408e14bc1SMauro Carvalho Chehab                        #
43508e14bc1SMauro Carvalho Chehab                        # if verbose, get the name of built PDF file
43608e14bc1SMauro Carvalho Chehab                        #
43708e14bc1SMauro Carvalho Chehab                        if self.verbose:
4380d9abc76SMauro Carvalho Chehab                           builds[out_name] = "SUCCESS"
43908e14bc1SMauro Carvalho Chehab                    else:
4400d9abc76SMauro Carvalho Chehab                        builds[out_name] = "FAILED"
44108e14bc1SMauro Carvalho Chehab                        build_failed = True
44208e14bc1SMauro Carvalho Chehab                except futures.Error as e:
4430d9abc76SMauro Carvalho Chehab                    builds[out_name] = f"FAILED ({repr(e)})"
44408e14bc1SMauro Carvalho Chehab                    build_failed = True
44508e14bc1SMauro Carvalho Chehab
44608e14bc1SMauro Carvalho Chehab        #
44708e14bc1SMauro Carvalho Chehab        # Handle case where no .tex files were found
44808e14bc1SMauro Carvalho Chehab        #
44908e14bc1SMauro Carvalho Chehab        if not has_tex:
4500d9abc76SMauro Carvalho Chehab            out_name = "LaTeX files"
4510d9abc76SMauro Carvalho Chehab            max_len = max(max_len, len(out_name))
4520d9abc76SMauro Carvalho Chehab            builds[out_name] = "FAILED: no .tex files were generated"
45308e14bc1SMauro Carvalho Chehab            build_failed = True
45408e14bc1SMauro Carvalho Chehab
45508e14bc1SMauro Carvalho Chehab        return builds, build_failed, max_len
45608e14bc1SMauro Carvalho Chehab
457819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
458819667bcSMauro Carvalho Chehab        """
459819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
460819667bcSMauro Carvalho Chehab
461819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
462819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
463819667bcSMauro Carvalho Chehab        directory.
464819667bcSMauro Carvalho Chehab        """
465819667bcSMauro Carvalho Chehab        builds = {}
466819667bcSMauro Carvalho Chehab        max_len = 0
46708e14bc1SMauro Carvalho Chehab        tex_suffix = ".tex"
46808e14bc1SMauro Carvalho Chehab        tex_files = []
469819667bcSMauro Carvalho Chehab
470819667bcSMauro Carvalho Chehab        #
471819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
472819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
473819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
474819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
475819667bcSMauro Carvalho Chehab        # file with a deny list.
476819667bcSMauro Carvalho Chehab        #
477819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
478819667bcSMauro Carvalho Chehab        #
479819667bcSMauro Carvalho Chehab        if deny_vf:
480819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
481819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
482819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
483819667bcSMauro Carvalho Chehab
484819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
485819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
486819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
487819667bcSMauro Carvalho Chehab
488819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
489819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
490819667bcSMauro Carvalho Chehab            else:
491819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
492819667bcSMauro Carvalho Chehab
493819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
494819667bcSMauro Carvalho Chehab
49508e14bc1SMauro Carvalho Chehab            # Get a list of tex files to process
496819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
497819667bcSMauro Carvalho Chehab                for entry in it:
49808e14bc1SMauro Carvalho Chehab                    if entry.name.endswith(tex_suffix):
49908e14bc1SMauro Carvalho Chehab                        tex_files.append((from_dir, pdf_dir, entry))
500819667bcSMauro Carvalho Chehab
501819667bcSMauro Carvalho Chehab        #
50208e14bc1SMauro Carvalho Chehab        # When using make, this won't be used, as the number of jobs comes
50308e14bc1SMauro Carvalho Chehab        # from POSIX jobserver. So, this covers the case where build comes
50408e14bc1SMauro Carvalho Chehab        # from command line. On such case, serialize by default, except if
50508e14bc1SMauro Carvalho Chehab        # the user explicitly sets the number of jobs.
506819667bcSMauro Carvalho Chehab        #
50708e14bc1SMauro Carvalho Chehab        n_jobs = 1
50808e14bc1SMauro Carvalho Chehab
50908e14bc1SMauro Carvalho Chehab        # n_jobs is either an integer or "auto". Only use it if it is a number
51008e14bc1SMauro Carvalho Chehab        if self.n_jobs:
511819667bcSMauro Carvalho Chehab            try:
51208e14bc1SMauro Carvalho Chehab                n_jobs = int(self.n_jobs)
51308e14bc1SMauro Carvalho Chehab            except ValueError:
514819667bcSMauro Carvalho Chehab                pass
515819667bcSMauro Carvalho Chehab
51608e14bc1SMauro Carvalho Chehab        #
51708e14bc1SMauro Carvalho Chehab        # When using make, jobserver.claim is the number of jobs that were
51808e14bc1SMauro Carvalho Chehab        # used with "-j" and that aren't used by other make targets
51908e14bc1SMauro Carvalho Chehab        #
52008e14bc1SMauro Carvalho Chehab        with JobserverExec() as jobserver:
52108e14bc1SMauro Carvalho Chehab            n_jobs = 1
522819667bcSMauro Carvalho Chehab
52308e14bc1SMauro Carvalho Chehab            #
52408e14bc1SMauro Carvalho Chehab            # Handle the case when a parameter is passed via command line,
52508e14bc1SMauro Carvalho Chehab            # using it as default, if jobserver doesn't claim anything
52608e14bc1SMauro Carvalho Chehab            #
52708e14bc1SMauro Carvalho Chehab            if self.n_jobs:
52808e14bc1SMauro Carvalho Chehab                try:
52908e14bc1SMauro Carvalho Chehab                    n_jobs = int(self.n_jobs)
53008e14bc1SMauro Carvalho Chehab                except ValueError:
53108e14bc1SMauro Carvalho Chehab                    pass
532819667bcSMauro Carvalho Chehab
53308e14bc1SMauro Carvalho Chehab            if jobserver.claim:
53408e14bc1SMauro Carvalho Chehab                n_jobs = jobserver.claim
535819667bcSMauro Carvalho Chehab
53608e14bc1SMauro Carvalho Chehab            builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
53708e14bc1SMauro Carvalho Chehab                                                                    latex_cmd,
53808e14bc1SMauro Carvalho Chehab                                                                    tex_files,
53908e14bc1SMauro Carvalho Chehab                                                                    n_jobs)
540819667bcSMauro Carvalho Chehab
54108e14bc1SMauro Carvalho Chehab        #
54208e14bc1SMauro Carvalho Chehab        # In verbose mode, print a summary with the build results per file.
54308e14bc1SMauro Carvalho Chehab        # Otherwise, print a single line with all failures, if any.
54408e14bc1SMauro Carvalho Chehab        # On both cases, return code 1 indicates build failures,
54508e14bc1SMauro Carvalho Chehab        #
54608e14bc1SMauro Carvalho Chehab        if self.verbose:
547819667bcSMauro Carvalho Chehab            msg = "Summary"
548819667bcSMauro Carvalho Chehab            msg += "\n" + "=" * len(msg)
549819667bcSMauro Carvalho Chehab            print()
550819667bcSMauro Carvalho Chehab            print(msg)
551819667bcSMauro Carvalho Chehab
552819667bcSMauro Carvalho Chehab            for pdf_name, pdf_file in builds.items():
553819667bcSMauro Carvalho Chehab                print(f"{pdf_name:<{max_len}}: {pdf_file}")
554819667bcSMauro Carvalho Chehab
555819667bcSMauro Carvalho Chehab            print()
556819667bcSMauro Carvalho Chehab            if build_failed:
557819667bcSMauro Carvalho Chehab                msg = LatexFontChecker().check()
558819667bcSMauro Carvalho Chehab                if msg:
559819667bcSMauro Carvalho Chehab                    print(msg)
560819667bcSMauro Carvalho Chehab
56108e14bc1SMauro Carvalho Chehab                sys.exit("Error: not all PDF files were created.")
56208e14bc1SMauro Carvalho Chehab
56308e14bc1SMauro Carvalho Chehab        elif build_failed:
56408e14bc1SMauro Carvalho Chehab            n_failures = len(builds)
56508e14bc1SMauro Carvalho Chehab            failures = ", ".join(builds.keys())
56608e14bc1SMauro Carvalho Chehab
56708e14bc1SMauro Carvalho Chehab            msg = LatexFontChecker().check()
56808e14bc1SMauro Carvalho Chehab            if msg:
56908e14bc1SMauro Carvalho Chehab                print(msg)
57008e14bc1SMauro Carvalho Chehab
57108e14bc1SMauro Carvalho Chehab            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
572819667bcSMauro Carvalho Chehab
573819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
574819667bcSMauro Carvalho Chehab        """
575819667bcSMauro Carvalho Chehab        Extra steps for Info output.
576819667bcSMauro Carvalho Chehab
577819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
578819667bcSMauro Carvalho Chehab        texinfo directory.
579819667bcSMauro Carvalho Chehab        """
580819667bcSMauro Carvalho Chehab
581819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
582819667bcSMauro Carvalho Chehab            try:
583819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
584819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
585819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
586819667bcSMauro Carvalho Chehab
5877e8a8143SMauro Carvalho Chehab    def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
5887e8a8143SMauro Carvalho Chehab        """
5897e8a8143SMauro Carvalho Chehab        Create man pages from kernel-doc output
5907e8a8143SMauro Carvalho Chehab        """
5917e8a8143SMauro Carvalho Chehab
5927e8a8143SMauro Carvalho Chehab        re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
5937e8a8143SMauro Carvalho Chehab
5947e8a8143SMauro Carvalho Chehab        if docs_dir == src_dir:
5957e8a8143SMauro Carvalho Chehab            #
5967e8a8143SMauro Carvalho Chehab            # Pick the entire set of kernel-doc markups from the entire tree
5977e8a8143SMauro Carvalho Chehab            #
5987e8a8143SMauro Carvalho Chehab            kdoc_files = set([self.srctree])
5997e8a8143SMauro Carvalho Chehab        else:
6007e8a8143SMauro Carvalho Chehab            kdoc_files = set()
6017e8a8143SMauro Carvalho Chehab
6027e8a8143SMauro Carvalho Chehab            for fname in glob(os.path.join(src_dir, "**"), recursive=True):
6037e8a8143SMauro Carvalho Chehab                if os.path.isfile(fname) and fname.endswith(".rst"):
6047e8a8143SMauro Carvalho Chehab                    with open(fname, "r", encoding="utf-8") as in_fp:
6057e8a8143SMauro Carvalho Chehab                        data = in_fp.read()
6067e8a8143SMauro Carvalho Chehab
6077e8a8143SMauro Carvalho Chehab                    for line in data.split("\n"):
6087e8a8143SMauro Carvalho Chehab                        match = re_kernel_doc.match(line)
6097e8a8143SMauro Carvalho Chehab                        if match:
6107e8a8143SMauro Carvalho Chehab                            if os.path.isfile(match.group(1)):
6117e8a8143SMauro Carvalho Chehab                                kdoc_files.add(match.group(1))
6127e8a8143SMauro Carvalho Chehab
6137e8a8143SMauro Carvalho Chehab        if not kdoc_files:
6147e8a8143SMauro Carvalho Chehab                sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
6157e8a8143SMauro Carvalho Chehab
6167e8a8143SMauro Carvalho Chehab        cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
6177e8a8143SMauro Carvalho Chehab        try:
6187e8a8143SMauro Carvalho Chehab            if self.verbose:
6197e8a8143SMauro Carvalho Chehab                print(" ".join(cmd))
6207e8a8143SMauro Carvalho Chehab
6217e8a8143SMauro Carvalho Chehab            result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
6227e8a8143SMauro Carvalho Chehab
6237e8a8143SMauro Carvalho Chehab            if result.returncode:
6247e8a8143SMauro Carvalho Chehab                print(f"Warning: kernel-doc returned {result.returncode} warnings")
6257e8a8143SMauro Carvalho Chehab
6267e8a8143SMauro Carvalho Chehab        except (OSError, ValueError, subprocess.SubprocessError) as e:
6277e8a8143SMauro Carvalho Chehab            sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
6287e8a8143SMauro Carvalho Chehab
6297e8a8143SMauro Carvalho Chehab        fp = None
6307e8a8143SMauro Carvalho Chehab        try:
6317e8a8143SMauro Carvalho Chehab            for line in result.stdout.split("\n"):
6325828d356SMauro Carvalho Chehab                if not line.startswith(".TH"):
6337e8a8143SMauro Carvalho Chehab                    if fp:
6347e8a8143SMauro Carvalho Chehab                        fp.write(line + '\n')
6357e8a8143SMauro Carvalho Chehab                    continue
6367e8a8143SMauro Carvalho Chehab
6377e8a8143SMauro Carvalho Chehab                if fp:
6387e8a8143SMauro Carvalho Chehab                    fp.close()
6397e8a8143SMauro Carvalho Chehab
6405828d356SMauro Carvalho Chehab                # Use shlex here, as it handles well parameters with commas
6415828d356SMauro Carvalho Chehab                args = shlex.split(line)
6424160533dSMauro Carvalho Chehab                fname = f"{args[1]}.{args[2]}"
6430e4c8adaSMauro Carvalho Chehab                fname = fname.replace("/", " ")
6440e4c8adaSMauro Carvalho Chehab                fname = f"{output_dir}/{fname}"
6457e8a8143SMauro Carvalho Chehab
6467e8a8143SMauro Carvalho Chehab                if self.verbose:
6477e8a8143SMauro Carvalho Chehab                    print(f"Creating {fname}")
6487e8a8143SMauro Carvalho Chehab                fp = open(fname, "w", encoding="utf-8")
6497e8a8143SMauro Carvalho Chehab                fp.write(line + '\n')
6507e8a8143SMauro Carvalho Chehab        finally:
6517e8a8143SMauro Carvalho Chehab            if fp:
6527e8a8143SMauro Carvalho Chehab                fp.close()
6537e8a8143SMauro Carvalho Chehab
654819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
655819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
656819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
657819667bcSMauro Carvalho Chehab
65872603d73SMauro Carvalho Chehab    def build(self, target, sphinxdirs=None,
659464257baSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None,
6604c6ece91SMauro Carvalho Chehab              skip_sphinx=False):
661819667bcSMauro Carvalho Chehab        """
662819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
663819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
664819667bcSMauro Carvalho Chehab        """
665819667bcSMauro Carvalho Chehab
666819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
667819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
668819667bcSMauro Carvalho Chehab
669819667bcSMauro Carvalho Chehab        #
670819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
671819667bcSMauro Carvalho Chehab        #
672819667bcSMauro Carvalho Chehab        if target == "cleandocs":
673819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
674819667bcSMauro Carvalho Chehab            return
675819667bcSMauro Carvalho Chehab
676819667bcSMauro Carvalho Chehab        if theme:
677819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
678819667bcSMauro Carvalho Chehab
679819667bcSMauro Carvalho Chehab        #
680819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
681819667bcSMauro Carvalho Chehab        #
6824c6ece91SMauro Carvalho Chehab        if not skip_sphinx:
683819667bcSMauro Carvalho Chehab            sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
6847e8a8143SMauro Carvalho Chehab            if not sphinxbuild and target != "mandocs":
685819667bcSMauro Carvalho Chehab                sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
686819667bcSMauro Carvalho Chehab
6875401f971SMauro Carvalho Chehab        if target == "pdfdocs":
688819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
689819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
690819667bcSMauro Carvalho Chehab
691819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
692819667bcSMauro Carvalho Chehab
693819667bcSMauro Carvalho Chehab        #
694819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
695819667bcSMauro Carvalho Chehab        #
696819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
697819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
698819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
699819667bcSMauro Carvalho Chehab
7006f9a96ccSThomas Weißschuh        if not sphinxdirs:
7016f9a96ccSThomas Weißschuh            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
7026f9a96ccSThomas Weißschuh
7036f9a96ccSThomas Weißschuh        #
7046f9a96ccSThomas Weißschuh        # sphinxdirs can be a list or a whitespace-separated string
7056f9a96ccSThomas Weißschuh        #
7066f9a96ccSThomas Weißschuh        sphinxdirs_list = []
7076f9a96ccSThomas Weißschuh        for sphinxdir in sphinxdirs:
7086f9a96ccSThomas Weißschuh            if isinstance(sphinxdir, list):
7096f9a96ccSThomas Weißschuh                sphinxdirs_list += sphinxdir
7106f9a96ccSThomas Weißschuh            else:
7116f9a96ccSThomas Weißschuh                sphinxdirs_list += sphinxdir.split()
7126f9a96ccSThomas Weißschuh
713819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
714819667bcSMauro Carvalho Chehab
715819667bcSMauro Carvalho Chehab        if builder == "latex":
716819667bcSMauro Carvalho Chehab            if not paper:
717819667bcSMauro Carvalho Chehab                paper = PAPER[1]
718819667bcSMauro Carvalho Chehab
719819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
720819667bcSMauro Carvalho Chehab
721ffb569d5SThomas Weißschuh        rustdoc = self.check_rust(sphinxdirs_list)
7222d652135SThomas Weißschuh        if rustdoc:
723819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
724819667bcSMauro Carvalho Chehab
725819667bcSMauro Carvalho Chehab        #
72682c294d4SMauro Carvalho Chehab        # The sphinx-build tool has a bug: internally, it tries to set
72782c294d4SMauro Carvalho Chehab        # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
72882c294d4SMauro Carvalho Chehab        # crash if language is not set. Detect and fix it.
72982c294d4SMauro Carvalho Chehab        #
73082c294d4SMauro Carvalho Chehab        try:
73182c294d4SMauro Carvalho Chehab            locale.setlocale(locale.LC_ALL, '')
73282c294d4SMauro Carvalho Chehab        except locale.Error:
73382c294d4SMauro Carvalho Chehab            self.env["LC_ALL"] = "C"
73482c294d4SMauro Carvalho Chehab
73582c294d4SMauro Carvalho Chehab        #
736819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
737819667bcSMauro Carvalho Chehab        #
738819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
739819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
740819667bcSMauro Carvalho Chehab        # the beginning.
741819667bcSMauro Carvalho Chehab        #
742819667bcSMauro Carvalho Chehab        output_dirs = []
743819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
744819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
745819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
746819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
747819667bcSMauro Carvalho Chehab
748819667bcSMauro Carvalho Chehab            #
749819667bcSMauro Carvalho Chehab            # Make directory names canonical
750819667bcSMauro Carvalho Chehab            #
751819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
752819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
753819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
754819667bcSMauro Carvalho Chehab
755819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
756819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
757819667bcSMauro Carvalho Chehab
758819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
759819667bcSMauro Carvalho Chehab
760819667bcSMauro Carvalho Chehab            build_args = args + [
761819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
762819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
763819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
764819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
765819667bcSMauro Carvalho Chehab                src_dir,
766819667bcSMauro Carvalho Chehab                output_dir,
767819667bcSMauro Carvalho Chehab            ]
768819667bcSMauro Carvalho Chehab
7697e8a8143SMauro Carvalho Chehab            if target == "mandocs":
7707e8a8143SMauro Carvalho Chehab                self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
7714c6ece91SMauro Carvalho Chehab            elif not skip_sphinx:
772819667bcSMauro Carvalho Chehab                try:
7730aa9c039SMauro Carvalho Chehab                    result = self.run_sphinx(sphinxbuild, build_args,
7740aa9c039SMauro Carvalho Chehab                                             env=self.env)
7750aa9c039SMauro Carvalho Chehab
7760aa9c039SMauro Carvalho Chehab                    if result:
7770aa9c039SMauro Carvalho Chehab                        sys.exit(f"Build failed: return code: {result}")
7780aa9c039SMauro Carvalho Chehab
779819667bcSMauro Carvalho Chehab                except (OSError, ValueError, subprocess.SubprocessError) as e:
780819667bcSMauro Carvalho Chehab                    sys.exit(f"Build failed: {repr(e)}")
781819667bcSMauro Carvalho Chehab
782819667bcSMauro Carvalho Chehab            #
783819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
784819667bcSMauro Carvalho Chehab            #
785819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
786464257baSMauro Carvalho Chehab                self.handle_html(css, output_dir)
787819667bcSMauro Carvalho Chehab
788819667bcSMauro Carvalho Chehab        #
789819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
790819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
791819667bcSMauro Carvalho Chehab        #
792819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
793819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
794819667bcSMauro Carvalho Chehab        elif target == "infodocs":
795819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
796819667bcSMauro Carvalho Chehab
7972d652135SThomas Weißschuh        if rustdoc and target in ["htmldocs", "epubdocs"]:
7985094f7d5SThomas Weißschuh            print("Building rust docs")
7995094f7d5SThomas Weißschuh            if "MAKE" in self.env:
8005094f7d5SThomas Weißschuh                cmd = [self.env["MAKE"]]
8015094f7d5SThomas Weißschuh            else:
8025094f7d5SThomas Weißschuh                cmd = ["make", "LLVM=1"]
8035094f7d5SThomas Weißschuh
8045094f7d5SThomas Weißschuh            cmd += [ "rustdoc"]
8055094f7d5SThomas Weißschuh            if self.verbose:
8065094f7d5SThomas Weißschuh                print(" ".join(cmd))
8075094f7d5SThomas Weißschuh
8085094f7d5SThomas Weißschuh            try:
8095094f7d5SThomas Weißschuh                subprocess.run(cmd, check=True)
8105094f7d5SThomas Weißschuh            except subprocess.CalledProcessError as e:
8115094f7d5SThomas Weißschuh                print(f"Ignored errors when building rustdoc: {e}. Is RUST enabled?",
8125094f7d5SThomas Weißschuh                      file=sys.stderr)
8135094f7d5SThomas Weißschuh
814819667bcSMauro Carvalho Chehabdef jobs_type(value):
815819667bcSMauro Carvalho Chehab    """
816819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
817819667bcSMauro Carvalho Chehab    equal or bigger than one.
818819667bcSMauro Carvalho Chehab    """
819819667bcSMauro Carvalho Chehab    if value is None:
820819667bcSMauro Carvalho Chehab        return None
821819667bcSMauro Carvalho Chehab
822819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
823819667bcSMauro Carvalho Chehab        return value.lower()
824819667bcSMauro Carvalho Chehab
825819667bcSMauro Carvalho Chehab    try:
826819667bcSMauro Carvalho Chehab        if int(value) >= 1:
827819667bcSMauro Carvalho Chehab            return value
828819667bcSMauro Carvalho Chehab
829819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
830819667bcSMauro Carvalho Chehab    except ValueError:
831819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
832819667bcSMauro Carvalho Chehab
83364e4882cSMauro Carvalho ChehabEPILOG="""
83464e4882cSMauro Carvalho ChehabBesides the command line arguments, several environment variables affect its
83564e4882cSMauro Carvalho Chehabdefault behavior, meant to be used when called via Kernel Makefile:
83664e4882cSMauro Carvalho Chehab
83764e4882cSMauro Carvalho Chehab- KERNELVERSION:  Kernel major version
83864e4882cSMauro Carvalho Chehab- KERNELRELEASE:  Kernel release
83964e4882cSMauro Carvalho Chehab- KBUILD_VERBOSE: Contains the value of "make V=[0|1] variable.
84064e4882cSMauro Carvalho Chehab                  When V=0 (KBUILD_VERBOSE=0), sets verbose level to "-q".
84164e4882cSMauro Carvalho Chehab- SPHINXBUILD:    Documentation build tool (default: "sphinx-build").
84264e4882cSMauro Carvalho Chehab- SPHINXOPTS:     Extra options pased to SPHINXBUILD
84364e4882cSMauro Carvalho Chehab                  (default: "-j auto" and "-q" if KBUILD_VERBOSE=0).
84464e4882cSMauro Carvalho Chehab                  The "-v" flag can be used to increase verbosity.
84564e4882cSMauro Carvalho Chehab                  If V=0, the first "-v" will drop "-q".
84664e4882cSMauro Carvalho Chehab- PYTHON3:        Python command to run SPHINXBUILD
84764e4882cSMauro Carvalho Chehab- PDFLATEX:       LaTeX PDF engine. (default: "xelatex")
84864e4882cSMauro Carvalho Chehab- LATEXOPTS:      Optional set of command line arguments to the LaTeX engine
84964e4882cSMauro Carvalho Chehab- srctree:        Location of the Kernel root directory (default: ".").
85064e4882cSMauro Carvalho Chehab
85164e4882cSMauro Carvalho Chehab"""
85264e4882cSMauro Carvalho Chehab
853819667bcSMauro Carvalho Chehabdef main():
854819667bcSMauro Carvalho Chehab    """
855819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
856819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
857819667bcSMauro Carvalho Chehab    specified at os.environ.
858819667bcSMauro Carvalho Chehab    """
85964e4882cSMauro Carvalho Chehab    parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
86064e4882cSMauro Carvalho Chehab                                     description=__doc__,
86164e4882cSMauro Carvalho Chehab                                     epilog=EPILOG)
862819667bcSMauro Carvalho Chehab
863819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
864819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
865819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
866819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
867819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
86864e4882cSMauro Carvalho Chehab                        help="Sphinx configuration file (default: %(default)s)")
869819667bcSMauro Carvalho Chehab
870819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
871819667bcSMauro Carvalho Chehab
872819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
873819667bcSMauro Carvalho Chehab
874819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
875819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
876819667bcSMauro Carvalho Chehab
877819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
878819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
879819667bcSMauro Carvalho Chehab
880819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
881819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
882819667bcSMauro Carvalho Chehab
883819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
88464e4882cSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build(default: auto)")
885819667bcSMauro Carvalho Chehab
8862f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
8872f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
8882f99b85eSMauro Carvalho Chehab
8894c6ece91SMauro Carvalho Chehab    parser.add_argument('-s', '--skip-sphinx-build', action='store_true',
8904c6ece91SMauro Carvalho Chehab                        help="Skip sphinx-build step")
8914c6ece91SMauro Carvalho Chehab
89242180adaSMauro Carvalho Chehab    parser.add_argument("-V", "--venv", nargs='?', const=f'{VENV_DEFAULT}',
89342180adaSMauro Carvalho Chehab                        default=None,
89442180adaSMauro Carvalho Chehab                        help=f'If used, run Sphinx from a venv dir (default dir: {VENV_DEFAULT})')
89542180adaSMauro Carvalho Chehab
896819667bcSMauro Carvalho Chehab    args = parser.parse_args()
897819667bcSMauro Carvalho Chehab
89862ea383bSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION, show_alternatives=True,
89962ea383bSMauro Carvalho Chehab                               bail_out=True)
900819667bcSMauro Carvalho Chehab
90142180adaSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir, venv=args.venv,
9022f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
9032f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
904819667bcSMauro Carvalho Chehab
90572603d73SMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs,
906819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
907464257baSMauro Carvalho Chehab                  deny_vf=args.deny_vf,
9084c6ece91SMauro Carvalho Chehab                  skip_sphinx=args.skip_sphinx_build)
909819667bcSMauro Carvalho Chehab
910819667bcSMauro Carvalho Chehabif __name__ == "__main__":
911819667bcSMauro Carvalho Chehab    main()
912