xref: /linux/tools/docs/sphinx-build-wrapper (revision 778b8ebe5192e7a7f00563a7456517dfa63e1d90)
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
60*778b8ebeSJonathan 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))
64*778b8ebeSJonathan Corbetsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR + '/kdoc'))  # temporary
65819667bcSMauro Carvalho Chehab
66*778b8ebeSJonathan Corbetfrom python_version import PythonVersion
67*778b8ebeSJonathan Corbetfrom latex_fonts import LatexFontChecker
68819667bcSMauro Carvalho Chehabfrom jobserver import JobserverExec         # pylint: disable=C0413,C0411,E0401
69819667bcSMauro Carvalho Chehab
70819667bcSMauro Carvalho Chehab#
71819667bcSMauro Carvalho Chehab#  Some constants
72819667bcSMauro Carvalho Chehab#
7342180adaSMauro Carvalho ChehabVENV_DEFAULT = "sphinx_latest"
74819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version
75819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"]
76819667bcSMauro Carvalho Chehab
77819667bcSMauro Carvalho ChehabTARGETS = {
78819667bcSMauro Carvalho Chehab    "cleandocs":     { "builder": "clean" },
79819667bcSMauro Carvalho Chehab    "linkcheckdocs": { "builder": "linkcheck" },
80819667bcSMauro Carvalho Chehab    "htmldocs":      { "builder": "html" },
81819667bcSMauro Carvalho Chehab    "epubdocs":      { "builder": "epub",    "out_dir": "epub" },
82819667bcSMauro Carvalho Chehab    "texinfodocs":   { "builder": "texinfo", "out_dir": "texinfo" },
83819667bcSMauro Carvalho Chehab    "infodocs":      { "builder": "texinfo", "out_dir": "texinfo" },
847e8a8143SMauro Carvalho Chehab    "mandocs":       { "builder": "man",     "out_dir": "man" },
85819667bcSMauro Carvalho Chehab    "latexdocs":     { "builder": "latex",   "out_dir": "latex" },
86819667bcSMauro Carvalho Chehab    "pdfdocs":       { "builder": "latex",   "out_dir": "latex" },
87819667bcSMauro Carvalho Chehab    "xmldocs":       { "builder": "xml",     "out_dir": "xml" },
88819667bcSMauro Carvalho Chehab}
89819667bcSMauro Carvalho Chehab
90819667bcSMauro Carvalho Chehab
91819667bcSMauro Carvalho Chehab#
92819667bcSMauro Carvalho Chehab# SphinxBuilder class
93819667bcSMauro Carvalho Chehab#
94819667bcSMauro Carvalho Chehab
95819667bcSMauro Carvalho Chehabclass SphinxBuilder:
96819667bcSMauro Carvalho Chehab    """
97819667bcSMauro Carvalho Chehab    Handles a sphinx-build target, adding needed arguments to build
98819667bcSMauro Carvalho Chehab    with the Kernel.
99819667bcSMauro Carvalho Chehab    """
100819667bcSMauro Carvalho Chehab
101819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
102819667bcSMauro Carvalho Chehab        """
103819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
104819667bcSMauro Carvalho Chehab
105819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
106819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
107819667bcSMauro Carvalho Chehab        """
108819667bcSMauro Carvalho Chehab
109819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
110819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
111819667bcSMauro Carvalho Chehab            if use_cwd:
112819667bcSMauro Carvalho Chehab                base = os.getcwd()
113819667bcSMauro Carvalho Chehab            else:
114819667bcSMauro Carvalho Chehab                base = self.srctree
115819667bcSMauro Carvalho Chehab
116819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
117819667bcSMauro Carvalho Chehab
118819667bcSMauro Carvalho Chehab        if abs_path:
119819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
120819667bcSMauro Carvalho Chehab
121819667bcSMauro Carvalho Chehab        return path
122819667bcSMauro Carvalho Chehab
123819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
124819667bcSMauro Carvalho Chehab        """
125819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
126819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
127819667bcSMauro Carvalho Chehab
128819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
129819667bcSMauro Carvalho Chehab
130819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
131819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
132819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
133819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
134819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
135819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
136819667bcSMauro Carvalho Chehab        """
137819667bcSMauro Carvalho Chehab
138819667bcSMauro Carvalho Chehab        #
139819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
140819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
141819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
142819667bcSMauro Carvalho Chehab        #
143819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
144819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
145e123e00aSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', action='store_true')
146819667bcSMauro Carvalho Chehab
147819667bcSMauro Carvalho Chehab        #
148819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
149819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
150819667bcSMauro Carvalho Chehab        #
151819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
152819667bcSMauro Carvalho Chehab
153819667bcSMauro Carvalho Chehab        #
154819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
155819667bcSMauro Carvalho Chehab        #
156819667bcSMauro Carvalho Chehab
157819667bcSMauro Carvalho Chehab        verbose = self.verbose
158819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
159819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
160819667bcSMauro Carvalho Chehab            verbose = False
161819667bcSMauro Carvalho Chehab
162819667bcSMauro Carvalho Chehab        #
163819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
164819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
165819667bcSMauro Carvalho Chehab        #
166819667bcSMauro Carvalho Chehab        if n_jobs:
167819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
168819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
169819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
170819667bcSMauro Carvalho Chehab        else:
171819667bcSMauro Carvalho Chehab            self.n_jobs = None
172819667bcSMauro Carvalho Chehab
173819667bcSMauro Carvalho Chehab        if not verbose:
174819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
175819667bcSMauro Carvalho Chehab
17642180adaSMauro Carvalho Chehab    def __init__(self, builddir, venv=None, verbose=False, n_jobs=None,
17742180adaSMauro Carvalho Chehab                 interactive=None):
178819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
17942180adaSMauro Carvalho Chehab        self.venv = venv
180819667bcSMauro Carvalho Chehab        self.verbose = None
181819667bcSMauro Carvalho Chehab
182819667bcSMauro Carvalho Chehab        #
183819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
184819667bcSMauro Carvalho Chehab        #
185819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
186819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
187819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
1882f99b85eSMauro Carvalho Chehab
18935b9d338SMauro Carvalho Chehab        #
19035b9d338SMauro Carvalho Chehab        # Kernel main Makefile defines a PYTHON3 variable whose default is
19135b9d338SMauro Carvalho Chehab        # "python3". When set to a different value, it allows running a
19235b9d338SMauro Carvalho Chehab        # diferent version than the default official python3 package.
19335b9d338SMauro Carvalho Chehab        # Several distros package python3xx-sphinx packages with newer
19435b9d338SMauro Carvalho Chehab        # versions of Python and sphinx-build.
19535b9d338SMauro Carvalho Chehab        #
19635b9d338SMauro Carvalho Chehab        # Honor such variable different than default
19735b9d338SMauro Carvalho Chehab        #
19835b9d338SMauro Carvalho Chehab        self.python = os.environ.get("PYTHON3")
19935b9d338SMauro Carvalho Chehab        if self.python == "python3":
20035b9d338SMauro Carvalho Chehab            self.python = None
20135b9d338SMauro Carvalho Chehab
2022f99b85eSMauro Carvalho Chehab        if not interactive:
203819667bcSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
2042f99b85eSMauro Carvalho Chehab        else:
2052f99b85eSMauro Carvalho Chehab            self.latexopts = os.environ.get("LATEXOPTS", "")
206819667bcSMauro Carvalho Chehab
207819667bcSMauro Carvalho Chehab        if not verbose:
208819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
209819667bcSMauro Carvalho Chehab
210819667bcSMauro Carvalho Chehab        if verbose is not None:
211819667bcSMauro Carvalho Chehab            self.verbose = verbose
212819667bcSMauro Carvalho Chehab
213819667bcSMauro Carvalho Chehab        #
214819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
215819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
216819667bcSMauro Carvalho Chehab        #
217819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
218819667bcSMauro Carvalho Chehab        if not self.srctree:
219819667bcSMauro Carvalho Chehab            self.srctree = "."
220819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
221819667bcSMauro Carvalho Chehab
222819667bcSMauro Carvalho Chehab        #
223819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
224819667bcSMauro Carvalho Chehab        #
225819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
226819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
227819667bcSMauro Carvalho Chehab                                                      "scripts/kernel-doc.py"))
228819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
229819667bcSMauro Carvalho Chehab
230819667bcSMauro Carvalho Chehab        #
231819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
232819667bcSMauro Carvalho Chehab        #
233819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
234819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
235819667bcSMauro Carvalho Chehab
236819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
237819667bcSMauro Carvalho Chehab
238819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
239819667bcSMauro Carvalho Chehab
24042180adaSMauro Carvalho Chehab        #
24142180adaSMauro Carvalho Chehab        # If venv command line argument is specified, run Sphinx from venv
24242180adaSMauro Carvalho Chehab        #
24342180adaSMauro Carvalho Chehab        if venv:
24442180adaSMauro Carvalho Chehab            bin_dir = os.path.join(venv, "bin")
24542180adaSMauro Carvalho Chehab            if not os.path.isfile(os.path.join(bin_dir, "activate")):
24642180adaSMauro Carvalho Chehab                sys.exit(f"Venv {venv} not found.")
24742180adaSMauro Carvalho Chehab
24842180adaSMauro Carvalho Chehab            # "activate" virtual env
24942180adaSMauro Carvalho Chehab            self.env["PATH"] = bin_dir + ":" + self.env["PATH"]
25042180adaSMauro Carvalho Chehab            self.env["VIRTUAL_ENV"] = venv
25142180adaSMauro Carvalho Chehab            if "PYTHONHOME" in self.env:
25242180adaSMauro Carvalho Chehab                del self.env["PYTHONHOME"]
25342180adaSMauro Carvalho Chehab            print(f"Setting venv to {venv}")
25442180adaSMauro Carvalho Chehab
255819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
256819667bcSMauro Carvalho Chehab        """
257819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
258819667bcSMauro Carvalho Chehab
259819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
260819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
261819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
262819667bcSMauro Carvalho Chehab        jobs.
263819667bcSMauro Carvalho Chehab
264819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
265819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
266819667bcSMauro Carvalho Chehab
267819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
268819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
269819667bcSMauro Carvalho Chehab        """
270819667bcSMauro Carvalho Chehab
271819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
272819667bcSMauro Carvalho Chehab            if jobserver.claim:
273819667bcSMauro Carvalho Chehab                #
274819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
275819667bcSMauro Carvalho Chehab                #
276819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
277819667bcSMauro Carvalho Chehab            else:
278819667bcSMauro Carvalho Chehab                #
279819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
280819667bcSMauro Carvalho Chehab                #
281819667bcSMauro Carvalho Chehab                n_jobs = "auto"
282819667bcSMauro Carvalho Chehab
283819667bcSMauro Carvalho Chehab            #
284819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
285819667bcSMauro Carvalho Chehab            #
286819667bcSMauro Carvalho Chehab            if self.n_jobs:
287819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
288819667bcSMauro Carvalho Chehab
28935b9d338SMauro Carvalho Chehab            #
29035b9d338SMauro Carvalho Chehab            # We can't simply call python3 sphinx-build, as OpenSUSE
29135b9d338SMauro Carvalho Chehab            # Tumbleweed uses an ELF binary file (/usr/bin/alts) to switch
29235b9d338SMauro Carvalho Chehab            # between different versions of sphinx-build. So, only call it
29335b9d338SMauro Carvalho Chehab            # prepending "python3.xx" when PYTHON3 variable is not default.
29435b9d338SMauro Carvalho Chehab            #
29535b9d338SMauro Carvalho Chehab            if self.python:
29635b9d338SMauro Carvalho Chehab                cmd = [self.python]
29742180adaSMauro Carvalho Chehab            else:
29835b9d338SMauro Carvalho Chehab                cmd = []
29942180adaSMauro Carvalho Chehab
30042180adaSMauro Carvalho Chehab            cmd += [sphinx_build]
301819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
302819667bcSMauro Carvalho Chehab            cmd += build_args
3031f6e3f21SAkira Yokosawa            cmd += self.sphinxopts
304819667bcSMauro Carvalho Chehab
305819667bcSMauro Carvalho Chehab            if self.verbose:
306819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
307819667bcSMauro Carvalho Chehab
308819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
309819667bcSMauro Carvalho Chehab
3102118ba7dSMauro Carvalho Chehab    def handle_html(self, css, output_dir, rustdoc):
311819667bcSMauro Carvalho Chehab        """
312819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
313819667bcSMauro Carvalho Chehab
314819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
315819667bcSMauro Carvalho Chehab        copied to the output _static directory
316819667bcSMauro Carvalho Chehab        """
317819667bcSMauro Carvalho Chehab
3182118ba7dSMauro Carvalho Chehab        if css:
319819667bcSMauro Carvalho Chehab            css = os.path.expanduser(css)
320819667bcSMauro Carvalho Chehab            if not css.startswith("/"):
321819667bcSMauro Carvalho Chehab                css = os.path.join(self.srctree, css)
322819667bcSMauro Carvalho Chehab
323819667bcSMauro Carvalho Chehab            static_dir = os.path.join(output_dir, "_static")
324819667bcSMauro Carvalho Chehab            os.makedirs(static_dir, exist_ok=True)
325819667bcSMauro Carvalho Chehab
326819667bcSMauro Carvalho Chehab            try:
327819667bcSMauro Carvalho Chehab                shutil.copy2(css, static_dir)
328819667bcSMauro Carvalho Chehab            except (OSError, IOError) as e:
329819667bcSMauro Carvalho Chehab                print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
330819667bcSMauro Carvalho Chehab
3312118ba7dSMauro Carvalho Chehab        if rustdoc:
3322118ba7dSMauro Carvalho Chehab            if "MAKE" in self.env:
3332118ba7dSMauro Carvalho Chehab                cmd = [self.env["MAKE"]]
3342118ba7dSMauro Carvalho Chehab            else:
3352118ba7dSMauro Carvalho Chehab                cmd = ["make", "LLVM=1"]
3362118ba7dSMauro Carvalho Chehab
3372118ba7dSMauro Carvalho Chehab            cmd += [ "rustdoc"]
3382118ba7dSMauro Carvalho Chehab            if self.verbose:
3392118ba7dSMauro Carvalho Chehab                print(" ".join(cmd))
3402118ba7dSMauro Carvalho Chehab
3412118ba7dSMauro Carvalho Chehab            try:
3422118ba7dSMauro Carvalho Chehab                subprocess.run(cmd, check=True)
3432118ba7dSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
3442118ba7dSMauro Carvalho Chehab                print(f"Ignored errors when building rustdoc: {e}. Is RUST enabled?",
3452118ba7dSMauro Carvalho Chehab                      file=sys.stderr)
3462118ba7dSMauro Carvalho Chehab
34708e14bc1SMauro Carvalho Chehab    def build_pdf_file(self, latex_cmd, from_dir, path):
34808e14bc1SMauro Carvalho Chehab        """Builds a single pdf file using latex_cmd"""
34908e14bc1SMauro Carvalho Chehab        try:
35008e14bc1SMauro Carvalho Chehab            subprocess.run(latex_cmd + [path],
35108e14bc1SMauro Carvalho Chehab                            cwd=from_dir, check=True, env=self.env)
35208e14bc1SMauro Carvalho Chehab
35308e14bc1SMauro Carvalho Chehab            return True
35408e14bc1SMauro Carvalho Chehab        except subprocess.CalledProcessError:
35508e14bc1SMauro Carvalho Chehab            return False
35608e14bc1SMauro Carvalho Chehab
35708e14bc1SMauro Carvalho Chehab    def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
35808e14bc1SMauro Carvalho Chehab        """Build PDF files in parallel if possible"""
35908e14bc1SMauro Carvalho Chehab        builds = {}
36008e14bc1SMauro Carvalho Chehab        build_failed = False
36108e14bc1SMauro Carvalho Chehab        max_len = 0
36208e14bc1SMauro Carvalho Chehab        has_tex = False
36308e14bc1SMauro Carvalho Chehab
36408e14bc1SMauro Carvalho Chehab        #
36508e14bc1SMauro Carvalho Chehab        # LaTeX PDF error code is almost useless for us:
36608e14bc1SMauro Carvalho Chehab        # any warning makes it non-zero. For kernel doc builds it always return
36708e14bc1SMauro Carvalho Chehab        # non-zero even when build succeeds. So, let's do the best next thing:
36808e14bc1SMauro Carvalho Chehab        # Ignore build errors. At the end, check if all PDF files were built,
36908e14bc1SMauro Carvalho Chehab        # printing a summary with the built ones and returning 0 if all of
37008e14bc1SMauro Carvalho Chehab        # them were actually built.
37108e14bc1SMauro Carvalho Chehab        #
37208e14bc1SMauro Carvalho Chehab        with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
37308e14bc1SMauro Carvalho Chehab            jobs = {}
37408e14bc1SMauro Carvalho Chehab
37508e14bc1SMauro Carvalho Chehab            for from_dir, pdf_dir, entry in tex_files:
37608e14bc1SMauro Carvalho Chehab                name = entry.name
37708e14bc1SMauro Carvalho Chehab
37808e14bc1SMauro Carvalho Chehab                if not name.endswith(tex_suffix):
37908e14bc1SMauro Carvalho Chehab                    continue
38008e14bc1SMauro Carvalho Chehab
38108e14bc1SMauro Carvalho Chehab                name = name[:-len(tex_suffix)]
38208e14bc1SMauro Carvalho Chehab                has_tex = True
38308e14bc1SMauro Carvalho Chehab
38408e14bc1SMauro Carvalho Chehab                future = executor.submit(self.build_pdf_file, latex_cmd,
38508e14bc1SMauro Carvalho Chehab                                         from_dir, entry.path)
38608e14bc1SMauro Carvalho Chehab                jobs[future] = (from_dir, pdf_dir, name)
38708e14bc1SMauro Carvalho Chehab
38808e14bc1SMauro Carvalho Chehab            for future in futures.as_completed(jobs):
38908e14bc1SMauro Carvalho Chehab                from_dir, pdf_dir, name = jobs[future]
39008e14bc1SMauro Carvalho Chehab
39108e14bc1SMauro Carvalho Chehab                pdf_name = name + ".pdf"
39208e14bc1SMauro Carvalho Chehab                pdf_from = os.path.join(from_dir, pdf_name)
3930d9abc76SMauro Carvalho Chehab                pdf_to = os.path.join(pdf_dir, pdf_name)
3940d9abc76SMauro Carvalho Chehab                out_name = os.path.relpath(pdf_to, self.builddir)
3950d9abc76SMauro Carvalho Chehab                max_len = max(max_len, len(out_name))
39608e14bc1SMauro Carvalho Chehab
39708e14bc1SMauro Carvalho Chehab                try:
39808e14bc1SMauro Carvalho Chehab                    success = future.result()
39908e14bc1SMauro Carvalho Chehab
40008e14bc1SMauro Carvalho Chehab                    if success and os.path.exists(pdf_from):
40108e14bc1SMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
40208e14bc1SMauro Carvalho Chehab
40308e14bc1SMauro Carvalho Chehab                        #
40408e14bc1SMauro Carvalho Chehab                        # if verbose, get the name of built PDF file
40508e14bc1SMauro Carvalho Chehab                        #
40608e14bc1SMauro Carvalho Chehab                        if self.verbose:
4070d9abc76SMauro Carvalho Chehab                           builds[out_name] = "SUCCESS"
40808e14bc1SMauro Carvalho Chehab                    else:
4090d9abc76SMauro Carvalho Chehab                        builds[out_name] = "FAILED"
41008e14bc1SMauro Carvalho Chehab                        build_failed = True
41108e14bc1SMauro Carvalho Chehab                except futures.Error as e:
4120d9abc76SMauro Carvalho Chehab                    builds[out_name] = f"FAILED ({repr(e)})"
41308e14bc1SMauro Carvalho Chehab                    build_failed = True
41408e14bc1SMauro Carvalho Chehab
41508e14bc1SMauro Carvalho Chehab        #
41608e14bc1SMauro Carvalho Chehab        # Handle case where no .tex files were found
41708e14bc1SMauro Carvalho Chehab        #
41808e14bc1SMauro Carvalho Chehab        if not has_tex:
4190d9abc76SMauro Carvalho Chehab            out_name = "LaTeX files"
4200d9abc76SMauro Carvalho Chehab            max_len = max(max_len, len(out_name))
4210d9abc76SMauro Carvalho Chehab            builds[out_name] = "FAILED: no .tex files were generated"
42208e14bc1SMauro Carvalho Chehab            build_failed = True
42308e14bc1SMauro Carvalho Chehab
42408e14bc1SMauro Carvalho Chehab        return builds, build_failed, max_len
42508e14bc1SMauro Carvalho Chehab
426819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
427819667bcSMauro Carvalho Chehab        """
428819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
429819667bcSMauro Carvalho Chehab
430819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
431819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
432819667bcSMauro Carvalho Chehab        directory.
433819667bcSMauro Carvalho Chehab        """
434819667bcSMauro Carvalho Chehab        builds = {}
435819667bcSMauro Carvalho Chehab        max_len = 0
43608e14bc1SMauro Carvalho Chehab        tex_suffix = ".tex"
43708e14bc1SMauro Carvalho Chehab        tex_files = []
438819667bcSMauro Carvalho Chehab
439819667bcSMauro Carvalho Chehab        #
440819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
441819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
442819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
443819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
444819667bcSMauro Carvalho Chehab        # file with a deny list.
445819667bcSMauro Carvalho Chehab        #
446819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
447819667bcSMauro Carvalho Chehab        #
448819667bcSMauro Carvalho Chehab        if deny_vf:
449819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
450819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
451819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
452819667bcSMauro Carvalho Chehab
453819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
454819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
455819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
456819667bcSMauro Carvalho Chehab
457819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
458819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
459819667bcSMauro Carvalho Chehab            else:
460819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
461819667bcSMauro Carvalho Chehab
462819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
463819667bcSMauro Carvalho Chehab
46408e14bc1SMauro Carvalho Chehab            # Get a list of tex files to process
465819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
466819667bcSMauro Carvalho Chehab                for entry in it:
46708e14bc1SMauro Carvalho Chehab                    if entry.name.endswith(tex_suffix):
46808e14bc1SMauro Carvalho Chehab                        tex_files.append((from_dir, pdf_dir, entry))
469819667bcSMauro Carvalho Chehab
470819667bcSMauro Carvalho Chehab        #
47108e14bc1SMauro Carvalho Chehab        # When using make, this won't be used, as the number of jobs comes
47208e14bc1SMauro Carvalho Chehab        # from POSIX jobserver. So, this covers the case where build comes
47308e14bc1SMauro Carvalho Chehab        # from command line. On such case, serialize by default, except if
47408e14bc1SMauro Carvalho Chehab        # the user explicitly sets the number of jobs.
475819667bcSMauro Carvalho Chehab        #
47608e14bc1SMauro Carvalho Chehab        n_jobs = 1
47708e14bc1SMauro Carvalho Chehab
47808e14bc1SMauro Carvalho Chehab        # n_jobs is either an integer or "auto". Only use it if it is a number
47908e14bc1SMauro Carvalho Chehab        if self.n_jobs:
480819667bcSMauro Carvalho Chehab            try:
48108e14bc1SMauro Carvalho Chehab                n_jobs = int(self.n_jobs)
48208e14bc1SMauro Carvalho Chehab            except ValueError:
483819667bcSMauro Carvalho Chehab                pass
484819667bcSMauro Carvalho Chehab
48508e14bc1SMauro Carvalho Chehab        #
48608e14bc1SMauro Carvalho Chehab        # When using make, jobserver.claim is the number of jobs that were
48708e14bc1SMauro Carvalho Chehab        # used with "-j" and that aren't used by other make targets
48808e14bc1SMauro Carvalho Chehab        #
48908e14bc1SMauro Carvalho Chehab        with JobserverExec() as jobserver:
49008e14bc1SMauro Carvalho Chehab            n_jobs = 1
491819667bcSMauro Carvalho Chehab
49208e14bc1SMauro Carvalho Chehab            #
49308e14bc1SMauro Carvalho Chehab            # Handle the case when a parameter is passed via command line,
49408e14bc1SMauro Carvalho Chehab            # using it as default, if jobserver doesn't claim anything
49508e14bc1SMauro Carvalho Chehab            #
49608e14bc1SMauro Carvalho Chehab            if self.n_jobs:
49708e14bc1SMauro Carvalho Chehab                try:
49808e14bc1SMauro Carvalho Chehab                    n_jobs = int(self.n_jobs)
49908e14bc1SMauro Carvalho Chehab                except ValueError:
50008e14bc1SMauro Carvalho Chehab                    pass
501819667bcSMauro Carvalho Chehab
50208e14bc1SMauro Carvalho Chehab            if jobserver.claim:
50308e14bc1SMauro Carvalho Chehab                n_jobs = jobserver.claim
504819667bcSMauro Carvalho Chehab
50508e14bc1SMauro Carvalho Chehab            builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
50608e14bc1SMauro Carvalho Chehab                                                                    latex_cmd,
50708e14bc1SMauro Carvalho Chehab                                                                    tex_files,
50808e14bc1SMauro Carvalho Chehab                                                                    n_jobs)
509819667bcSMauro Carvalho Chehab
51008e14bc1SMauro Carvalho Chehab        #
51108e14bc1SMauro Carvalho Chehab        # In verbose mode, print a summary with the build results per file.
51208e14bc1SMauro Carvalho Chehab        # Otherwise, print a single line with all failures, if any.
51308e14bc1SMauro Carvalho Chehab        # On both cases, return code 1 indicates build failures,
51408e14bc1SMauro Carvalho Chehab        #
51508e14bc1SMauro Carvalho Chehab        if self.verbose:
516819667bcSMauro Carvalho Chehab            msg = "Summary"
517819667bcSMauro Carvalho Chehab            msg += "\n" + "=" * len(msg)
518819667bcSMauro Carvalho Chehab            print()
519819667bcSMauro Carvalho Chehab            print(msg)
520819667bcSMauro Carvalho Chehab
521819667bcSMauro Carvalho Chehab            for pdf_name, pdf_file in builds.items():
522819667bcSMauro Carvalho Chehab                print(f"{pdf_name:<{max_len}}: {pdf_file}")
523819667bcSMauro Carvalho Chehab
524819667bcSMauro Carvalho Chehab            print()
525819667bcSMauro Carvalho Chehab            if build_failed:
526819667bcSMauro Carvalho Chehab                msg = LatexFontChecker().check()
527819667bcSMauro Carvalho Chehab                if msg:
528819667bcSMauro Carvalho Chehab                    print(msg)
529819667bcSMauro Carvalho Chehab
53008e14bc1SMauro Carvalho Chehab                sys.exit("Error: not all PDF files were created.")
53108e14bc1SMauro Carvalho Chehab
53208e14bc1SMauro Carvalho Chehab        elif build_failed:
53308e14bc1SMauro Carvalho Chehab            n_failures = len(builds)
53408e14bc1SMauro Carvalho Chehab            failures = ", ".join(builds.keys())
53508e14bc1SMauro Carvalho Chehab
53608e14bc1SMauro Carvalho Chehab            msg = LatexFontChecker().check()
53708e14bc1SMauro Carvalho Chehab            if msg:
53808e14bc1SMauro Carvalho Chehab                print(msg)
53908e14bc1SMauro Carvalho Chehab
54008e14bc1SMauro Carvalho Chehab            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
541819667bcSMauro Carvalho Chehab
542819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
543819667bcSMauro Carvalho Chehab        """
544819667bcSMauro Carvalho Chehab        Extra steps for Info output.
545819667bcSMauro Carvalho Chehab
546819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
547819667bcSMauro Carvalho Chehab        texinfo directory.
548819667bcSMauro Carvalho Chehab        """
549819667bcSMauro Carvalho Chehab
550819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
551819667bcSMauro Carvalho Chehab            try:
552819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
553819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
554819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
555819667bcSMauro Carvalho Chehab
5567e8a8143SMauro Carvalho Chehab    def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
5577e8a8143SMauro Carvalho Chehab        """
5587e8a8143SMauro Carvalho Chehab        Create man pages from kernel-doc output
5597e8a8143SMauro Carvalho Chehab        """
5607e8a8143SMauro Carvalho Chehab
5617e8a8143SMauro Carvalho Chehab        re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
5627e8a8143SMauro Carvalho Chehab        re_man = re.compile(r'^\.TH "[^"]*" (\d+) "([^"]*)"')
5637e8a8143SMauro Carvalho Chehab
5647e8a8143SMauro Carvalho Chehab        if docs_dir == src_dir:
5657e8a8143SMauro Carvalho Chehab            #
5667e8a8143SMauro Carvalho Chehab            # Pick the entire set of kernel-doc markups from the entire tree
5677e8a8143SMauro Carvalho Chehab            #
5687e8a8143SMauro Carvalho Chehab            kdoc_files = set([self.srctree])
5697e8a8143SMauro Carvalho Chehab        else:
5707e8a8143SMauro Carvalho Chehab            kdoc_files = set()
5717e8a8143SMauro Carvalho Chehab
5727e8a8143SMauro Carvalho Chehab            for fname in glob(os.path.join(src_dir, "**"), recursive=True):
5737e8a8143SMauro Carvalho Chehab                if os.path.isfile(fname) and fname.endswith(".rst"):
5747e8a8143SMauro Carvalho Chehab                    with open(fname, "r", encoding="utf-8") as in_fp:
5757e8a8143SMauro Carvalho Chehab                        data = in_fp.read()
5767e8a8143SMauro Carvalho Chehab
5777e8a8143SMauro Carvalho Chehab                    for line in data.split("\n"):
5787e8a8143SMauro Carvalho Chehab                        match = re_kernel_doc.match(line)
5797e8a8143SMauro Carvalho Chehab                        if match:
5807e8a8143SMauro Carvalho Chehab                            if os.path.isfile(match.group(1)):
5817e8a8143SMauro Carvalho Chehab                                kdoc_files.add(match.group(1))
5827e8a8143SMauro Carvalho Chehab
5837e8a8143SMauro Carvalho Chehab        if not kdoc_files:
5847e8a8143SMauro Carvalho Chehab                sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
5857e8a8143SMauro Carvalho Chehab
5867e8a8143SMauro Carvalho Chehab        cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
5877e8a8143SMauro Carvalho Chehab        try:
5887e8a8143SMauro Carvalho Chehab            if self.verbose:
5897e8a8143SMauro Carvalho Chehab                print(" ".join(cmd))
5907e8a8143SMauro Carvalho Chehab
5917e8a8143SMauro Carvalho Chehab            result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
5927e8a8143SMauro Carvalho Chehab
5937e8a8143SMauro Carvalho Chehab            if result.returncode:
5947e8a8143SMauro Carvalho Chehab                print(f"Warning: kernel-doc returned {result.returncode} warnings")
5957e8a8143SMauro Carvalho Chehab
5967e8a8143SMauro Carvalho Chehab        except (OSError, ValueError, subprocess.SubprocessError) as e:
5977e8a8143SMauro Carvalho Chehab            sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
5987e8a8143SMauro Carvalho Chehab
5997e8a8143SMauro Carvalho Chehab        fp = None
6007e8a8143SMauro Carvalho Chehab        try:
6017e8a8143SMauro Carvalho Chehab            for line in result.stdout.split("\n"):
6027e8a8143SMauro Carvalho Chehab                match = re_man.match(line)
6037e8a8143SMauro Carvalho Chehab                if not match:
6047e8a8143SMauro Carvalho Chehab                    if fp:
6057e8a8143SMauro Carvalho Chehab                        fp.write(line + '\n')
6067e8a8143SMauro Carvalho Chehab                    continue
6077e8a8143SMauro Carvalho Chehab
6087e8a8143SMauro Carvalho Chehab                if fp:
6097e8a8143SMauro Carvalho Chehab                    fp.close()
6107e8a8143SMauro Carvalho Chehab
6117e8a8143SMauro Carvalho Chehab                fname = f"{output_dir}/{match.group(2)}.{match.group(1)}"
6127e8a8143SMauro Carvalho Chehab
6137e8a8143SMauro Carvalho Chehab                if self.verbose:
6147e8a8143SMauro Carvalho Chehab                    print(f"Creating {fname}")
6157e8a8143SMauro Carvalho Chehab                fp = open(fname, "w", encoding="utf-8")
6167e8a8143SMauro Carvalho Chehab                fp.write(line + '\n')
6177e8a8143SMauro Carvalho Chehab        finally:
6187e8a8143SMauro Carvalho Chehab            if fp:
6197e8a8143SMauro Carvalho Chehab                fp.close()
6207e8a8143SMauro Carvalho Chehab
621819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
622819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
623819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
624819667bcSMauro Carvalho Chehab
62572603d73SMauro Carvalho Chehab    def build(self, target, sphinxdirs=None,
6264c6ece91SMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None, rustdoc=False,
6274c6ece91SMauro Carvalho Chehab              skip_sphinx=False):
628819667bcSMauro Carvalho Chehab        """
629819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
630819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
631819667bcSMauro Carvalho Chehab        """
632819667bcSMauro Carvalho Chehab
633819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
634819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
635819667bcSMauro Carvalho Chehab
636819667bcSMauro Carvalho Chehab        #
637819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
638819667bcSMauro Carvalho Chehab        #
639819667bcSMauro Carvalho Chehab        if target == "cleandocs":
640819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
641819667bcSMauro Carvalho Chehab            return
642819667bcSMauro Carvalho Chehab
643819667bcSMauro Carvalho Chehab        if theme:
644819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
645819667bcSMauro Carvalho Chehab
646819667bcSMauro Carvalho Chehab        #
647819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
648819667bcSMauro Carvalho Chehab        #
6494c6ece91SMauro Carvalho Chehab        if not skip_sphinx:
650819667bcSMauro Carvalho Chehab            sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
6517e8a8143SMauro Carvalho Chehab            if not sphinxbuild and target != "mandocs":
652819667bcSMauro Carvalho Chehab                sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
653819667bcSMauro Carvalho Chehab
6545401f971SMauro Carvalho Chehab        if target == "pdfdocs":
655819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
656819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
657819667bcSMauro Carvalho Chehab
658819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
659819667bcSMauro Carvalho Chehab
660819667bcSMauro Carvalho Chehab        #
661819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
662819667bcSMauro Carvalho Chehab        #
663819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
664819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
665819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
666819667bcSMauro Carvalho Chehab
667819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
668819667bcSMauro Carvalho Chehab
669819667bcSMauro Carvalho Chehab        if builder == "latex":
670819667bcSMauro Carvalho Chehab            if not paper:
671819667bcSMauro Carvalho Chehab                paper = PAPER[1]
672819667bcSMauro Carvalho Chehab
673819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
674819667bcSMauro Carvalho Chehab
6752118ba7dSMauro Carvalho Chehab        if rustdoc:
676819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
677819667bcSMauro Carvalho Chehab
678819667bcSMauro Carvalho Chehab        if not sphinxdirs:
679819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
680819667bcSMauro Carvalho Chehab
681819667bcSMauro Carvalho Chehab        #
68282c294d4SMauro Carvalho Chehab        # The sphinx-build tool has a bug: internally, it tries to set
68382c294d4SMauro Carvalho Chehab        # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
68482c294d4SMauro Carvalho Chehab        # crash if language is not set. Detect and fix it.
68582c294d4SMauro Carvalho Chehab        #
68682c294d4SMauro Carvalho Chehab        try:
68782c294d4SMauro Carvalho Chehab            locale.setlocale(locale.LC_ALL, '')
68882c294d4SMauro Carvalho Chehab        except locale.Error:
68982c294d4SMauro Carvalho Chehab            self.env["LC_ALL"] = "C"
69082c294d4SMauro Carvalho Chehab
69182c294d4SMauro Carvalho Chehab        #
692819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
693819667bcSMauro Carvalho Chehab        #
694819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
695819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
696819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
697819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
698819667bcSMauro Carvalho Chehab            else:
699819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
700819667bcSMauro Carvalho Chehab
701819667bcSMauro Carvalho Chehab        #
702819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
703819667bcSMauro Carvalho Chehab        #
704819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
705819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
706819667bcSMauro Carvalho Chehab        # the beginning.
707819667bcSMauro Carvalho Chehab        #
708819667bcSMauro Carvalho Chehab        output_dirs = []
709819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
710819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
711819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
712819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
713819667bcSMauro Carvalho Chehab
714819667bcSMauro Carvalho Chehab            #
715819667bcSMauro Carvalho Chehab            # Make directory names canonical
716819667bcSMauro Carvalho Chehab            #
717819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
718819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
719819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
720819667bcSMauro Carvalho Chehab
721819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
722819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
723819667bcSMauro Carvalho Chehab
724819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
725819667bcSMauro Carvalho Chehab
726819667bcSMauro Carvalho Chehab            build_args = args + [
727819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
728819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_bin={kerneldoc}",
729819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
730819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
731819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
732819667bcSMauro Carvalho Chehab                src_dir,
733819667bcSMauro Carvalho Chehab                output_dir,
734819667bcSMauro Carvalho Chehab            ]
735819667bcSMauro Carvalho Chehab
7367e8a8143SMauro Carvalho Chehab            if target == "mandocs":
7377e8a8143SMauro Carvalho Chehab                self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
7384c6ece91SMauro Carvalho Chehab            elif not skip_sphinx:
739819667bcSMauro Carvalho Chehab                try:
7400aa9c039SMauro Carvalho Chehab                    result = self.run_sphinx(sphinxbuild, build_args,
7410aa9c039SMauro Carvalho Chehab                                             env=self.env)
7420aa9c039SMauro Carvalho Chehab
7430aa9c039SMauro Carvalho Chehab                    if result:
7440aa9c039SMauro Carvalho Chehab                        sys.exit(f"Build failed: return code: {result}")
7450aa9c039SMauro Carvalho Chehab
746819667bcSMauro Carvalho Chehab                except (OSError, ValueError, subprocess.SubprocessError) as e:
747819667bcSMauro Carvalho Chehab                    sys.exit(f"Build failed: {repr(e)}")
748819667bcSMauro Carvalho Chehab
749819667bcSMauro Carvalho Chehab            #
750819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
751819667bcSMauro Carvalho Chehab            #
752819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
7532118ba7dSMauro Carvalho Chehab                self.handle_html(css, output_dir, rustdoc)
754819667bcSMauro Carvalho Chehab
755819667bcSMauro Carvalho Chehab        #
756819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
757819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
758819667bcSMauro Carvalho Chehab        #
759819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
760819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
761819667bcSMauro Carvalho Chehab        elif target == "infodocs":
762819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
763819667bcSMauro Carvalho Chehab
764819667bcSMauro Carvalho Chehabdef jobs_type(value):
765819667bcSMauro Carvalho Chehab    """
766819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
767819667bcSMauro Carvalho Chehab    equal or bigger than one.
768819667bcSMauro Carvalho Chehab    """
769819667bcSMauro Carvalho Chehab    if value is None:
770819667bcSMauro Carvalho Chehab        return None
771819667bcSMauro Carvalho Chehab
772819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
773819667bcSMauro Carvalho Chehab        return value.lower()
774819667bcSMauro Carvalho Chehab
775819667bcSMauro Carvalho Chehab    try:
776819667bcSMauro Carvalho Chehab        if int(value) >= 1:
777819667bcSMauro Carvalho Chehab            return value
778819667bcSMauro Carvalho Chehab
779819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
780819667bcSMauro Carvalho Chehab    except ValueError:
781819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
782819667bcSMauro Carvalho Chehab
783819667bcSMauro Carvalho Chehabdef main():
784819667bcSMauro Carvalho Chehab    """
785819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
786819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
787819667bcSMauro Carvalho Chehab    specified at os.environ.
788819667bcSMauro Carvalho Chehab    """
789819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
790819667bcSMauro Carvalho Chehab
791819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
792819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
793819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
794819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
795819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
796819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
797819667bcSMauro Carvalho Chehab
798819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
799819667bcSMauro Carvalho Chehab
800819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
801819667bcSMauro Carvalho Chehab
802819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
803819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
804819667bcSMauro Carvalho Chehab
805819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
806819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
807819667bcSMauro Carvalho Chehab
8082118ba7dSMauro Carvalho Chehab    parser.add_argument('--rustdoc', action="store_true",
8092118ba7dSMauro Carvalho Chehab                        help="Enable rustdoc build. Requires CONFIG_RUST")
8102118ba7dSMauro Carvalho Chehab
811819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
812819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
813819667bcSMauro Carvalho Chehab
814819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
815819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
816819667bcSMauro Carvalho Chehab
8172f99b85eSMauro Carvalho Chehab    parser.add_argument('-i', '--interactive', action='store_true',
8182f99b85eSMauro Carvalho Chehab                        help="Change latex default to run in interactive mode")
8192f99b85eSMauro Carvalho Chehab
8204c6ece91SMauro Carvalho Chehab    parser.add_argument('-s', '--skip-sphinx-build', action='store_true',
8214c6ece91SMauro Carvalho Chehab                        help="Skip sphinx-build step")
8224c6ece91SMauro Carvalho Chehab
82342180adaSMauro Carvalho Chehab    parser.add_argument("-V", "--venv", nargs='?', const=f'{VENV_DEFAULT}',
82442180adaSMauro Carvalho Chehab                        default=None,
82542180adaSMauro Carvalho Chehab                        help=f'If used, run Sphinx from a venv dir (default dir: {VENV_DEFAULT})')
82642180adaSMauro Carvalho Chehab
827819667bcSMauro Carvalho Chehab    args = parser.parse_args()
828819667bcSMauro Carvalho Chehab
82962ea383bSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION, show_alternatives=True,
83062ea383bSMauro Carvalho Chehab                               bail_out=True)
831819667bcSMauro Carvalho Chehab
83242180adaSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir, venv=args.venv,
8332f99b85eSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs,
8342f99b85eSMauro Carvalho Chehab                            interactive=args.interactive)
835819667bcSMauro Carvalho Chehab
83672603d73SMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs,
837819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
8384c6ece91SMauro Carvalho Chehab                  rustdoc=args.rustdoc, deny_vf=args.deny_vf,
8394c6ece91SMauro Carvalho Chehab                  skip_sphinx=args.skip_sphinx_build)
840819667bcSMauro Carvalho Chehab
841819667bcSMauro Carvalho Chehabif __name__ == "__main__":
842819667bcSMauro Carvalho Chehab    main()
843