xref: /linux/tools/docs/sphinx-build-wrapper (revision 819667bc3ccdbb2a037fef5881d9e815b2f5f5b1)
1*819667bcSMauro Carvalho Chehab#!/usr/bin/env python3
2*819667bcSMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
3*819667bcSMauro Carvalho Chehab# Copyright (C) 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
4*819667bcSMauro Carvalho Chehab#
5*819667bcSMauro Carvalho Chehab# pylint: disable=R0902, R0912, R0913, R0914, R0915, R0917, C0103
6*819667bcSMauro Carvalho Chehab#
7*819667bcSMauro Carvalho Chehab# Converted from docs Makefile and parallel-wrapper.sh, both under
8*819667bcSMauro Carvalho Chehab# GPLv2, copyrighted since 2008 by the following authors:
9*819667bcSMauro Carvalho Chehab#
10*819667bcSMauro Carvalho Chehab#    Akira Yokosawa <akiyks@gmail.com>
11*819667bcSMauro Carvalho Chehab#    Arnd Bergmann <arnd@arndb.de>
12*819667bcSMauro Carvalho Chehab#    Breno Leitao <leitao@debian.org>
13*819667bcSMauro Carvalho Chehab#    Carlos Bilbao <carlos.bilbao@amd.com>
14*819667bcSMauro Carvalho Chehab#    Dave Young <dyoung@redhat.com>
15*819667bcSMauro Carvalho Chehab#    Donald Hunter <donald.hunter@gmail.com>
16*819667bcSMauro Carvalho Chehab#    Geert Uytterhoeven <geert+renesas@glider.be>
17*819667bcSMauro Carvalho Chehab#    Jani Nikula <jani.nikula@intel.com>
18*819667bcSMauro Carvalho Chehab#    Jan Stancek <jstancek@redhat.com>
19*819667bcSMauro Carvalho Chehab#    Jonathan Corbet <corbet@lwn.net>
20*819667bcSMauro Carvalho Chehab#    Joshua Clayton <stillcompiling@gmail.com>
21*819667bcSMauro Carvalho Chehab#    Kees Cook <keescook@chromium.org>
22*819667bcSMauro Carvalho Chehab#    Linus Torvalds <torvalds@linux-foundation.org>
23*819667bcSMauro Carvalho Chehab#    Magnus Damm <damm+renesas@opensource.se>
24*819667bcSMauro Carvalho Chehab#    Masahiro Yamada <masahiroy@kernel.org>
25*819667bcSMauro Carvalho Chehab#    Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
26*819667bcSMauro Carvalho Chehab#    Maxim Cournoyer <maxim.cournoyer@gmail.com>
27*819667bcSMauro Carvalho Chehab#    Peter Foley <pefoley2@pefoley.com>
28*819667bcSMauro Carvalho Chehab#    Randy Dunlap <rdunlap@infradead.org>
29*819667bcSMauro Carvalho Chehab#    Rob Herring <robh@kernel.org>
30*819667bcSMauro Carvalho Chehab#    Shuah Khan <shuahkh@osg.samsung.com>
31*819667bcSMauro Carvalho Chehab#    Thorsten Blum <thorsten.blum@toblux.com>
32*819667bcSMauro Carvalho Chehab#    Tomas Winkler <tomas.winkler@intel.com>
33*819667bcSMauro Carvalho Chehab
34*819667bcSMauro Carvalho Chehab
35*819667bcSMauro Carvalho Chehab"""
36*819667bcSMauro Carvalho ChehabSphinx build wrapper that handles Kernel-specific business rules:
37*819667bcSMauro Carvalho Chehab
38*819667bcSMauro Carvalho Chehab- it gets the Kernel build environment vars;
39*819667bcSMauro Carvalho Chehab- it determines what's the best parallelism;
40*819667bcSMauro Carvalho Chehab- it handles SPHINXDIRS
41*819667bcSMauro Carvalho Chehab
42*819667bcSMauro Carvalho ChehabThis tool ensures that MIN_PYTHON_VERSION is satisfied. If version is
43*819667bcSMauro Carvalho Chehabbelow that, it seeks for a new Python version. If found, it re-runs using
44*819667bcSMauro Carvalho Chehabthe newer version.
45*819667bcSMauro Carvalho Chehab"""
46*819667bcSMauro Carvalho Chehab
47*819667bcSMauro Carvalho Chehabimport argparse
48*819667bcSMauro Carvalho Chehabimport os
49*819667bcSMauro Carvalho Chehabimport shlex
50*819667bcSMauro Carvalho Chehabimport shutil
51*819667bcSMauro Carvalho Chehabimport subprocess
52*819667bcSMauro Carvalho Chehabimport sys
53*819667bcSMauro Carvalho Chehab
54*819667bcSMauro Carvalho Chehabfrom lib.python_version import PythonVersion
55*819667bcSMauro Carvalho Chehabfrom lib.latex_fonts import LatexFontChecker
56*819667bcSMauro Carvalho Chehab
57*819667bcSMauro Carvalho ChehabLIB_DIR = "../../scripts/lib"
58*819667bcSMauro Carvalho ChehabSRC_DIR = os.path.dirname(os.path.realpath(__file__))
59*819667bcSMauro Carvalho Chehab
60*819667bcSMauro Carvalho Chehabsys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
61*819667bcSMauro Carvalho Chehab
62*819667bcSMauro Carvalho Chehabfrom jobserver import JobserverExec         # pylint: disable=C0413,C0411,E0401
63*819667bcSMauro Carvalho Chehab
64*819667bcSMauro Carvalho Chehab#
65*819667bcSMauro Carvalho Chehab#  Some constants
66*819667bcSMauro Carvalho Chehab#
67*819667bcSMauro Carvalho ChehabMIN_PYTHON_VERSION = PythonVersion("3.7").version
68*819667bcSMauro Carvalho ChehabPAPER = ["", "a4", "letter"]
69*819667bcSMauro Carvalho Chehab
70*819667bcSMauro Carvalho ChehabTARGETS = {
71*819667bcSMauro Carvalho Chehab    "cleandocs":     { "builder": "clean" },
72*819667bcSMauro Carvalho Chehab    "linkcheckdocs": { "builder": "linkcheck" },
73*819667bcSMauro Carvalho Chehab    "htmldocs":      { "builder": "html" },
74*819667bcSMauro Carvalho Chehab    "epubdocs":      { "builder": "epub",    "out_dir": "epub" },
75*819667bcSMauro Carvalho Chehab    "texinfodocs":   { "builder": "texinfo", "out_dir": "texinfo" },
76*819667bcSMauro Carvalho Chehab    "infodocs":      { "builder": "texinfo", "out_dir": "texinfo" },
77*819667bcSMauro Carvalho Chehab    "latexdocs":     { "builder": "latex",   "out_dir": "latex" },
78*819667bcSMauro Carvalho Chehab    "pdfdocs":       { "builder": "latex",   "out_dir": "latex" },
79*819667bcSMauro Carvalho Chehab    "xmldocs":       { "builder": "xml",     "out_dir": "xml" },
80*819667bcSMauro Carvalho Chehab}
81*819667bcSMauro Carvalho Chehab
82*819667bcSMauro Carvalho Chehab
83*819667bcSMauro Carvalho Chehab#
84*819667bcSMauro Carvalho Chehab# SphinxBuilder class
85*819667bcSMauro Carvalho Chehab#
86*819667bcSMauro Carvalho Chehab
87*819667bcSMauro Carvalho Chehabclass SphinxBuilder:
88*819667bcSMauro Carvalho Chehab    """
89*819667bcSMauro Carvalho Chehab    Handles a sphinx-build target, adding needed arguments to build
90*819667bcSMauro Carvalho Chehab    with the Kernel.
91*819667bcSMauro Carvalho Chehab    """
92*819667bcSMauro Carvalho Chehab
93*819667bcSMauro Carvalho Chehab    def is_rust_enabled(self):
94*819667bcSMauro Carvalho Chehab        """Check if rust is enabled at .config"""
95*819667bcSMauro Carvalho Chehab        config_path = os.path.join(self.srctree, ".config")
96*819667bcSMauro Carvalho Chehab        if os.path.isfile(config_path):
97*819667bcSMauro Carvalho Chehab            with open(config_path, "r", encoding="utf-8") as f:
98*819667bcSMauro Carvalho Chehab                return "CONFIG_RUST=y" in f.read()
99*819667bcSMauro Carvalho Chehab        return False
100*819667bcSMauro Carvalho Chehab
101*819667bcSMauro Carvalho Chehab    def get_path(self, path, use_cwd=False, abs_path=False):
102*819667bcSMauro Carvalho Chehab        """
103*819667bcSMauro Carvalho Chehab        Ancillary routine to handle patches the right way, as shell does.
104*819667bcSMauro Carvalho Chehab
105*819667bcSMauro Carvalho Chehab        It first expands "~" and "~user". Then, if patch is not absolute,
106*819667bcSMauro Carvalho Chehab        join self.srctree. Finally, if requested, convert to abspath.
107*819667bcSMauro Carvalho Chehab        """
108*819667bcSMauro Carvalho Chehab
109*819667bcSMauro Carvalho Chehab        path = os.path.expanduser(path)
110*819667bcSMauro Carvalho Chehab        if not path.startswith("/"):
111*819667bcSMauro Carvalho Chehab            if use_cwd:
112*819667bcSMauro Carvalho Chehab                base = os.getcwd()
113*819667bcSMauro Carvalho Chehab            else:
114*819667bcSMauro Carvalho Chehab                base = self.srctree
115*819667bcSMauro Carvalho Chehab
116*819667bcSMauro Carvalho Chehab            path = os.path.join(base, path)
117*819667bcSMauro Carvalho Chehab
118*819667bcSMauro Carvalho Chehab        if abs_path:
119*819667bcSMauro Carvalho Chehab            return os.path.abspath(path)
120*819667bcSMauro Carvalho Chehab
121*819667bcSMauro Carvalho Chehab        return path
122*819667bcSMauro Carvalho Chehab
123*819667bcSMauro Carvalho Chehab    def get_sphinx_extra_opts(self, n_jobs):
124*819667bcSMauro Carvalho Chehab        """
125*819667bcSMauro Carvalho Chehab        Get the number of jobs to be used for docs build passed via command
126*819667bcSMauro Carvalho Chehab        line and desired sphinx verbosity.
127*819667bcSMauro Carvalho Chehab
128*819667bcSMauro Carvalho Chehab        The number of jobs can be on different places:
129*819667bcSMauro Carvalho Chehab
130*819667bcSMauro Carvalho Chehab        1) It can be passed via "-j" argument;
131*819667bcSMauro Carvalho Chehab        2) The SPHINXOPTS="-j8" env var may have "-j";
132*819667bcSMauro Carvalho Chehab        3) if called via GNU make, -j specifies the desired number of jobs.
133*819667bcSMauro Carvalho Chehab           with GNU makefile, this number is available via POSIX jobserver;
134*819667bcSMauro Carvalho Chehab        4) if none of the above is available, it should default to "-jauto",
135*819667bcSMauro Carvalho Chehab           and let sphinx decide the best value.
136*819667bcSMauro Carvalho Chehab        """
137*819667bcSMauro Carvalho Chehab
138*819667bcSMauro Carvalho Chehab        #
139*819667bcSMauro Carvalho Chehab        # SPHINXOPTS env var, if used, contains extra arguments to be used
140*819667bcSMauro Carvalho Chehab        # by sphinx-build time. Among them, it may contain sphinx verbosity
141*819667bcSMauro Carvalho Chehab        # and desired number of parallel jobs.
142*819667bcSMauro Carvalho Chehab        #
143*819667bcSMauro Carvalho Chehab        parser = argparse.ArgumentParser()
144*819667bcSMauro Carvalho Chehab        parser.add_argument('-j', '--jobs', type=int)
145*819667bcSMauro Carvalho Chehab        parser.add_argument('-q', '--quiet', type=int)
146*819667bcSMauro Carvalho Chehab
147*819667bcSMauro Carvalho Chehab        #
148*819667bcSMauro Carvalho Chehab        # Other sphinx-build arguments go as-is, so place them
149*819667bcSMauro Carvalho Chehab        # at self.sphinxopts, using shell parser
150*819667bcSMauro Carvalho Chehab        #
151*819667bcSMauro Carvalho Chehab        sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
152*819667bcSMauro Carvalho Chehab
153*819667bcSMauro Carvalho Chehab        #
154*819667bcSMauro Carvalho Chehab        # Build a list of sphinx args, honoring verbosity here if specified
155*819667bcSMauro Carvalho Chehab        #
156*819667bcSMauro Carvalho Chehab
157*819667bcSMauro Carvalho Chehab        verbose = self.verbose
158*819667bcSMauro Carvalho Chehab        sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
159*819667bcSMauro Carvalho Chehab        if sphinx_args.quiet is True:
160*819667bcSMauro Carvalho Chehab            verbose = False
161*819667bcSMauro Carvalho Chehab
162*819667bcSMauro Carvalho Chehab        #
163*819667bcSMauro Carvalho Chehab        # If the user explicitly sets "-j" at command line, use it.
164*819667bcSMauro Carvalho Chehab        # Otherwise, pick it from SPHINXOPTS args
165*819667bcSMauro Carvalho Chehab        #
166*819667bcSMauro Carvalho Chehab        if n_jobs:
167*819667bcSMauro Carvalho Chehab            self.n_jobs = n_jobs
168*819667bcSMauro Carvalho Chehab        elif sphinx_args.jobs:
169*819667bcSMauro Carvalho Chehab            self.n_jobs = sphinx_args.jobs
170*819667bcSMauro Carvalho Chehab        else:
171*819667bcSMauro Carvalho Chehab            self.n_jobs = None
172*819667bcSMauro Carvalho Chehab
173*819667bcSMauro Carvalho Chehab        if not verbose:
174*819667bcSMauro Carvalho Chehab            self.sphinxopts += ["-q"]
175*819667bcSMauro Carvalho Chehab
176*819667bcSMauro Carvalho Chehab    def __init__(self, builddir, verbose=False, n_jobs=None):
177*819667bcSMauro Carvalho Chehab        """Initialize internal variables"""
178*819667bcSMauro Carvalho Chehab        self.verbose = None
179*819667bcSMauro Carvalho Chehab
180*819667bcSMauro Carvalho Chehab        #
181*819667bcSMauro Carvalho Chehab        # Normal variables passed from Kernel's makefile
182*819667bcSMauro Carvalho Chehab        #
183*819667bcSMauro Carvalho Chehab        self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
184*819667bcSMauro Carvalho Chehab        self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
185*819667bcSMauro Carvalho Chehab        self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
186*819667bcSMauro Carvalho Chehab        self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
187*819667bcSMauro Carvalho Chehab
188*819667bcSMauro Carvalho Chehab        if not verbose:
189*819667bcSMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
190*819667bcSMauro Carvalho Chehab
191*819667bcSMauro Carvalho Chehab        if verbose is not None:
192*819667bcSMauro Carvalho Chehab            self.verbose = verbose
193*819667bcSMauro Carvalho Chehab
194*819667bcSMauro Carvalho Chehab        #
195*819667bcSMauro Carvalho Chehab        # Source tree directory. This needs to be at os.environ, as
196*819667bcSMauro Carvalho Chehab        # Sphinx extensions use it
197*819667bcSMauro Carvalho Chehab        #
198*819667bcSMauro Carvalho Chehab        self.srctree = os.environ.get("srctree")
199*819667bcSMauro Carvalho Chehab        if not self.srctree:
200*819667bcSMauro Carvalho Chehab            self.srctree = "."
201*819667bcSMauro Carvalho Chehab            os.environ["srctree"] = self.srctree
202*819667bcSMauro Carvalho Chehab
203*819667bcSMauro Carvalho Chehab        #
204*819667bcSMauro Carvalho Chehab        # Now that we can expand srctree, get other directories as well
205*819667bcSMauro Carvalho Chehab        #
206*819667bcSMauro Carvalho Chehab        self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
207*819667bcSMauro Carvalho Chehab        self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
208*819667bcSMauro Carvalho Chehab                                                      "scripts/kernel-doc.py"))
209*819667bcSMauro Carvalho Chehab        self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
210*819667bcSMauro Carvalho Chehab
211*819667bcSMauro Carvalho Chehab        self.config_rust = self.is_rust_enabled()
212*819667bcSMauro Carvalho Chehab
213*819667bcSMauro Carvalho Chehab        #
214*819667bcSMauro Carvalho Chehab        # Get directory locations for LaTeX build toolchain
215*819667bcSMauro Carvalho Chehab        #
216*819667bcSMauro Carvalho Chehab        self.pdflatex_cmd = shutil.which(self.pdflatex)
217*819667bcSMauro Carvalho Chehab        self.latexmk_cmd = shutil.which("latexmk")
218*819667bcSMauro Carvalho Chehab
219*819667bcSMauro Carvalho Chehab        self.env = os.environ.copy()
220*819667bcSMauro Carvalho Chehab
221*819667bcSMauro Carvalho Chehab        self.get_sphinx_extra_opts(n_jobs)
222*819667bcSMauro Carvalho Chehab
223*819667bcSMauro Carvalho Chehab    def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
224*819667bcSMauro Carvalho Chehab        """
225*819667bcSMauro Carvalho Chehab        Executes sphinx-build using current python3 command.
226*819667bcSMauro Carvalho Chehab
227*819667bcSMauro Carvalho Chehab        When calling via GNU make, POSIX jobserver is used to tell how
228*819667bcSMauro Carvalho Chehab        many jobs are still available from a job pool. claim all remaining
229*819667bcSMauro Carvalho Chehab        jobs, as we don't want sphinx-build to run in parallel with other
230*819667bcSMauro Carvalho Chehab        jobs.
231*819667bcSMauro Carvalho Chehab
232*819667bcSMauro Carvalho Chehab        Despite that, the user may actually force a different value than
233*819667bcSMauro Carvalho Chehab        the number of available jobs via command line.
234*819667bcSMauro Carvalho Chehab
235*819667bcSMauro Carvalho Chehab        The "with" logic here is used to ensure that the claimed jobs will
236*819667bcSMauro Carvalho Chehab        be freed once subprocess finishes
237*819667bcSMauro Carvalho Chehab        """
238*819667bcSMauro Carvalho Chehab
239*819667bcSMauro Carvalho Chehab        with JobserverExec() as jobserver:
240*819667bcSMauro Carvalho Chehab            if jobserver.claim:
241*819667bcSMauro Carvalho Chehab                #
242*819667bcSMauro Carvalho Chehab                # when GNU make is used, claim available jobs from jobserver
243*819667bcSMauro Carvalho Chehab                #
244*819667bcSMauro Carvalho Chehab                n_jobs = str(jobserver.claim)
245*819667bcSMauro Carvalho Chehab            else:
246*819667bcSMauro Carvalho Chehab                #
247*819667bcSMauro Carvalho Chehab                # Otherwise, let sphinx decide by default
248*819667bcSMauro Carvalho Chehab                #
249*819667bcSMauro Carvalho Chehab                n_jobs = "auto"
250*819667bcSMauro Carvalho Chehab
251*819667bcSMauro Carvalho Chehab            #
252*819667bcSMauro Carvalho Chehab            # If explicitly requested via command line, override default
253*819667bcSMauro Carvalho Chehab            #
254*819667bcSMauro Carvalho Chehab            if self.n_jobs:
255*819667bcSMauro Carvalho Chehab                n_jobs = str(self.n_jobs)
256*819667bcSMauro Carvalho Chehab
257*819667bcSMauro Carvalho Chehab            cmd = [sys.executable, sphinx_build]
258*819667bcSMauro Carvalho Chehab            cmd += [f"-j{n_jobs}"]
259*819667bcSMauro Carvalho Chehab            cmd += self.sphinxopts
260*819667bcSMauro Carvalho Chehab            cmd += build_args
261*819667bcSMauro Carvalho Chehab
262*819667bcSMauro Carvalho Chehab            if self.verbose:
263*819667bcSMauro Carvalho Chehab                print(" ".join(cmd))
264*819667bcSMauro Carvalho Chehab
265*819667bcSMauro Carvalho Chehab            return subprocess.call(cmd, *args, **pwargs)
266*819667bcSMauro Carvalho Chehab
267*819667bcSMauro Carvalho Chehab    def handle_html(self, css, output_dir):
268*819667bcSMauro Carvalho Chehab        """
269*819667bcSMauro Carvalho Chehab        Extra steps for HTML and epub output.
270*819667bcSMauro Carvalho Chehab
271*819667bcSMauro Carvalho Chehab        For such targets, we need to ensure that CSS will be properly
272*819667bcSMauro Carvalho Chehab        copied to the output _static directory
273*819667bcSMauro Carvalho Chehab        """
274*819667bcSMauro Carvalho Chehab
275*819667bcSMauro Carvalho Chehab        if not css:
276*819667bcSMauro Carvalho Chehab            return
277*819667bcSMauro Carvalho Chehab
278*819667bcSMauro Carvalho Chehab        css = os.path.expanduser(css)
279*819667bcSMauro Carvalho Chehab        if not css.startswith("/"):
280*819667bcSMauro Carvalho Chehab            css = os.path.join(self.srctree, css)
281*819667bcSMauro Carvalho Chehab
282*819667bcSMauro Carvalho Chehab        static_dir = os.path.join(output_dir, "_static")
283*819667bcSMauro Carvalho Chehab        os.makedirs(static_dir, exist_ok=True)
284*819667bcSMauro Carvalho Chehab
285*819667bcSMauro Carvalho Chehab        try:
286*819667bcSMauro Carvalho Chehab            shutil.copy2(css, static_dir)
287*819667bcSMauro Carvalho Chehab        except (OSError, IOError) as e:
288*819667bcSMauro Carvalho Chehab            print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
289*819667bcSMauro Carvalho Chehab
290*819667bcSMauro Carvalho Chehab    def handle_pdf(self, output_dirs, deny_vf):
291*819667bcSMauro Carvalho Chehab        """
292*819667bcSMauro Carvalho Chehab        Extra steps for PDF output.
293*819667bcSMauro Carvalho Chehab
294*819667bcSMauro Carvalho Chehab        As PDF is handled via a LaTeX output, after building the .tex file,
295*819667bcSMauro Carvalho Chehab        a new build is needed to create the PDF output from the latex
296*819667bcSMauro Carvalho Chehab        directory.
297*819667bcSMauro Carvalho Chehab        """
298*819667bcSMauro Carvalho Chehab        builds = {}
299*819667bcSMauro Carvalho Chehab        max_len = 0
300*819667bcSMauro Carvalho Chehab
301*819667bcSMauro Carvalho Chehab        #
302*819667bcSMauro Carvalho Chehab        # Since early 2024, Fedora and openSUSE tumbleweed have started
303*819667bcSMauro Carvalho Chehab        # deploying variable-font format of "Noto CJK", causing LaTeX
304*819667bcSMauro Carvalho Chehab        # to break with CJK. Work around it, by denying the variable font
305*819667bcSMauro Carvalho Chehab        # usage during xelatex build by passing the location of a config
306*819667bcSMauro Carvalho Chehab        # file with a deny list.
307*819667bcSMauro Carvalho Chehab        #
308*819667bcSMauro Carvalho Chehab        # See tools/docs/lib/latex_fonts.py for more details.
309*819667bcSMauro Carvalho Chehab        #
310*819667bcSMauro Carvalho Chehab        if deny_vf:
311*819667bcSMauro Carvalho Chehab            deny_vf = os.path.expanduser(deny_vf)
312*819667bcSMauro Carvalho Chehab            if os.path.isdir(deny_vf):
313*819667bcSMauro Carvalho Chehab                self.env["XDG_CONFIG_HOME"] = deny_vf
314*819667bcSMauro Carvalho Chehab
315*819667bcSMauro Carvalho Chehab        for from_dir in output_dirs:
316*819667bcSMauro Carvalho Chehab            pdf_dir = os.path.join(from_dir, "../pdf")
317*819667bcSMauro Carvalho Chehab            os.makedirs(pdf_dir, exist_ok=True)
318*819667bcSMauro Carvalho Chehab
319*819667bcSMauro Carvalho Chehab            if self.latexmk_cmd:
320*819667bcSMauro Carvalho Chehab                latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
321*819667bcSMauro Carvalho Chehab            else:
322*819667bcSMauro Carvalho Chehab                latex_cmd = [self.pdflatex]
323*819667bcSMauro Carvalho Chehab
324*819667bcSMauro Carvalho Chehab            latex_cmd.extend(shlex.split(self.latexopts))
325*819667bcSMauro Carvalho Chehab
326*819667bcSMauro Carvalho Chehab            tex_suffix = ".tex"
327*819667bcSMauro Carvalho Chehab
328*819667bcSMauro Carvalho Chehab            #
329*819667bcSMauro Carvalho Chehab            # Process each .tex file
330*819667bcSMauro Carvalho Chehab            #
331*819667bcSMauro Carvalho Chehab
332*819667bcSMauro Carvalho Chehab            has_tex = False
333*819667bcSMauro Carvalho Chehab            build_failed = False
334*819667bcSMauro Carvalho Chehab            with os.scandir(from_dir) as it:
335*819667bcSMauro Carvalho Chehab                for entry in it:
336*819667bcSMauro Carvalho Chehab                    if not entry.name.endswith(tex_suffix):
337*819667bcSMauro Carvalho Chehab                        continue
338*819667bcSMauro Carvalho Chehab
339*819667bcSMauro Carvalho Chehab                    name = entry.name[:-len(tex_suffix)]
340*819667bcSMauro Carvalho Chehab                    has_tex = True
341*819667bcSMauro Carvalho Chehab
342*819667bcSMauro Carvalho Chehab                    #
343*819667bcSMauro Carvalho Chehab                    # LaTeX PDF error code is almost useless for us:
344*819667bcSMauro Carvalho Chehab                    # any warning makes it non-zero. For kernel doc builds it
345*819667bcSMauro Carvalho Chehab                    # always return non-zero even when build succeeds.
346*819667bcSMauro Carvalho Chehab                    # So, let's do the best next thing: check if all PDF
347*819667bcSMauro Carvalho Chehab                    # files were built. If they're, print a summary and
348*819667bcSMauro Carvalho Chehab                    # return 0 at the end of this function
349*819667bcSMauro Carvalho Chehab                    #
350*819667bcSMauro Carvalho Chehab                    try:
351*819667bcSMauro Carvalho Chehab                        subprocess.run(latex_cmd + [entry.path],
352*819667bcSMauro Carvalho Chehab                                       cwd=from_dir, check=True, env=self.env)
353*819667bcSMauro Carvalho Chehab                    except subprocess.CalledProcessError:
354*819667bcSMauro Carvalho Chehab                        pass
355*819667bcSMauro Carvalho Chehab
356*819667bcSMauro Carvalho Chehab                    pdf_name = name + ".pdf"
357*819667bcSMauro Carvalho Chehab                    pdf_from = os.path.join(from_dir, pdf_name)
358*819667bcSMauro Carvalho Chehab                    pdf_to = os.path.join(pdf_dir, pdf_name)
359*819667bcSMauro Carvalho Chehab
360*819667bcSMauro Carvalho Chehab                    if os.path.exists(pdf_from):
361*819667bcSMauro Carvalho Chehab                        os.rename(pdf_from, pdf_to)
362*819667bcSMauro Carvalho Chehab                        builds[name] = os.path.relpath(pdf_to, self.builddir)
363*819667bcSMauro Carvalho Chehab                    else:
364*819667bcSMauro Carvalho Chehab                        builds[name] = "FAILED"
365*819667bcSMauro Carvalho Chehab                        build_failed = True
366*819667bcSMauro Carvalho Chehab
367*819667bcSMauro Carvalho Chehab                    name = entry.name.removesuffix(".tex")
368*819667bcSMauro Carvalho Chehab                    max_len = max(max_len, len(name))
369*819667bcSMauro Carvalho Chehab
370*819667bcSMauro Carvalho Chehab            if not has_tex:
371*819667bcSMauro Carvalho Chehab                name = os.path.basename(from_dir)
372*819667bcSMauro Carvalho Chehab                max_len = max(max_len, len(name))
373*819667bcSMauro Carvalho Chehab                builds[name] = "FAILED (no .tex)"
374*819667bcSMauro Carvalho Chehab                build_failed = True
375*819667bcSMauro Carvalho Chehab
376*819667bcSMauro Carvalho Chehab        msg = "Summary"
377*819667bcSMauro Carvalho Chehab        msg += "\n" + "=" * len(msg)
378*819667bcSMauro Carvalho Chehab        print()
379*819667bcSMauro Carvalho Chehab        print(msg)
380*819667bcSMauro Carvalho Chehab
381*819667bcSMauro Carvalho Chehab        for pdf_name, pdf_file in builds.items():
382*819667bcSMauro Carvalho Chehab            print(f"{pdf_name:<{max_len}}: {pdf_file}")
383*819667bcSMauro Carvalho Chehab
384*819667bcSMauro Carvalho Chehab        print()
385*819667bcSMauro Carvalho Chehab
386*819667bcSMauro Carvalho Chehab        if build_failed:
387*819667bcSMauro Carvalho Chehab            msg = LatexFontChecker().check()
388*819667bcSMauro Carvalho Chehab            if msg:
389*819667bcSMauro Carvalho Chehab                print(msg)
390*819667bcSMauro Carvalho Chehab
391*819667bcSMauro Carvalho Chehab            sys.exit("PDF build failed: not all PDF files were created.")
392*819667bcSMauro Carvalho Chehab        else:
393*819667bcSMauro Carvalho Chehab            print("All PDF files were built.")
394*819667bcSMauro Carvalho Chehab
395*819667bcSMauro Carvalho Chehab    def handle_info(self, output_dirs):
396*819667bcSMauro Carvalho Chehab        """
397*819667bcSMauro Carvalho Chehab        Extra steps for Info output.
398*819667bcSMauro Carvalho Chehab
399*819667bcSMauro Carvalho Chehab        For texinfo generation, an additional make is needed from the
400*819667bcSMauro Carvalho Chehab        texinfo directory.
401*819667bcSMauro Carvalho Chehab        """
402*819667bcSMauro Carvalho Chehab
403*819667bcSMauro Carvalho Chehab        for output_dir in output_dirs:
404*819667bcSMauro Carvalho Chehab            try:
405*819667bcSMauro Carvalho Chehab                subprocess.run(["make", "info"], cwd=output_dir, check=True)
406*819667bcSMauro Carvalho Chehab            except subprocess.CalledProcessError as e:
407*819667bcSMauro Carvalho Chehab                sys.exit(f"Error generating info docs: {e}")
408*819667bcSMauro Carvalho Chehab
409*819667bcSMauro Carvalho Chehab    def cleandocs(self, builder):           # pylint: disable=W0613
410*819667bcSMauro Carvalho Chehab        """Remove documentation output directory"""
411*819667bcSMauro Carvalho Chehab        shutil.rmtree(self.builddir, ignore_errors=True)
412*819667bcSMauro Carvalho Chehab
413*819667bcSMauro Carvalho Chehab    def build(self, target, sphinxdirs=None, conf="conf.py",
414*819667bcSMauro Carvalho Chehab              theme=None, css=None, paper=None, deny_vf=None):
415*819667bcSMauro Carvalho Chehab        """
416*819667bcSMauro Carvalho Chehab        Build documentation using Sphinx. This is the core function of this
417*819667bcSMauro Carvalho Chehab        module. It prepares all arguments required by sphinx-build.
418*819667bcSMauro Carvalho Chehab        """
419*819667bcSMauro Carvalho Chehab
420*819667bcSMauro Carvalho Chehab        builder = TARGETS[target]["builder"]
421*819667bcSMauro Carvalho Chehab        out_dir = TARGETS[target].get("out_dir", "")
422*819667bcSMauro Carvalho Chehab
423*819667bcSMauro Carvalho Chehab        #
424*819667bcSMauro Carvalho Chehab        # Cleandocs doesn't require sphinx-build
425*819667bcSMauro Carvalho Chehab        #
426*819667bcSMauro Carvalho Chehab        if target == "cleandocs":
427*819667bcSMauro Carvalho Chehab            self.cleandocs(builder)
428*819667bcSMauro Carvalho Chehab            return
429*819667bcSMauro Carvalho Chehab
430*819667bcSMauro Carvalho Chehab        if theme:
431*819667bcSMauro Carvalho Chehab            os.environ["DOCS_THEME"] = theme
432*819667bcSMauro Carvalho Chehab
433*819667bcSMauro Carvalho Chehab        #
434*819667bcSMauro Carvalho Chehab        # Other targets require sphinx-build, so check if it exists
435*819667bcSMauro Carvalho Chehab        #
436*819667bcSMauro Carvalho Chehab        sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
437*819667bcSMauro Carvalho Chehab        if not sphinxbuild:
438*819667bcSMauro Carvalho Chehab            sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
439*819667bcSMauro Carvalho Chehab
440*819667bcSMauro Carvalho Chehab        if builder == "latex":
441*819667bcSMauro Carvalho Chehab            if not self.pdflatex_cmd and not self.latexmk_cmd:
442*819667bcSMauro Carvalho Chehab                sys.exit("Error: pdflatex or latexmk required for PDF generation")
443*819667bcSMauro Carvalho Chehab
444*819667bcSMauro Carvalho Chehab        docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
445*819667bcSMauro Carvalho Chehab
446*819667bcSMauro Carvalho Chehab        #
447*819667bcSMauro Carvalho Chehab        # Fill in base arguments for Sphinx build
448*819667bcSMauro Carvalho Chehab        #
449*819667bcSMauro Carvalho Chehab        kerneldoc = self.kerneldoc
450*819667bcSMauro Carvalho Chehab        if kerneldoc.startswith(self.srctree):
451*819667bcSMauro Carvalho Chehab            kerneldoc = os.path.relpath(kerneldoc, self.srctree)
452*819667bcSMauro Carvalho Chehab
453*819667bcSMauro Carvalho Chehab        args = [ "-b", builder, "-c", docs_dir ]
454*819667bcSMauro Carvalho Chehab
455*819667bcSMauro Carvalho Chehab        if builder == "latex":
456*819667bcSMauro Carvalho Chehab            if not paper:
457*819667bcSMauro Carvalho Chehab                paper = PAPER[1]
458*819667bcSMauro Carvalho Chehab
459*819667bcSMauro Carvalho Chehab            args.extend(["-D", f"latex_elements.papersize={paper}paper"])
460*819667bcSMauro Carvalho Chehab
461*819667bcSMauro Carvalho Chehab        if self.config_rust:
462*819667bcSMauro Carvalho Chehab            args.extend(["-t", "rustdoc"])
463*819667bcSMauro Carvalho Chehab
464*819667bcSMauro Carvalho Chehab        if conf:
465*819667bcSMauro Carvalho Chehab            self.env["SPHINX_CONF"] = self.get_path(conf, abs_path=True)
466*819667bcSMauro Carvalho Chehab
467*819667bcSMauro Carvalho Chehab        if not sphinxdirs:
468*819667bcSMauro Carvalho Chehab            sphinxdirs = os.environ.get("SPHINXDIRS", ".")
469*819667bcSMauro Carvalho Chehab
470*819667bcSMauro Carvalho Chehab        #
471*819667bcSMauro Carvalho Chehab        # sphinxdirs can be a list or a whitespace-separated string
472*819667bcSMauro Carvalho Chehab        #
473*819667bcSMauro Carvalho Chehab        sphinxdirs_list = []
474*819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs:
475*819667bcSMauro Carvalho Chehab            if isinstance(sphinxdir, list):
476*819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir
477*819667bcSMauro Carvalho Chehab            else:
478*819667bcSMauro Carvalho Chehab                sphinxdirs_list += sphinxdir.split()
479*819667bcSMauro Carvalho Chehab
480*819667bcSMauro Carvalho Chehab        #
481*819667bcSMauro Carvalho Chehab        # Step 1:  Build each directory in separate.
482*819667bcSMauro Carvalho Chehab        #
483*819667bcSMauro Carvalho Chehab        # This is not the best way of handling it, as cross-references between
484*819667bcSMauro Carvalho Chehab        # them will be broken, but this is what we've been doing since
485*819667bcSMauro Carvalho Chehab        # the beginning.
486*819667bcSMauro Carvalho Chehab        #
487*819667bcSMauro Carvalho Chehab        output_dirs = []
488*819667bcSMauro Carvalho Chehab        for sphinxdir in sphinxdirs_list:
489*819667bcSMauro Carvalho Chehab            src_dir = os.path.join(docs_dir, sphinxdir)
490*819667bcSMauro Carvalho Chehab            doctree_dir = os.path.join(self.builddir, ".doctrees")
491*819667bcSMauro Carvalho Chehab            output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
492*819667bcSMauro Carvalho Chehab
493*819667bcSMauro Carvalho Chehab            #
494*819667bcSMauro Carvalho Chehab            # Make directory names canonical
495*819667bcSMauro Carvalho Chehab            #
496*819667bcSMauro Carvalho Chehab            src_dir = os.path.normpath(src_dir)
497*819667bcSMauro Carvalho Chehab            doctree_dir = os.path.normpath(doctree_dir)
498*819667bcSMauro Carvalho Chehab            output_dir = os.path.normpath(output_dir)
499*819667bcSMauro Carvalho Chehab
500*819667bcSMauro Carvalho Chehab            os.makedirs(doctree_dir, exist_ok=True)
501*819667bcSMauro Carvalho Chehab            os.makedirs(output_dir, exist_ok=True)
502*819667bcSMauro Carvalho Chehab
503*819667bcSMauro Carvalho Chehab            output_dirs.append(output_dir)
504*819667bcSMauro Carvalho Chehab
505*819667bcSMauro Carvalho Chehab            build_args = args + [
506*819667bcSMauro Carvalho Chehab                "-d", doctree_dir,
507*819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_bin={kerneldoc}",
508*819667bcSMauro Carvalho Chehab                "-D", f"version={self.kernelversion}",
509*819667bcSMauro Carvalho Chehab                "-D", f"release={self.kernelrelease}",
510*819667bcSMauro Carvalho Chehab                "-D", f"kerneldoc_srctree={self.srctree}",
511*819667bcSMauro Carvalho Chehab                src_dir,
512*819667bcSMauro Carvalho Chehab                output_dir,
513*819667bcSMauro Carvalho Chehab            ]
514*819667bcSMauro Carvalho Chehab
515*819667bcSMauro Carvalho Chehab            try:
516*819667bcSMauro Carvalho Chehab                self.run_sphinx(sphinxbuild, build_args, env=self.env)
517*819667bcSMauro Carvalho Chehab            except (OSError, ValueError, subprocess.SubprocessError) as e:
518*819667bcSMauro Carvalho Chehab                sys.exit(f"Build failed: {repr(e)}")
519*819667bcSMauro Carvalho Chehab
520*819667bcSMauro Carvalho Chehab            #
521*819667bcSMauro Carvalho Chehab            # Ensure that each html/epub output will have needed static files
522*819667bcSMauro Carvalho Chehab            #
523*819667bcSMauro Carvalho Chehab            if target in ["htmldocs", "epubdocs"]:
524*819667bcSMauro Carvalho Chehab                self.handle_html(css, output_dir)
525*819667bcSMauro Carvalho Chehab
526*819667bcSMauro Carvalho Chehab        #
527*819667bcSMauro Carvalho Chehab        # Step 2: Some targets (PDF and info) require an extra step once
528*819667bcSMauro Carvalho Chehab        #         sphinx-build finishes
529*819667bcSMauro Carvalho Chehab        #
530*819667bcSMauro Carvalho Chehab        if target == "pdfdocs":
531*819667bcSMauro Carvalho Chehab            self.handle_pdf(output_dirs, deny_vf)
532*819667bcSMauro Carvalho Chehab        elif target == "infodocs":
533*819667bcSMauro Carvalho Chehab            self.handle_info(output_dirs)
534*819667bcSMauro Carvalho Chehab
535*819667bcSMauro Carvalho Chehabdef jobs_type(value):
536*819667bcSMauro Carvalho Chehab    """
537*819667bcSMauro Carvalho Chehab    Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
538*819667bcSMauro Carvalho Chehab    equal or bigger than one.
539*819667bcSMauro Carvalho Chehab    """
540*819667bcSMauro Carvalho Chehab    if value is None:
541*819667bcSMauro Carvalho Chehab        return None
542*819667bcSMauro Carvalho Chehab
543*819667bcSMauro Carvalho Chehab    if value.lower() == 'auto':
544*819667bcSMauro Carvalho Chehab        return value.lower()
545*819667bcSMauro Carvalho Chehab
546*819667bcSMauro Carvalho Chehab    try:
547*819667bcSMauro Carvalho Chehab        if int(value) >= 1:
548*819667bcSMauro Carvalho Chehab            return value
549*819667bcSMauro Carvalho Chehab
550*819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
551*819667bcSMauro Carvalho Chehab    except ValueError:
552*819667bcSMauro Carvalho Chehab        raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}")  # pylint: disable=W0707
553*819667bcSMauro Carvalho Chehab
554*819667bcSMauro Carvalho Chehabdef main():
555*819667bcSMauro Carvalho Chehab    """
556*819667bcSMauro Carvalho Chehab    Main function. The only mandatory argument is the target. If not
557*819667bcSMauro Carvalho Chehab    specified, the other arguments will use default values if not
558*819667bcSMauro Carvalho Chehab    specified at os.environ.
559*819667bcSMauro Carvalho Chehab    """
560*819667bcSMauro Carvalho Chehab    parser = argparse.ArgumentParser(description="Kernel documentation builder")
561*819667bcSMauro Carvalho Chehab
562*819667bcSMauro Carvalho Chehab    parser.add_argument("target", choices=list(TARGETS.keys()),
563*819667bcSMauro Carvalho Chehab                        help="Documentation target to build")
564*819667bcSMauro Carvalho Chehab    parser.add_argument("--sphinxdirs", nargs="+",
565*819667bcSMauro Carvalho Chehab                        help="Specific directories to build")
566*819667bcSMauro Carvalho Chehab    parser.add_argument("--conf", default="conf.py",
567*819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
568*819667bcSMauro Carvalho Chehab    parser.add_argument("--builddir", default="output",
569*819667bcSMauro Carvalho Chehab                        help="Sphinx configuration file")
570*819667bcSMauro Carvalho Chehab
571*819667bcSMauro Carvalho Chehab    parser.add_argument("--theme", help="Sphinx theme to use")
572*819667bcSMauro Carvalho Chehab
573*819667bcSMauro Carvalho Chehab    parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
574*819667bcSMauro Carvalho Chehab
575*819667bcSMauro Carvalho Chehab    parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
576*819667bcSMauro Carvalho Chehab                        help="Paper size for LaTeX/PDF output")
577*819667bcSMauro Carvalho Chehab
578*819667bcSMauro Carvalho Chehab    parser.add_argument('--deny-vf',
579*819667bcSMauro Carvalho Chehab                        help="Configuration to deny variable fonts on pdf builds")
580*819667bcSMauro Carvalho Chehab
581*819667bcSMauro Carvalho Chehab    parser.add_argument("-v", "--verbose", action='store_true',
582*819667bcSMauro Carvalho Chehab                        help="place build in verbose mode")
583*819667bcSMauro Carvalho Chehab
584*819667bcSMauro Carvalho Chehab    parser.add_argument('-j', '--jobs', type=jobs_type,
585*819667bcSMauro Carvalho Chehab                        help="Sets number of jobs to use with sphinx-build")
586*819667bcSMauro Carvalho Chehab
587*819667bcSMauro Carvalho Chehab    args = parser.parse_args()
588*819667bcSMauro Carvalho Chehab
589*819667bcSMauro Carvalho Chehab    PythonVersion.check_python(MIN_PYTHON_VERSION)
590*819667bcSMauro Carvalho Chehab
591*819667bcSMauro Carvalho Chehab    builder = SphinxBuilder(builddir=args.builddir,
592*819667bcSMauro Carvalho Chehab                            verbose=args.verbose, n_jobs=args.jobs)
593*819667bcSMauro Carvalho Chehab
594*819667bcSMauro Carvalho Chehab    builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf,
595*819667bcSMauro Carvalho Chehab                  theme=args.theme, css=args.css, paper=args.paper,
596*819667bcSMauro Carvalho Chehab                  deny_vf=args.deny_vf)
597*819667bcSMauro Carvalho Chehab
598*819667bcSMauro Carvalho Chehabif __name__ == "__main__":
599*819667bcSMauro Carvalho Chehab    main()
600