| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895 |
- #!/usr/bin/env python3
- # SPDX-License-Identifier: GPL-2.0
- # Copyright (C) 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
- #
- # pylint: disable=R0902, R0912, R0913, R0914, R0915, R0917, C0103
- #
- # Converted from docs Makefile and parallel-wrapper.sh, both under
- # GPLv2, copyrighted since 2008 by the following authors:
- #
- # Akira Yokosawa <akiyks@gmail.com>
- # Arnd Bergmann <arnd@arndb.de>
- # Breno Leitao <leitao@debian.org>
- # Carlos Bilbao <carlos.bilbao@amd.com>
- # Dave Young <dyoung@redhat.com>
- # Donald Hunter <donald.hunter@gmail.com>
- # Geert Uytterhoeven <geert+renesas@glider.be>
- # Jani Nikula <jani.nikula@intel.com>
- # Jan Stancek <jstancek@redhat.com>
- # Jonathan Corbet <corbet@lwn.net>
- # Joshua Clayton <stillcompiling@gmail.com>
- # Kees Cook <keescook@chromium.org>
- # Linus Torvalds <torvalds@linux-foundation.org>
- # Magnus Damm <damm+renesas@opensource.se>
- # Masahiro Yamada <masahiroy@kernel.org>
- # Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
- # Maxim Cournoyer <maxim.cournoyer@gmail.com>
- # Peter Foley <pefoley2@pefoley.com>
- # Randy Dunlap <rdunlap@infradead.org>
- # Rob Herring <robh@kernel.org>
- # Shuah Khan <shuahkh@osg.samsung.com>
- # Thorsten Blum <thorsten.blum@toblux.com>
- # Tomas Winkler <tomas.winkler@intel.com>
- """
- Sphinx build wrapper that handles Kernel-specific business rules:
- - it gets the Kernel build environment vars;
- - it determines what's the best parallelism;
- - it handles SPHINXDIRS
- This tool ensures that MIN_PYTHON_VERSION is satisfied. If version is
- below that, it seeks for a new Python version. If found, it re-runs using
- the newer version.
- """
- import argparse
- import locale
- import os
- import re
- import shlex
- import shutil
- import subprocess
- import sys
- from concurrent import futures
- from glob import glob
- LIB_DIR = "../lib/python"
- SRC_DIR = os.path.dirname(os.path.realpath(__file__))
- sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
- from kdoc.python_version import PythonVersion
- from kdoc.latex_fonts import LatexFontChecker
- from jobserver import JobserverExec # pylint: disable=C0413,C0411,E0401
- #
- # Some constants
- #
- VENV_DEFAULT = "sphinx_latest"
- MIN_PYTHON_VERSION = PythonVersion("3.7").version
- PAPER = ["", "a4", "letter"]
- TARGETS = {
- "cleandocs": { "builder": "clean" },
- "linkcheckdocs": { "builder": "linkcheck" },
- "htmldocs": { "builder": "html" },
- "epubdocs": { "builder": "epub", "out_dir": "epub" },
- "texinfodocs": { "builder": "texinfo", "out_dir": "texinfo" },
- "infodocs": { "builder": "texinfo", "out_dir": "texinfo" },
- "mandocs": { "builder": "man", "out_dir": "man" },
- "latexdocs": { "builder": "latex", "out_dir": "latex" },
- "pdfdocs": { "builder": "latex", "out_dir": "latex" },
- "xmldocs": { "builder": "xml", "out_dir": "xml" },
- }
- #
- # SphinxBuilder class
- #
- class SphinxBuilder:
- """
- Handles a sphinx-build target, adding needed arguments to build
- with the Kernel.
- """
- def get_path(self, path, use_cwd=False, abs_path=False):
- """
- Ancillary routine to handle patches the right way, as shell does.
- It first expands "~" and "~user". Then, if patch is not absolute,
- join self.srctree. Finally, if requested, convert to abspath.
- """
- path = os.path.expanduser(path)
- if not path.startswith("/"):
- if use_cwd:
- base = os.getcwd()
- else:
- base = self.srctree
- path = os.path.join(base, path)
- if abs_path:
- return os.path.abspath(path)
- return path
- def check_rust(self, sphinxdirs):
- """
- Checks if Rust is enabled
- """
- config = os.path.join(self.srctree, ".config")
- if not {'.', 'rust'}.intersection(sphinxdirs):
- return False
- if not os.path.isfile(config):
- return False
- re_rust = re.compile(r"CONFIG_RUST=(m|y)")
- try:
- with open(config, "r", encoding="utf-8") as fp:
- for line in fp:
- if re_rust.match(line):
- return True
- except OSError as e:
- print(f"Failed to open {config}", file=sys.stderr)
- return False
- return False
- def get_sphinx_extra_opts(self, n_jobs):
- """
- Get the number of jobs to be used for docs build passed via command
- line and desired sphinx verbosity.
- The number of jobs can be on different places:
- 1) It can be passed via "-j" argument;
- 2) The SPHINXOPTS="-j8" env var may have "-j";
- 3) if called via GNU make, -j specifies the desired number of jobs.
- with GNU makefile, this number is available via POSIX jobserver;
- 4) if none of the above is available, it should default to "-jauto",
- and let sphinx decide the best value.
- """
- #
- # SPHINXOPTS env var, if used, contains extra arguments to be used
- # by sphinx-build time. Among them, it may contain sphinx verbosity
- # and desired number of parallel jobs.
- #
- parser = argparse.ArgumentParser()
- parser.add_argument('-j', '--jobs', type=int)
- parser.add_argument('-q', '--quiet', action='store_true')
- parser.add_argument('-v', '--verbose', default=0, action='count')
- #
- # Other sphinx-build arguments go as-is, so place them
- # at self.sphinxopts, using shell parser
- #
- sphinxopts = shlex.split(os.environ.get("SPHINXOPTS", ""))
- #
- # Build a list of sphinx args, honoring verbosity here if specified
- #
- sphinx_args, self.sphinxopts = parser.parse_known_args(sphinxopts)
- verbose = sphinx_args.verbose
- if self.verbose:
- verbose += 1
- if sphinx_args.quiet is True:
- verbose = 0
- #
- # If the user explicitly sets "-j" at command line, use it.
- # Otherwise, pick it from SPHINXOPTS args
- #
- if n_jobs:
- self.n_jobs = n_jobs
- elif sphinx_args.jobs:
- self.n_jobs = sphinx_args.jobs
- else:
- self.n_jobs = None
- if verbose < 1:
- self.sphinxopts += ["-q"]
- else:
- for i in range(1, sphinx_args.verbose):
- self.sphinxopts += ["-v"]
- def __init__(self, builddir, venv=None, verbose=False, n_jobs=None,
- interactive=None):
- """Initialize internal variables"""
- self.venv = venv
- self.verbose = None
- #
- # Normal variables passed from Kernel's makefile
- #
- self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
- self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
- self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
- #
- # Kernel main Makefile defines a PYTHON3 variable whose default is
- # "python3". When set to a different value, it allows running a
- # diferent version than the default official python3 package.
- # Several distros package python3xx-sphinx packages with newer
- # versions of Python and sphinx-build.
- #
- # Honor such variable different than default
- #
- self.python = os.environ.get("PYTHON3")
- if self.python == "python3":
- self.python = None
- if not interactive:
- self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
- else:
- self.latexopts = os.environ.get("LATEXOPTS", "")
- if not verbose:
- verbose = bool(os.environ.get("KBUILD_VERBOSE", "") != "")
- if verbose is not None:
- self.verbose = verbose
- #
- # Source tree directory. This needs to be at os.environ, as
- # Sphinx extensions use it
- #
- self.srctree = os.environ.get("srctree")
- if not self.srctree:
- self.srctree = "."
- os.environ["srctree"] = self.srctree
- #
- # Now that we can expand srctree, get other directories as well
- #
- self.sphinxbuild = os.environ.get("SPHINXBUILD", "sphinx-build")
- self.kerneldoc = self.get_path(os.environ.get("KERNELDOC",
- "tools/docs/kernel-doc"))
- self.builddir = self.get_path(builddir, use_cwd=True, abs_path=True)
- #
- # Get directory locations for LaTeX build toolchain
- #
- self.pdflatex_cmd = shutil.which(self.pdflatex)
- self.latexmk_cmd = shutil.which("latexmk")
- self.env = os.environ.copy()
- self.get_sphinx_extra_opts(n_jobs)
- #
- # If venv command line argument is specified, run Sphinx from venv
- #
- if venv:
- bin_dir = os.path.join(venv, "bin")
- if not os.path.isfile(os.path.join(bin_dir, "activate")):
- sys.exit(f"Venv {venv} not found.")
- # "activate" virtual env
- self.env["PATH"] = bin_dir + ":" + self.env["PATH"]
- self.env["VIRTUAL_ENV"] = venv
- if "PYTHONHOME" in self.env:
- del self.env["PYTHONHOME"]
- print(f"Setting venv to {venv}")
- def run_sphinx(self, sphinx_build, build_args, *args, **pwargs):
- """
- Executes sphinx-build using current python3 command.
- When calling via GNU make, POSIX jobserver is used to tell how
- many jobs are still available from a job pool. claim all remaining
- jobs, as we don't want sphinx-build to run in parallel with other
- jobs.
- Despite that, the user may actually force a different value than
- the number of available jobs via command line.
- The "with" logic here is used to ensure that the claimed jobs will
- be freed once subprocess finishes
- """
- with JobserverExec() as jobserver:
- if jobserver.claim:
- #
- # when GNU make is used, claim available jobs from jobserver
- #
- n_jobs = str(jobserver.claim)
- else:
- #
- # Otherwise, let sphinx decide by default
- #
- n_jobs = "auto"
- #
- # If explicitly requested via command line, override default
- #
- if self.n_jobs:
- n_jobs = str(self.n_jobs)
- #
- # We can't simply call python3 sphinx-build, as OpenSUSE
- # Tumbleweed uses an ELF binary file (/usr/bin/alts) to switch
- # between different versions of sphinx-build. So, only call it
- # prepending "python3.xx" when PYTHON3 variable is not default.
- #
- if self.python:
- cmd = [self.python]
- else:
- cmd = []
- cmd += [sphinx_build]
- cmd += [f"-j{n_jobs}"]
- cmd += build_args
- cmd += self.sphinxopts
- if self.verbose:
- print(" ".join(cmd))
- return subprocess.call(cmd, *args, **pwargs)
- def handle_html(self, css, output_dir):
- """
- Extra steps for HTML and epub output.
- For such targets, we need to ensure that CSS will be properly
- copied to the output _static directory
- """
- if css:
- css = os.path.expanduser(css)
- if not css.startswith("/"):
- css = os.path.join(self.srctree, css)
- static_dir = os.path.join(output_dir, "_static")
- os.makedirs(static_dir, exist_ok=True)
- try:
- shutil.copy2(css, static_dir)
- except (OSError, IOError) as e:
- print(f"Warning: Failed to copy CSS: {e}", file=sys.stderr)
- def build_pdf_file(self, latex_cmd, from_dir, path):
- """Builds a single pdf file using latex_cmd"""
- try:
- subprocess.run(latex_cmd + [path],
- cwd=from_dir, check=True, env=self.env)
- return True
- except subprocess.CalledProcessError:
- return False
- def pdf_parallel_build(self, tex_suffix, latex_cmd, tex_files, n_jobs):
- """Build PDF files in parallel if possible"""
- builds = {}
- build_failed = False
- max_len = 0
- has_tex = False
- #
- # LaTeX PDF error code is almost useless for us:
- # any warning makes it non-zero. For kernel doc builds it always return
- # non-zero even when build succeeds. So, let's do the best next thing:
- # Ignore build errors. At the end, check if all PDF files were built,
- # printing a summary with the built ones and returning 0 if all of
- # them were actually built.
- #
- with futures.ThreadPoolExecutor(max_workers=n_jobs) as executor:
- jobs = {}
- for from_dir, pdf_dir, entry in tex_files:
- name = entry.name
- if not name.endswith(tex_suffix):
- continue
- name = name[:-len(tex_suffix)]
- has_tex = True
- future = executor.submit(self.build_pdf_file, latex_cmd,
- from_dir, entry.path)
- jobs[future] = (from_dir, pdf_dir, name)
- for future in futures.as_completed(jobs):
- from_dir, pdf_dir, name = jobs[future]
- pdf_name = name + ".pdf"
- pdf_from = os.path.join(from_dir, pdf_name)
- pdf_to = os.path.join(pdf_dir, pdf_name)
- out_name = os.path.relpath(pdf_to, self.builddir)
- max_len = max(max_len, len(out_name))
- try:
- success = future.result()
- if success and os.path.exists(pdf_from):
- os.rename(pdf_from, pdf_to)
- #
- # if verbose, get the name of built PDF file
- #
- if self.verbose:
- builds[out_name] = "SUCCESS"
- else:
- builds[out_name] = "FAILED"
- build_failed = True
- except futures.Error as e:
- builds[out_name] = f"FAILED ({repr(e)})"
- build_failed = True
- #
- # Handle case where no .tex files were found
- #
- if not has_tex:
- out_name = "LaTeX files"
- max_len = max(max_len, len(out_name))
- builds[out_name] = "FAILED: no .tex files were generated"
- build_failed = True
- return builds, build_failed, max_len
- def handle_pdf(self, output_dirs, deny_vf):
- """
- Extra steps for PDF output.
- As PDF is handled via a LaTeX output, after building the .tex file,
- a new build is needed to create the PDF output from the latex
- directory.
- """
- builds = {}
- max_len = 0
- tex_suffix = ".tex"
- tex_files = []
- #
- # Since early 2024, Fedora and openSUSE tumbleweed have started
- # deploying variable-font format of "Noto CJK", causing LaTeX
- # to break with CJK. Work around it, by denying the variable font
- # usage during xelatex build by passing the location of a config
- # file with a deny list.
- #
- # See tools/docs/lib/latex_fonts.py for more details.
- #
- if deny_vf:
- deny_vf = os.path.expanduser(deny_vf)
- if os.path.isdir(deny_vf):
- self.env["XDG_CONFIG_HOME"] = deny_vf
- for from_dir in output_dirs:
- pdf_dir = os.path.join(from_dir, "../pdf")
- os.makedirs(pdf_dir, exist_ok=True)
- if self.latexmk_cmd:
- latex_cmd = [self.latexmk_cmd, f"-{self.pdflatex}"]
- else:
- latex_cmd = [self.pdflatex]
- latex_cmd.extend(shlex.split(self.latexopts))
- # Get a list of tex files to process
- with os.scandir(from_dir) as it:
- for entry in it:
- if entry.name.endswith(tex_suffix):
- tex_files.append((from_dir, pdf_dir, entry))
- #
- # When using make, this won't be used, as the number of jobs comes
- # from POSIX jobserver. So, this covers the case where build comes
- # from command line. On such case, serialize by default, except if
- # the user explicitly sets the number of jobs.
- #
- n_jobs = 1
- # n_jobs is either an integer or "auto". Only use it if it is a number
- if self.n_jobs:
- try:
- n_jobs = int(self.n_jobs)
- except ValueError:
- pass
- #
- # When using make, jobserver.claim is the number of jobs that were
- # used with "-j" and that aren't used by other make targets
- #
- with JobserverExec() as jobserver:
- n_jobs = 1
- #
- # Handle the case when a parameter is passed via command line,
- # using it as default, if jobserver doesn't claim anything
- #
- if self.n_jobs:
- try:
- n_jobs = int(self.n_jobs)
- except ValueError:
- pass
- if jobserver.claim:
- n_jobs = jobserver.claim
- builds, build_failed, max_len = self.pdf_parallel_build(tex_suffix,
- latex_cmd,
- tex_files,
- n_jobs)
- #
- # In verbose mode, print a summary with the build results per file.
- # Otherwise, print a single line with all failures, if any.
- # On both cases, return code 1 indicates build failures,
- #
- if self.verbose:
- msg = "Summary"
- msg += "\n" + "=" * len(msg)
- print()
- print(msg)
- for pdf_name, pdf_file in builds.items():
- print(f"{pdf_name:<{max_len}}: {pdf_file}")
- print()
- if build_failed:
- msg = LatexFontChecker().check()
- if msg:
- print(msg)
- sys.exit("Error: not all PDF files were created.")
- elif build_failed:
- n_failures = len(builds)
- failures = ", ".join(builds.keys())
- msg = LatexFontChecker().check()
- if msg:
- print(msg)
- sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
- def handle_info(self, output_dirs):
- """
- Extra steps for Info output.
- For texinfo generation, an additional make is needed from the
- texinfo directory.
- """
- for output_dir in output_dirs:
- try:
- subprocess.run(["make", "info"], cwd=output_dir, check=True)
- except subprocess.CalledProcessError as e:
- sys.exit(f"Error generating info docs: {e}")
- def handle_man(self, kerneldoc, docs_dir, src_dir, output_dir):
- """
- Create man pages from kernel-doc output
- """
- re_kernel_doc = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
- re_man = re.compile(r'^\.TH "[^"]*" (\d+) "([^"]*)"')
- if docs_dir == src_dir:
- #
- # Pick the entire set of kernel-doc markups from the entire tree
- #
- kdoc_files = set([self.srctree])
- else:
- kdoc_files = set()
- for fname in glob(os.path.join(src_dir, "**"), recursive=True):
- if os.path.isfile(fname) and fname.endswith(".rst"):
- with open(fname, "r", encoding="utf-8") as in_fp:
- data = in_fp.read()
- for line in data.split("\n"):
- match = re_kernel_doc.match(line)
- if match:
- if os.path.isfile(match.group(1)):
- kdoc_files.add(match.group(1))
- if not kdoc_files:
- sys.exit(f"Directory {src_dir} doesn't contain kernel-doc tags")
- cmd = [ kerneldoc, "-m" ] + sorted(kdoc_files)
- try:
- if self.verbose:
- print(" ".join(cmd))
- result = subprocess.run(cmd, stdout=subprocess.PIPE, text= True)
- if result.returncode:
- print(f"Warning: kernel-doc returned {result.returncode} warnings")
- except (OSError, ValueError, subprocess.SubprocessError) as e:
- sys.exit(f"Failed to create man pages for {src_dir}: {repr(e)}")
- fp = None
- try:
- for line in result.stdout.split("\n"):
- match = re_man.match(line)
- if not match:
- if fp:
- fp.write(line + '\n')
- continue
- if fp:
- fp.close()
- fname = f"{output_dir}/{match.group(2)}.{match.group(1)}"
- if self.verbose:
- print(f"Creating {fname}")
- fp = open(fname, "w", encoding="utf-8")
- fp.write(line + '\n')
- finally:
- if fp:
- fp.close()
- def cleandocs(self, builder): # pylint: disable=W0613
- """Remove documentation output directory"""
- shutil.rmtree(self.builddir, ignore_errors=True)
- def build(self, target, sphinxdirs=None,
- theme=None, css=None, paper=None, deny_vf=None,
- skip_sphinx=False):
- """
- Build documentation using Sphinx. This is the core function of this
- module. It prepares all arguments required by sphinx-build.
- """
- builder = TARGETS[target]["builder"]
- out_dir = TARGETS[target].get("out_dir", "")
- #
- # Cleandocs doesn't require sphinx-build
- #
- if target == "cleandocs":
- self.cleandocs(builder)
- return
- if theme:
- os.environ["DOCS_THEME"] = theme
- #
- # Other targets require sphinx-build, so check if it exists
- #
- if not skip_sphinx:
- sphinxbuild = shutil.which(self.sphinxbuild, path=self.env["PATH"])
- if not sphinxbuild and target != "mandocs":
- sys.exit(f"Error: {self.sphinxbuild} not found in PATH.\n")
- if target == "pdfdocs":
- if not self.pdflatex_cmd and not self.latexmk_cmd:
- sys.exit("Error: pdflatex or latexmk required for PDF generation")
- docs_dir = os.path.abspath(os.path.join(self.srctree, "Documentation"))
- #
- # Fill in base arguments for Sphinx build
- #
- kerneldoc = self.kerneldoc
- if kerneldoc.startswith(self.srctree):
- kerneldoc = os.path.relpath(kerneldoc, self.srctree)
- if not sphinxdirs:
- sphinxdirs = os.environ.get("SPHINXDIRS", ".")
- #
- # sphinxdirs can be a list or a whitespace-separated string
- #
- sphinxdirs_list = []
- for sphinxdir in sphinxdirs:
- if isinstance(sphinxdir, list):
- sphinxdirs_list += sphinxdir
- else:
- sphinxdirs_list += sphinxdir.split()
- args = [ "-b", builder, "-c", docs_dir ]
- if builder == "latex":
- if not paper:
- paper = PAPER[1]
- args.extend(["-D", f"latex_elements.papersize={paper}paper"])
- rustdoc = self.check_rust(sphinxdirs_list)
- if rustdoc:
- args.extend(["-t", "rustdoc"])
- #
- # The sphinx-build tool has a bug: internally, it tries to set
- # locale with locale.setlocale(locale.LC_ALL, ''). This causes a
- # crash if language is not set. Detect and fix it.
- #
- try:
- locale.setlocale(locale.LC_ALL, '')
- except locale.Error:
- self.env["LC_ALL"] = "C"
- #
- # Step 1: Build each directory in separate.
- #
- # This is not the best way of handling it, as cross-references between
- # them will be broken, but this is what we've been doing since
- # the beginning.
- #
- output_dirs = []
- for sphinxdir in sphinxdirs_list:
- src_dir = os.path.join(docs_dir, sphinxdir)
- doctree_dir = os.path.join(self.builddir, ".doctrees")
- output_dir = os.path.join(self.builddir, sphinxdir, out_dir)
- #
- # Make directory names canonical
- #
- src_dir = os.path.normpath(src_dir)
- doctree_dir = os.path.normpath(doctree_dir)
- output_dir = os.path.normpath(output_dir)
- os.makedirs(doctree_dir, exist_ok=True)
- os.makedirs(output_dir, exist_ok=True)
- output_dirs.append(output_dir)
- build_args = args + [
- "-d", doctree_dir,
- "-D", f"version={self.kernelversion}",
- "-D", f"release={self.kernelrelease}",
- "-D", f"kerneldoc_srctree={self.srctree}",
- src_dir,
- output_dir,
- ]
- if target == "mandocs":
- self.handle_man(kerneldoc, docs_dir, src_dir, output_dir)
- elif not skip_sphinx:
- try:
- result = self.run_sphinx(sphinxbuild, build_args,
- env=self.env)
- if result:
- sys.exit(f"Build failed: return code: {result}")
- except (OSError, ValueError, subprocess.SubprocessError) as e:
- sys.exit(f"Build failed: {repr(e)}")
- #
- # Ensure that each html/epub output will have needed static files
- #
- if target in ["htmldocs", "epubdocs"]:
- self.handle_html(css, output_dir)
- #
- # Step 2: Some targets (PDF and info) require an extra step once
- # sphinx-build finishes
- #
- if target == "pdfdocs":
- self.handle_pdf(output_dirs, deny_vf)
- elif target == "infodocs":
- self.handle_info(output_dirs)
- if rustdoc and target in ["htmldocs", "epubdocs"]:
- print("Building rust docs")
- if "MAKE" in self.env:
- cmd = [self.env["MAKE"]]
- else:
- cmd = ["make", "LLVM=1"]
- cmd += [ "rustdoc"]
- if self.verbose:
- print(" ".join(cmd))
- try:
- subprocess.run(cmd, check=True)
- except subprocess.CalledProcessError as e:
- print(f"Ignored errors when building rustdoc: {e}. Is RUST enabled?",
- file=sys.stderr)
- def jobs_type(value):
- """
- Handle valid values for -j. Accepts Sphinx "-jauto", plus a number
- equal or bigger than one.
- """
- if value is None:
- return None
- if value.lower() == 'auto':
- return value.lower()
- try:
- if int(value) >= 1:
- return value
- raise argparse.ArgumentTypeError(f"Minimum jobs is 1, got {value}")
- except ValueError:
- raise argparse.ArgumentTypeError(f"Must be 'auto' or positive integer, got {value}") # pylint: disable=W0707
- EPILOG="""
- Besides the command line arguments, several environment variables affect its
- default behavior, meant to be used when called via Kernel Makefile:
- - KERNELVERSION: Kernel major version
- - KERNELRELEASE: Kernel release
- - KBUILD_VERBOSE: Contains the value of "make V=[0|1] variable.
- When V=0 (KBUILD_VERBOSE=0), sets verbose level to "-q".
- - SPHINXBUILD: Documentation build tool (default: "sphinx-build").
- - SPHINXOPTS: Extra options pased to SPHINXBUILD
- (default: "-j auto" and "-q" if KBUILD_VERBOSE=0).
- The "-v" flag can be used to increase verbosity.
- If V=0, the first "-v" will drop "-q".
- - PYTHON3: Python command to run SPHINXBUILD
- - PDFLATEX: LaTeX PDF engine. (default: "xelatex")
- - LATEXOPTS: Optional set of command line arguments to the LaTeX engine
- - srctree: Location of the Kernel root directory (default: ".").
- """
- def main():
- """
- Main function. The only mandatory argument is the target. If not
- specified, the other arguments will use default values if not
- specified at os.environ.
- """
- parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
- description=__doc__,
- epilog=EPILOG)
- parser.add_argument("target", choices=list(TARGETS.keys()),
- help="Documentation target to build")
- parser.add_argument("--sphinxdirs", nargs="+",
- help="Specific directories to build")
- parser.add_argument("--builddir", default="output",
- help="Sphinx configuration file (default: %(default)s)")
- parser.add_argument("--theme", help="Sphinx theme to use")
- parser.add_argument("--css", help="Custom CSS file for HTML/EPUB")
- parser.add_argument("--paper", choices=PAPER, default=PAPER[0],
- help="Paper size for LaTeX/PDF output")
- parser.add_argument('--deny-vf',
- help="Configuration to deny variable fonts on pdf builds")
- parser.add_argument("-v", "--verbose", action='store_true',
- help="place build in verbose mode")
- parser.add_argument('-j', '--jobs', type=jobs_type,
- help="Sets number of jobs to use with sphinx-build(default: auto)")
- parser.add_argument('-i', '--interactive', action='store_true',
- help="Change latex default to run in interactive mode")
- parser.add_argument('-s', '--skip-sphinx-build', action='store_true',
- help="Skip sphinx-build step")
- parser.add_argument("-V", "--venv", nargs='?', const=f'{VENV_DEFAULT}',
- default=None,
- help=f'If used, run Sphinx from a venv dir (default dir: {VENV_DEFAULT})')
- args = parser.parse_args()
- PythonVersion.check_python(MIN_PYTHON_VERSION, show_alternatives=True,
- bail_out=True)
- builder = SphinxBuilder(builddir=args.builddir, venv=args.venv,
- verbose=args.verbose, n_jobs=args.jobs,
- interactive=args.interactive)
- builder.build(args.target, sphinxdirs=args.sphinxdirs,
- theme=args.theme, css=args.css, paper=args.paper,
- deny_vf=args.deny_vf,
- skip_sphinx=args.skip_sphinx_build)
- if __name__ == "__main__":
- main()
|