Skip to contentSkip to conversation

Split sphinx call logic from docs Makefile

PATCH In review Mauro Carvalho Chehab 3 participants 35 messages Sep 18, 2025 – Oct 18, 2025

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

v8 cover Split sphinx call logic from docs Makefile

Hi Jon,

v8 contains a bug fix for ./tools/docs/check-variable-fonts.py command line together with a new --deny-vf argument to it, plus addresses a couple checkpatch warnings. Only 4 patches changed: patches 1, 2, 4 and 5.

-

This series should probably be called:

"Move the trick-or-treat build hacks accumulated over time
into a single place and document them."

as this reflects its main goal. As such:

- it places the jobserver logic on a library;
- it removes sphinx/parallel-wrapper.sh;
- the code now properly implements a jobserver-aware logic to do the parallelism when called via GNU make, failing back to "-j" when there's no jobserver;
- converts check-variable-fonts.sh to Python and uses it via function call;
- drops an extra script to generate man pages, adding a makefile target for it;
- ensures that return code is 0 when PDF successfully builds;
- about half of the script is comments and documentation.

I tried to do my best to document all tricks that are inside the script. This way, the docs build steps is now documented.

It should be noticed that it is out of the scope of this series to change the implementation. Surely the process can be improved, but first let's consolidate and document everything on a single place.

Such script was written in a way that it can be called either directly or via a Makefile. Running outside Makefile is interesting specially when debug is needed. The command line interface replaces the need of having lots of env vars before calling sphinx-build:

    $ ./tools/docs/sphinx-build-wrapper --help
    usage: sphinx-build-wrapper [-h]
           [--sphinxdirs SPHINXDIRS [SPHINXDIRS ...]] [--conf CONF]
           [--builddir BUILDDIR] [--theme THEME] [--css CSS] [--paper {,a4,letter}] [-v]
           [-j JOBS] [-i] [-V [VENV]]
           {cleandocs,linkcheckdocs,htmldocs,epubdocs,texinfodocs,infodocs,mandocs,latexdocs,pdfdocs,xmldocs}

Kernel documentation builder

positional arguments:
{cleandocs,linkcheckdocs,htmldocs,epubdocs,texinfodocs,infodocs,mandocs,latexdocs,pdfdocs,xmldocs}
Documentation target to build

options:
-h, --help show this help message and exit
--sphinxdirs SPHINXDIRS [SPHINXDIRS ...]
Specific directories to build
--conf CONF Sphinx configuration file
--builddir BUILDDIR Sphinx configuration file
--theme THEME Sphinx theme to use
--css CSS Custom CSS file for HTML/EPUB
--paper {,a4,letter} Paper size for LaTeX/PDF output
-v, --verbose place build in verbose mode
-j, --jobs JOBS Sets number of jobs to use with sphinx-build
-i, --interactive Change latex default to run in interactive mode
-V, --venv [VENV] If used, run Sphinx from a venv dir (default dir: sphinx_latest)

the only mandatory argument is the target, which is identical with "make" targets.

The call inside Makefile doesn't use the last four arguments. They're there to help identifying problems at the build:

    -v makes the output verbose;
    -j helps to test parallelism;
    -i runs latexmk in interactive mode, allowing to debug PDF
       build issues;
    -V is useful when testing it with different venvs.

When used with GNU make (or some other make which implements jobserver), a call like:

make -j <targets> htmldocs

will make the wrapper to automatically use POSIX jobserver to claim the number of available job slots, calling sphinx-build with a "-j" parameter reflecting it. ON such case, the default can be overriden via SPHINXDIRS argument.

Visiable changes when compared with the old behavior:

When V=0, the only visible difference is that:
- pdfdocs target now returns 0 on success, 1 on failures. This addresses an issue over the current process where we it always return success even on failures;
- it will now print the name of PDF files that failed to build, if any.

In verbose mode, sphinx-build-wrapper and sphinx-build command lines are now displayed.

---

v8:
- fixed a typo at check-variable-fonts.py libary causing the command line to not work properly;
- added a --deny-vf command line arg for check-variable-fonts.py;
- fixed two checkpatch warnings (duplicated "allows" word on a patch description, and "childs" instead of "children).

v7:
- added a --help option to ./tools/docs/check-variable-fonts.py;
- document FONTS_CONF_DENY_VF=;
- moved the python_version changes to be earlier in the series, before moving it to a separate library, as suggested by Jani;
- added a --rustdoc to the wrapper, cleaning up Makefile even further;
- solved some problems related to DENY_VF logic.

v6:
- On success, PDF output is identical as before when V=0;
- when V=1 is used, PDF output will print a build summary, as on v5;
- solved a problem when multiple PDF files have the same basename but are located on different directories;
- merged a patch series converting check-variable-fonts.sh to Python. Its logic is now called directly without running a subprocess.
- venv patch moved to the end.

v5:
- merged comments with the script;
- placed n_jobs on a separate function;
- nitpick: dropped a for loop used instead of list append.

v4:
- updated references for sphinx-pre-install after its rename;
- added some extra patches to add more options to python_version, allowing it to bail out and suggest alternatives;
- added a patch at the end to explicitly break doc builds when python3 points to python3.6 or older.

v3:
- rebased on the top of docs-next;
- added two patches to build man files that were on a separate patch series.

v2:
- there's no generic exception handler anymore;
- it moves sphinx-pre-install to tools/docs;
- the logic which ensures a minimal Python version got moved to a library, which is now used by both pre-install and wrapper;
- The first wrapper (05/13) doesn't contain comments (except for shebang and SPDX). The goal is to help showing the size increase when moving from Makefile to Python. Some file increase is unavoidable, as Makefile is more compact: no includes, multple statements per line, no argparse, etc;
- The second patch adds docstrings and comments. It has almost the same size of the code itself;
- I moved the venv logic to a third wrapper patch;
- I fixed an issue at the paraller build logic;
- There are no generic except blocks anymore.

Mauro Carvalho Chehab (24):
scripts/jobserver-exec: move the code to a class scripts/jobserver-exec: move its class to the lib directory scripts/jobserver-exec: add a help message
scripts: check-variable-fonts.sh: convert to Python tools/docs: check-variable-fonts.py: split into a lib and an exec file check-variable-fonts.py: add a helper to display instructions
scripts: sphinx-pre-install: move it to tools/docs tools/docs: sphinx-pre-install: drop a debug print tools/docs: sphinx-pre-install: allow check for alternatives and bail
out
tools/docs: python_version: move version check from sphinx-pre-install tools/docs: sphinx-build-wrapper: add a wrapper for sphinx-build
docs: parallel-wrapper.sh: remove script
docs: Makefile: document latex/PDF PAPER= parameter
docs: Makefile: document FONTS_CONF_DENY_VF= parameter tools/docs: sphinx-build-wrapper: add an argument for LaTeX
interactive mode
tools/docs: sphinx-build-wrapper: allow building PDF files in parallel tools/docs,scripts: sphinx-*: prevent sphinx-build crashes tools/docs: sphinx-build-wrapper: Fix output for duplicated names
docs: add support to build manpages from kerneldoc output
tools: kernel-doc: add a see also section at man pages
scripts: kdoc_parser.py: warn about Python version only once tools/docs: sphinx-build-wrapper: move rust doc builder to wrapper tools/docs: sphinx-* break documentation bulds on openSUSE tools/docs: sphinx-build-wrapper: add support to run inside venv

 Documentation/Makefile                        | 144 +---
 Documentation/doc-guide/kernel-doc.rst        |  29 +-
 Documentation/doc-guide/sphinx.rst            |   4 +-
 Documentation/sphinx/kerneldoc-preamble.sty   |   2 +-
 Documentation/sphinx/parallel-wrapper.sh      |  33 -
 .../translations/it_IT/doc-guide/sphinx.rst   |   4 +-
 .../translations/zh_CN/doc-guide/sphinx.rst   |   4 +-
 Documentation/translations/zh_CN/how-to.rst   |   2 +-
 MAINTAINERS                                   |   4 +-
 Makefile                                      |   2 +-
 scripts/check-variable-fonts.sh               | 115 ---
 scripts/jobserver-exec                        |  88 +-
 scripts/lib/jobserver.py                      | 149 ++++
 scripts/lib/kdoc/kdoc_files.py                |   5 +-
 scripts/lib/kdoc/kdoc_output.py               |  84 +-
 scripts/lib/kdoc/kdoc_parser.py               |   7 +-
 scripts/split-man.pl                          |  28 -
 tools/docs/check-variable-fonts.py            |  33 +
 tools/docs/lib/latex_fonts.py                 | 167 ++++
 tools/docs/lib/python_version.py              | 178 ++++
 tools/docs/sphinx-build-wrapper               | 816 ++++++++++++++++++
 {scripts => tools/docs}/sphinx-pre-install    | 135 +--
 22 files changed, 1540 insertions(+), 493 deletions(-)
 delete mode 100644 Documentation/sphinx/parallel-wrapper.sh
 delete mode 100755 scripts/check-variable-fonts.sh
 create mode 100755 scripts/lib/jobserver.py
 delete mode 100755 scripts/split-man.pl
 create mode 100755 tools/docs/check-variable-fonts.py
 create mode 100755 tools/docs/lib/latex_fonts.py
 create mode 100644 tools/docs/lib/python_version.py
 create mode 100755 tools/docs/sphinx-build-wrapper
 rename {scripts => tools/docs}/sphinx-pre-install (93%)
Signature · 1 line
2.51.0
#1 lore raw

PATCH v8 1/24 scripts/jobserver-exec: move the code to a class

Convert the code inside jobserver-exec to a class and properly document it.

Using a class allows reusing the jobserver logic on other scripts.

While the main code remains unchanged, being compatible with Python 2.6 and 3.0+, its coding style now follows a more modern standard, having tabs replaced by a 4-spaces indent, passing autopep8, black and pylint.

The code allows using a pythonic way to enter/exit a python code, e.g. it now supports:

	with JobserverExec() as jobserver:
	    jobserver.run(sys.argv[1:])

With the new code, the __exit__() function should ensure that the jobserver slot will be closed at the end, even if something bad happens somewhere.

---
 scripts/jobserver-exec | 218 ++++++++++++++++++++++++++++-------------
 1 file changed, 151 insertions(+), 67 deletions(-)
scripts/jobserver-exec +151 -67
index 7eca035472d3..897c0cca9e6e 100755
@@ -1,77 +1,161 @@
 #!/usr/bin/env python3
 # SPDX-License-Identifier: GPL-2.0+
 #
+# pylint: disable=C0103,C0209
+#
 # This determines how many parallel tasks "make" is expecting, as it is
 # not exposed via an special variables, reserves them all, runs a subprocess
 # with PARALLELISM environment variable set, and releases the jobs back again.
 #
 # https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver
-from __future__ import print_function
-import os, sys, errno
+
+"""
+Interacts with the POSIX jobserver during the Kernel build time.
+
+A "normal" jobserver task, like the one initiated by a make subrocess would do:
+
+    - open read/write file descriptors to communicate with the job server;
+    - ask for one slot by calling:
+        claim = os.read(reader, 1)
+    - when the job finshes, call:
+        os.write(writer, b"+")  # os.write(writer, claim)
+
+Here, the goal is different: This script aims to get the remaining number
+of slots available, using all of them to run a command which handle tasks in
+parallel. To to that, it has a loop that ends only after there are no
+slots left. It then increments the number by one, in order to allow a
+call equivalent to make -j$((claim+1)), e.g. having a parent make creating
+$claim child to do the actual work.
+
+The end goal here is to keep the total number of build tasks under the
+limit established by the initial make -j$n_proc call.
+"""
+
+import errno
+import os
 import subprocess
+import sys
 
-# Extract and prepare jobserver file descriptors from environment.
-claim = 0
-jobs = b""
-try:
-	# Fetch the make environment options.
-	flags = os.environ['MAKEFLAGS']
-
-	# Look for "--jobserver=R,W"
-	# Note that GNU Make has used --jobserver-fds and --jobserver-auth
-	# so this handles all of them.
-	opts = [x for x in flags.split(" ") if x.startswith("--jobserver")]
-
-	# Parse out R,W file descriptor numbers and set them nonblocking.
-	# If the MAKEFLAGS variable contains multiple instances of the
-	# --jobserver-auth= option, the last one is relevant.
-	fds = opts[-1].split("=", 1)[1]
-
-	# Starting with GNU Make 4.4, named pipes are used for reader and writer.
-	# Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134
-	_, _, path = fds.partition('fifo:')
-
-	if path:
-		reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
-		writer = os.open(path, os.O_WRONLY)
-	else:
-		reader, writer = [int(x) for x in fds.split(",", 1)]
-		# Open a private copy of reader to avoid setting nonblocking
-		# on an unexpecting process with the same reader fd.
-		reader = os.open("/proc/self/fd/%d" % (reader),
-				 os.O_RDONLY | os.O_NONBLOCK)
-
-	# Read out as many jobserver slots as possible.
-	while True:
-		try:
-			slot = os.read(reader, 8)
-			jobs += slot
-		except (OSError, IOError) as e:
-			if e.errno == errno.EWOULDBLOCK:
-				# Stop at the end of the jobserver queue.
-				break
-			# If something went wrong, give back the jobs.
-			if len(jobs):
-				os.write(writer, jobs)
-			raise e
-	# Add a bump for our caller's reserveration, since we're just going
-	# to sit here blocked on our child.
-	claim = len(jobs) + 1
-except (KeyError, IndexError, ValueError, OSError, IOError) as e:
-	# Any missing environment strings or bad fds should result in just
-	# not being parallel.
-	pass
-
-# We can only claim parallelism if there was a jobserver (i.e. a top-level
-# "-jN" argument) and there were no other failures. Otherwise leave out the
-# environment variable and let the child figure out what is best.
-if claim > 0:
-	os.environ['PARALLELISM'] = '%d' % (claim)
-
-rc = subprocess.call(sys.argv[1:])
-
-# Return all the reserved slots.
-if len(jobs):
-	os.write(writer, jobs)
-
-sys.exit(rc)
+
+class JobserverExec:
+    """
+    Claim all slots from make using POSIX Jobserver.
+
+    The main methods here are:
+    - open(): reserves all slots;
+    - close(): method returns all used slots back to make;
+    - run(): executes a command setting PARALLELISM=<available slots jobs + 1>
+    """
+
+    def __init__(self):
+        """Initialize internal vars"""
+        self.claim = 0
+        self.jobs = b""
+        self.reader = None
+        self.writer = None
+        self.is_open = False
+
+    def open(self):
+        """Reserve all available slots to be claimed later on"""
+
+        if self.is_open:
+            return
+
+        try:
+            # Fetch the make environment options.
+            flags = os.environ["MAKEFLAGS"]
+            # Look for "--jobserver=R,W"
+            # Note that GNU Make has used --jobserver-fds and --jobserver-auth
+            # so this handles all of them.
+            opts = [x for x in flags.split(" ") if x.startswith("--jobserver")]
+
+            # Parse out R,W file descriptor numbers and set them nonblocking.
+            # If the MAKEFLAGS variable contains multiple instances of the
+            # --jobserver-auth= option, the last one is relevant.
+            fds = opts[-1].split("=", 1)[1]
+
+            # Starting with GNU Make 4.4, named pipes are used for reader
+            # and writer.
+            # Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134
+            _, _, path = fds.partition("fifo:")
+
+            if path:
+                self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
+                self.writer = os.open(path, os.O_WRONLY)
+            else:
+                self.reader, self.writer = [int(x) for x in fds.split(",", 1)]
+                # Open a private copy of reader to avoid setting nonblocking
+                # on an unexpecting process with the same reader fd.
+                self.reader = os.open("/proc/self/fd/%d" % (self.reader),
+                                      os.O_RDONLY | os.O_NONBLOCK)
+
+            # Read out as many jobserver slots as possible
+            while True:
+                try:
+                    slot = os.read(self.reader, 8)
+                    self.jobs += slot
+                except (OSError, IOError) as e:
+                    if e.errno == errno.EWOULDBLOCK:
+                        # Stop at the end of the jobserver queue.
+                        break
+                    # If something went wrong, give back the jobs.
+                    if self.jobs:
+                        os.write(self.writer, self.jobs)
+                    raise e
+
+            # Add a bump for our caller's reserveration, since we're just going
+            # to sit here blocked on our child.
+            self.claim = len(self.jobs) + 1
+
+        except (KeyError, IndexError, ValueError, OSError, IOError):
+            # Any missing environment strings or bad fds should result in just
+            # not being parallel.
+            self.claim = None
+
+        self.is_open = True
+
+    def close(self):
+        """Return all reserved slots to Jobserver"""
+
+        if not self.is_open:
+            return
+
+        # Return all the reserved slots.
+        if len(self.jobs):
+            os.write(self.writer, self.jobs)
+
+        self.is_open = False
+
+    def __enter__(self):
+        self.open()
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_traceback):
+        self.close()
+
+    def run(self, cmd):
+        """
+        Run a command setting PARALLELISM env variable to the number of
+        available job slots (claim) + 1, e.g. it will reserve claim slots
+        to do the actual build work, plus one to monitor its children.
+        """
+        self.open()             # Ensure that self.claim is set
+
+        # We can only claim parallelism if there was a jobserver (i.e. a
+        # top-level "-jN" argument) and there were no other failures. Otherwise
+        # leave out the environment variable and let the child figure out what
+        # is best.
+        if self.claim:
+            os.environ["PARALLELISM"] = str(self.claim)
+
+        return subprocess.call(cmd)
+
+
+def main():
+    """Main program"""
+    with JobserverExec() as jobserver:
+        jobserver.run(sys.argv[1:])
+
+
+if __name__ == "__main__":
+    main()
Signature · 1 line
2.51.0
maintainer
#2 lore raw

Re: [PATCH v8 01/24] scripts/jobserver-exec: move the code to a class

Quoted 64 lines · Mauro Carvalho Chehab
scripts/jobserver-exec +64 -0
+class JobserverExec:
+    """
+    Claim all slots from make using POSIX Jobserver.
+
+    The main methods here are:
+    - open(): reserves all slots;
+    - close(): method returns all used slots back to make;
+    - run(): executes a command setting PARALLELISM=<available slots jobs + 1>
+    """
+
+    def __init__(self):
+        """Initialize internal vars"""
+        self.claim = 0
+        self.jobs = b""
+        self.reader = None
+        self.writer = None
+        self.is_open = False
+
+    def open(self):
+        """Reserve all available slots to be claimed later on"""
+
+        if self.is_open:
+            return
+
+        try:
+            # Fetch the make environment options.
+            flags = os.environ["MAKEFLAGS"]
+            # Look for "--jobserver=R,W"
+            # Note that GNU Make has used --jobserver-fds and --jobserver-auth
+            # so this handles all of them.
+            opts = [x for x in flags.split(" ") if x.startswith("--jobserver")]
+
+            # Parse out R,W file descriptor numbers and set them nonblocking.
+            # If the MAKEFLAGS variable contains multiple instances of the
+            # --jobserver-auth= option, the last one is relevant.
+            fds = opts[-1].split("=", 1)[1]
+
+            # Starting with GNU Make 4.4, named pipes are used for reader
+            # and writer.
+            # Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134
+            _, _, path = fds.partition("fifo:")
+
+            if path:
+                self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
+                self.writer = os.open(path, os.O_WRONLY)
+            else:
+                self.reader, self.writer = [int(x) for x in fds.split(",", 1)]
+                # Open a private copy of reader to avoid setting nonblocking
+                # on an unexpecting process with the same reader fd.
+                self.reader = os.open("/proc/self/fd/%d" % (self.reader),
+                                      os.O_RDONLY | os.O_NONBLOCK)
+
+            # Read out as many jobserver slots as possible
+            while True:
+                try:
+                    slot = os.read(self.reader, 8)
+                    self.jobs += slot
+                except (OSError, IOError) as e:
+                    if e.errno == errno.EWOULDBLOCK:
+                        # Stop at the end of the jobserver queue.
+                        break
+                    # If something went wrong, give back the jobs.
+                    if self.jobs:
+                        os.write(self.writer, self.jobs)

scripts/jobserver-exec line 113

Quoted 10 lines
scripts/jobserver-exec +10 -0
+                    raise e
+
+            # Add a bump for our caller's reserveration, since we're just going
+            # to sit here blocked on our child.
+            self.claim = len(self.jobs) + 1
+
+        except (KeyError, IndexError, ValueError, OSError, IOError):
+            # Any missing environment strings or bad fds should result in just
+            # not being parallel.
+            self.claim = None

Sigh ... this kind of massive try/except block is one of the best ways I've found in Python to hide bugs - and Python has a few of those. I realize this is existing code, this is not the place to fix it, but I would like to clean that up at some point.

jon

#3 lore raw

Re: [PATCH v8 01/24] scripts/jobserver-exec: move the code to a class

Quoted 73 lines · Jonathan Corbet
Quoted 71 lines · Mauro Carvalho Chehab
scripts/jobserver-exec +71 -0
+class JobserverExec:
+    """
+    Claim all slots from make using POSIX Jobserver.
+
+    The main methods here are:
+    - open(): reserves all slots;
+    - close(): method returns all used slots back to make;
+    - run(): executes a command setting PARALLELISM=<available slots jobs + 1>
+    """
+
+    def __init__(self):
+        """Initialize internal vars"""
+        self.claim = 0
+        self.jobs = b""
+        self.reader = None
+        self.writer = None
+        self.is_open = False
+
+    def open(self):
+        """Reserve all available slots to be claimed later on"""
+
+        if self.is_open:
+            return
+
+        try:
+            # Fetch the make environment options.
+            flags = os.environ["MAKEFLAGS"]
+            # Look for "--jobserver=R,W"
+            # Note that GNU Make has used --jobserver-fds and --jobserver-auth
+            # so this handles all of them.
+            opts = [x for x in flags.split(" ") if x.startswith("--jobserver")]
+
+            # Parse out R,W file descriptor numbers and set them nonblocking.
+            # If the MAKEFLAGS variable contains multiple instances of the
+            # --jobserver-auth= option, the last one is relevant.
+            fds = opts[-1].split("=", 1)[1]
+
+            # Starting with GNU Make 4.4, named pipes are used for reader
+            # and writer.
+            # Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134
+            _, _, path = fds.partition("fifo:")
+
+            if path:
+                self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
+                self.writer = os.open(path, os.O_WRONLY)
+            else:
+                self.reader, self.writer = [int(x) for x in fds.split(",", 1)]
+                # Open a private copy of reader to avoid setting nonblocking
+                # on an unexpecting process with the same reader fd.
+                self.reader = os.open("/proc/self/fd/%d" % (self.reader),
+                                      os.O_RDONLY | os.O_NONBLOCK)
+
+            # Read out as many jobserver slots as possible
+            while True:
+                try:
+                    slot = os.read(self.reader, 8)
+                    self.jobs += slot
+                except (OSError, IOError) as e:
+                    if e.errno == errno.EWOULDBLOCK:
+                        # Stop at the end of the jobserver queue.
+                        break
+                    # If something went wrong, give back the jobs.
+                    if self.jobs:
+                        os.write(self.writer, self.jobs)
+                    raise e
+
+            # Add a bump for our caller's reserveration, since we're just going
+            # to sit here blocked on our child.
+            self.claim = len(self.jobs) + 1
+
+        except (KeyError, IndexError, ValueError, OSError, IOError):
Quoted 8 lines

scripts/jobserver-exec line 113

Quoted 3 lines
scripts/jobserver-exec +3 -0
+            # Any missing environment strings or bad fds should result in just
+            # not being parallel.
+            self.claim = None

Sigh ... this kind of massive try/except block is one of the best ways I've found in Python to hide bugs - and Python has a few of those. I realize this is existing code, this is not the place to fix it, but I would like to clean that up at some point.

Agreed: this class deserves some cleanup.

While working here, I considered doing more changes, but I refrained myself. As you pointed, this is not the right series to do large changes. Also, this is used not only for documentation build but also inside scripts/Makefile.vmlinux_o. A major change there could affect vmlinux generation.

Btw, besides Python cleanups, IMHO jobserver class could benefit on having a maximum value for claim (which could even be 1). I was also tempted to add a jobserver-aware subprocess.call here.

That's said, doing a risk analysis, if it fails to properly read jobserver pipes, the except logic will set claim to None, which:

- for documentation: will use "-jauto";
- for vmlinux generation: will probably use a single job when building vmlinux. If target dependencies are correct, this should not cause build failures.

On my tests, I didn't get any such exception for doc build. I wrote a small testbench to check if jobserver was handling claim the right way. Before/after the changes, the behavior remained the same, and caim was always initialized when running via make.

Signature · 2 lines
Thanks,
Mauro
#4 lore raw

PATCH v8 2/24 scripts/jobserver-exec: move its class to the lib directory

In reply to Mauro Carvalho Chehab

To make it easier to be re-used, move the JobserverExec class to the library directory.

---
 scripts/jobserver-exec   | 152 +++------------------------------------
 scripts/lib/jobserver.py | 149 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 160 insertions(+), 141 deletions(-)
 create mode 100755 scripts/lib/jobserver.py
scripts/jobserver-exec +11 -141
index 897c0cca9e6e..40a0f0058733 100755
@@ -1,155 +1,25 @@
 #!/usr/bin/env python3
 # SPDX-License-Identifier: GPL-2.0+
-#
-# pylint: disable=C0103,C0209
-#
-# This determines how many parallel tasks "make" is expecting, as it is
-# not exposed via an special variables, reserves them all, runs a subprocess
-# with PARALLELISM environment variable set, and releases the jobs back again.
-#
-# https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver
 
-"""
-Interacts with the POSIX jobserver during the Kernel build time.
-
-A "normal" jobserver task, like the one initiated by a make subrocess would do:
-
-    - open read/write file descriptors to communicate with the job server;
-    - ask for one slot by calling:
-        claim = os.read(reader, 1)
-    - when the job finshes, call:
-        os.write(writer, b"+")  # os.write(writer, claim)
-
-Here, the goal is different: This script aims to get the remaining number
-of slots available, using all of them to run a command which handle tasks in
-parallel. To to that, it has a loop that ends only after there are no
-slots left. It then increments the number by one, in order to allow a
-call equivalent to make -j$((claim+1)), e.g. having a parent make creating
-$claim child to do the actual work.
-
-The end goal here is to keep the total number of build tasks under the
-limit established by the initial make -j$n_proc call.
-"""
-
-import errno
 import os
-import subprocess
 import sys
 
+LIB_DIR = "lib"
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
 
-class JobserverExec:
-    """
-    Claim all slots from make using POSIX Jobserver.
+sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
 
-    The main methods here are:
-    - open(): reserves all slots;
-    - close(): method returns all used slots back to make;
-    - run(): executes a command setting PARALLELISM=<available slots jobs + 1>
-    """
+from jobserver import JobserverExec                  # pylint: disable=C0415
 
-    def __init__(self):
-        """Initialize internal vars"""
-        self.claim = 0
-        self.jobs = b""
-        self.reader = None
-        self.writer = None
-        self.is_open = False
 
-    def open(self):
-        """Reserve all available slots to be claimed later on"""
-
-        if self.is_open:
-            return
-
-        try:
-            # Fetch the make environment options.
-            flags = os.environ["MAKEFLAGS"]
-            # Look for "--jobserver=R,W"
-            # Note that GNU Make has used --jobserver-fds and --jobserver-auth
-            # so this handles all of them.
-            opts = [x for x in flags.split(" ") if x.startswith("--jobserver")]
-
-            # Parse out R,W file descriptor numbers and set them nonblocking.
-            # If the MAKEFLAGS variable contains multiple instances of the
-            # --jobserver-auth= option, the last one is relevant.
-            fds = opts[-1].split("=", 1)[1]
-
-            # Starting with GNU Make 4.4, named pipes are used for reader
-            # and writer.
-            # Example argument: --jobserver-auth=fifo:/tmp/GMfifo8134
-            _, _, path = fds.partition("fifo:")
-
-            if path:
-                self.reader = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
-                self.writer = os.open(path, os.O_WRONLY)
-            else:
-                self.reader, self.writer = [int(x) for x in fds.split(",", 1)]
-                # Open a private copy of reader to avoid setting nonblocking
-                # on an unexpecting process with the same reader fd.
-                self.reader = os.open("/proc/self/fd/%d" % (self.reader),
-                                      os.O_RDONLY | os.O_NONBLOCK)
-
-            # Read out as many jobserver slots as possible
-            while True:
-                try:
-                    slot = os.read(self.reader, 8)
-                    self.jobs += slot
-                except (OSError, IOError) as e:
-                    if e.errno == errno.EWOULDBLOCK:
-                        # Stop at the end of the jobserver queue.
-                        break
-                    # If something went wrong, give back the jobs.
-                    if self.jobs:
-                        os.write(self.writer, self.jobs)
-                    raise e
-
-            # Add a bump for our caller's reserveration, since we're just going
-            # to sit here blocked on our child.
-            self.claim = len(self.jobs) + 1
-
-        except (KeyError, IndexError, ValueError, OSError, IOError):
-            # Any missing environment strings or bad fds should result in just
-            # not being parallel.
-            self.claim = None
-
-        self.is_open = True
-
-    def close(self):
-        """Return all reserved slots to Jobserver"""
-
-        if not self.is_open:
-            return
-
-        # Return all the reserved slots.
-        if len(self.jobs):
-            os.write(self.writer, self.jobs)
-
-        self.is_open = False
-
-    def __enter__(self):
-        self.open()
-        return self
-
-    def __exit__(self, exc_type, exc_value, exc_traceback):
-        self.close()
-
-    def run(self, cmd):
-        """
-        Run a command setting PARALLELISM env variable to the number of
-        available job slots (claim) + 1, e.g. it will reserve claim slots
-        to do the actual build work, plus one to monitor its children.
-        """
-        self.open()             # Ensure that self.claim is set
-
-        # We can only claim parallelism if there was a jobserver (i.e. a
-        # top-level "-jN" argument) and there were no other failures. Otherwise
-        # leave out the environment variable and let the child figure out what
-        # is best.
-        if self.claim:
-            os.environ["PARALLELISM"] = str(self.claim)
-
-        return subprocess.call(cmd)
+"""
+Determines how many parallel tasks "make" is expecting, as it is
+not exposed via an special variables, reserves them all, runs a subprocess
+with PARALLELISM environment variable set, and releases the jobs back again.
 
+See:
+    https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver
+"""
 
 def main():
     """Main program"""
scripts/lib/jobserver.py new file +149 -0

149 lines not on this page · raw message

Signature · 1 line
2.51.0
#5 lore raw

PATCH v8 3/24 scripts/jobserver-exec: add a help message

In reply to Mauro Carvalho Chehab

Currently, calling it without an argument shows an ugly error message. Instead, print a message using pythondoc as description.

---
 scripts/jobserver-exec | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)
scripts/jobserver-exec +13 -9
index 40a0f0058733..ae23afd344ec 100755
@@ -1,6 +1,15 @@
 #!/usr/bin/env python3
 # SPDX-License-Identifier: GPL-2.0+
 
+"""
+Determines how many parallel tasks "make" is expecting, as it is
+not exposed via any special variables, reserves them all, runs a subprocess
+with PARALLELISM environment variable set, and releases the jobs back again.
+
+See:
+    https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver
+"""
+
 import os
 import sys
 
@@ -12,17 +21,12 @@ sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
 from jobserver import JobserverExec                  # pylint: disable=C0415
 
 
-"""
-Determines how many parallel tasks "make" is expecting, as it is
-not exposed via an special variables, reserves them all, runs a subprocess
-with PARALLELISM environment variable set, and releases the jobs back again.
-
-See:
-    https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver
-"""
-
 def main():
     """Main program"""
+    if len(sys.argv) < 2:
+        name = os.path.basename(__file__)
+        sys.exit("usage: " + name +" command [args ...]\n" + __doc__)
+
     with JobserverExec() as jobserver:
         jobserver.run(sys.argv[1:])
 
Signature · 1 line
2.51.0
#6 lore raw

PATCH v8 4/24 scripts: check-variable-fonts.sh: convert to Python

In reply to Mauro Carvalho Chehab

This script handle errors when trying to build translations with make pdfdocs.

As part of our cleanup work to remove hacks from docs Makefile, convert this to python, preparing it to be part of a library to be called by sphinx-build-wrapper.

---
 Documentation/Makefile                        |   2 +-
 MAINTAINERS                                   |   2 +-
 ...iable-fonts.sh => check-variable-fonts.py} | 104 +++++++++++++-----
 3 files changed, 79 insertions(+), 29 deletions(-)
 rename scripts/{check-variable-fonts.sh => check-variable-fonts.py} (61%)
Documentation/Makefile +1 -1
index 5c20c68be89a..d5e436435eab 100644
@@ -146,7 +146,7 @@ pdfdocs: DENY_VF = XDG_CONFIG_HOME=$(FONTS_CONF_DENY_VF)
 pdfdocs: latexdocs
 	@$(srctree)/scripts/sphinx-pre-install --version-check
 	$(foreach var,$(SPHINXDIRS), \
-	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || sh $(srctree)/scripts/check-variable-fonts.sh || exit; \
+	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || $(PYTHON3) $(srctree)/scripts/check-variable-fonts.py || exit; \
 	   mkdir -p $(BUILDDIR)/$(var)/pdf; \
 	   mv $(subst .tex,.pdf,$(wildcard $(BUILDDIR)/$(var)/latex/*.tex)) $(BUILDDIR)/$(var)/pdf/; \
 	)
MAINTAINERS +1 -1
index ef87548b8f88..88d8f7435e6d 100644
@@ -7301,7 +7301,7 @@ S:	Maintained
 P:	Documentation/doc-guide/maintainer-profile.rst
 T:	git git://git.lwn.net/linux.git docs-next
 F:	Documentation/
-F:	scripts/check-variable-fonts.sh
+F:	scripts/check-variable-fonts.py
 F:	scripts/checktransupdate.py
 F:	scripts/documentation-file-ref-check
 F:	scripts/get_abi.py
scripts/check-variable-fonts.py renamed from scripts/check-variable-fonts.sh +77 -27

113 lines not on this page · raw message

Signature · 1 line
2.51.0
#7 lore raw

PATCH v8 5/24 tools/docs: check-variable-fonts.py: split into a lib and an exec file

In reply to Mauro Carvalho Chehab

As we'll be using the actual code inside sphinx-build-wrapper, split the library from the executable, placing the exec at the new place we've been using:

tools/docs

No functional changes.

---
 Documentation/Makefile             |   2 +-
 MAINTAINERS                        |   1 -
 scripts/check-variable-fonts.py    | 165 -----------------------------
 tools/docs/check-variable-fonts.py |  23 ++++
 tools/docs/lib/latex_fonts.py      | 162 ++++++++++++++++++++++++++++
 5 files changed, 186 insertions(+), 167 deletions(-)
 delete mode 100755 scripts/check-variable-fonts.py
 create mode 100755 tools/docs/check-variable-fonts.py
 create mode 100755 tools/docs/lib/latex_fonts.py
Documentation/Makefile +1 -1
index d5e436435eab..6e11d0593742 100644
@@ -146,7 +146,7 @@ pdfdocs: DENY_VF = XDG_CONFIG_HOME=$(FONTS_CONF_DENY_VF)
 pdfdocs: latexdocs
 	@$(srctree)/scripts/sphinx-pre-install --version-check
 	$(foreach var,$(SPHINXDIRS), \
-	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || $(PYTHON3) $(srctree)/scripts/check-variable-fonts.py || exit; \
+	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || $(PYTHON3) $(srctree)/tools/docs/check-variable-fonts.py || exit; \
 	   mkdir -p $(BUILDDIR)/$(var)/pdf; \
 	   mv $(subst .tex,.pdf,$(wildcard $(BUILDDIR)/$(var)/latex/*.tex)) $(BUILDDIR)/$(var)/pdf/; \
 	)
MAINTAINERS +0 -1
index 88d8f7435e6d..76dd823bfcc4 100644
@@ -7301,7 +7301,6 @@ S:	Maintained
 P:	Documentation/doc-guide/maintainer-profile.rst
 T:	git git://git.lwn.net/linux.git docs-next
 F:	Documentation/
-F:	scripts/check-variable-fonts.py
 F:	scripts/checktransupdate.py
 F:	scripts/documentation-file-ref-check
 F:	scripts/get_abi.py
scripts/check-variable-fonts.py deleted +0 -165

165 lines not on this page · raw message

tools/docs/check-variable-fonts.py new file +23 -0
new file mode 100755
index 000000000000..79b28f0f7d85
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright (C) Akira Yokosawa, 2024
+#
+# Ported to Python by (c) Mauro Carvalho Chehab, 2025
+#
+# pylint: disable=C0103
+
+"""
+Detect problematic Noto CJK variable fonts.
+
+or more details, see lib/latex_fonts.py.
+"""
+
+import sys
+
+from lib.latex_fonts import LatexFontChecker
+
+msg = LatexFontChecker().check()
+if msg:
+    print(msg)
+
+sys.exit(1)
tools/docs/lib/latex_fonts.py new file +162 -0

162 lines not on this page · raw message

Signature · 1 line
2.51.0
#8 lore raw

PATCH v8 6/24 check-variable-fonts.py: add a helper to display instructions

In reply to Mauro Carvalho Chehab

Use lib docstring to output the comments via --help/-h. With that, update the default instructions to recomment it instead of asking the user to read the source code.

---
 tools/docs/check-variable-fonts.py | 12 +++++++++++-
 tools/docs/lib/latex_fonts.py      | 13 +++++++++----
 2 files changed, 20 insertions(+), 5 deletions(-)
tools/docs/check-variable-fonts.py +11 -1
index 79b28f0f7d85..c0997d6861dc 100755
@@ -12,11 +12,21 @@ Detect problematic Noto CJK variable fonts.
 or more details, see lib/latex_fonts.py.
 """
 
+import argparse
 import sys
 
 from lib.latex_fonts import LatexFontChecker
 
-msg = LatexFontChecker().check()
+checker = LatexFontChecker()
+
+parser=argparse.ArgumentParser(description=checker.description(),
+                               formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument("--deny-vf",
+                    help="XDG_CONFIG_HOME dir containing fontconfig/fonts.conf file")
+
+args=parser.parse_args()
+
+msg = LatexFontChecker(args.deny_vf).check()
 if msg:
     print(msg)
 
tools/docs/lib/latex_fonts.py +9 -4
index 81358a70f320..29317f8006ea 100755
@@ -105,14 +105,18 @@ class LatexFontChecker:
     translations.
     """
 
-    def __init__(self):
-        deny_vf = os.environ.get('FONTS_CONF_DENY_VF', "~/deny-vf")
+    def __init__(self, deny_vf=None):
+        if not deny_vf:
+            deny_vf = os.environ.get('FONTS_CONF_DENY_VF', "~/deny-vf")
 
         self.environ = os.environ.copy()
         self.environ['XDG_CONFIG_HOME'] = os.path.expanduser(deny_vf)
 
         self.re_cjk = re.compile(r"([^:]+):\s*Noto\s+(Sans|Sans Mono|Serif) CJK")
 
+    def description(self):
+        return __doc__
+
     def get_noto_cjk_vf_fonts(self):
         """Get Noto CJK fonts"""
 
@@ -154,8 +158,9 @@ class LatexFontChecker:
                 For CJK pages in PDF, they need to be hidden from XeTeX by denylisting.
                 Or, CJK pages can be skipped by uninstalling texlive-xecjk.
 
-                For more info on denylisting, other options, and variable font, see header
-                comments of {rel_file}.
+                For more info on denylisting, other options, and variable font, run:
+
+                    tools/docs/check-variable-fonts.py -h
             """)
         msg += "=" * 77
 
Signature · 1 line
2.51.0
#9 lore raw

PATCH v8 7/24 scripts: sphinx-pre-install: move it to tools/docs

In reply to Mauro Carvalho Chehab

As we're reorganizing the place where doc scripts are located, move this one to tools/docs.

No functional changes.

---
 Documentation/Makefile                             | 14 +++++++-------
 Documentation/doc-guide/sphinx.rst                 |  4 ++--
 Documentation/sphinx/kerneldoc-preamble.sty        |  2 +-
 .../translations/it_IT/doc-guide/sphinx.rst        |  4 ++--
 .../translations/zh_CN/doc-guide/sphinx.rst        |  4 ++--
 Documentation/translations/zh_CN/how-to.rst        |  2 +-
 MAINTAINERS                                        |  3 +--
 {scripts => tools/docs}/sphinx-pre-install         |  0
 8 files changed, 16 insertions(+), 17 deletions(-)
 rename {scripts => tools/docs}/sphinx-pre-install (100%)
Documentation/Makefile +7 -7
index 6e11d0593742..fd6399c79fab 100644
@@ -46,7 +46,7 @@ ifeq ($(HAVE_SPHINX),0)
 .DEFAULT:
 	$(warning The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed and in PATH, or set the SPHINXBUILD make variable to point to the full path of the '$(SPHINXBUILD)' executable.)
 	@echo
-	@$(srctree)/scripts/sphinx-pre-install
+	@$(srctree)/tools/docs/sphinx-pre-install
 	@echo "  SKIP    Sphinx $@ target."
 
 else # HAVE_SPHINX
@@ -105,7 +105,7 @@ quiet_cmd_sphinx = SPHINX  $@ --> file://$(abspath $(BUILDDIR)/$3/$4)
 	fi
 
 htmldocs:
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,html,$(var),,$(var)))
 
 # If Rust support is available and .config exists, add rustdoc generated contents.
@@ -119,7 +119,7 @@ endif
 endif
 
 texinfodocs:
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,texinfo,$(var),texinfo,$(var)))
 
 # Note: the 'info' Make target is generated by sphinx itself when
@@ -131,7 +131,7 @@ linkcheckdocs:
 	@$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,linkcheck,$(var),,$(var)))
 
 latexdocs:
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,latex,$(var),latex,$(var)))
 
 ifeq ($(HAVE_PDFLATEX),0)
@@ -144,7 +144,7 @@ else # HAVE_PDFLATEX
 
 pdfdocs: DENY_VF = XDG_CONFIG_HOME=$(FONTS_CONF_DENY_VF)
 pdfdocs: latexdocs
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	$(foreach var,$(SPHINXDIRS), \
 	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || $(PYTHON3) $(srctree)/tools/docs/check-variable-fonts.py || exit; \
 	   mkdir -p $(BUILDDIR)/$(var)/pdf; \
@@ -154,11 +154,11 @@ pdfdocs: latexdocs
 endif # HAVE_PDFLATEX
 
 epubdocs:
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,epub,$(var),epub,$(var)))
 
 xmldocs:
-	@$(srctree)/scripts/sphinx-pre-install --version-check
+	@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,xml,$(var),xml,$(var)))
 
 endif # HAVE_SPHINX
Documentation/doc-guide/sphinx.rst +2 -2
index 607589592bfb..932f68c53075 100644
@@ -106,7 +106,7 @@ There's a script that automatically checks for Sphinx dependencies. If it can
 recognize your distribution, it will also give a hint about the install
 command line options for your distro::
 
-	$ ./scripts/sphinx-pre-install
+	$ ./tools/docs/sphinx-pre-install
 	Checking if the needed tools for Fedora release 26 (Twenty Six) are available
 	Warning: better to also install "texlive-luatex85".
 	You should run:
@@ -116,7 +116,7 @@ command line options for your distro::
 		. sphinx_2.4.4/bin/activate
 		pip install -r Documentation/sphinx/requirements.txt
 
-	Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468.
+	Can't build as 1 mandatory dependency is missing at ./tools/docs/sphinx-pre-install line 468.
 
 By default, it checks all the requirements for both html and PDF, including
 the requirements for images, math expressions and LaTeX build, and assumes
Documentation/sphinx/kerneldoc-preamble.sty +1 -1
index 5d68395539fe..16d9ff46fdf6 100644
@@ -220,7 +220,7 @@
 	    If you want them, please install non-variable ``Noto Sans CJK''
 	    font families along with the texlive-xecjk package by following
 	    instructions from
-	    \sphinxcode{./scripts/sphinx-pre-install}.
+	    \sphinxcode{./tools/docs/sphinx-pre-install}.
 	    Having optional non-variable ``Noto Serif CJK'' font families will
 	    improve the looks of those translations.
 	\end{sphinxadmonition}}
Documentation/translations/it_IT/doc-guide/sphinx.rst +2 -2
index 1f513bc33618..a5c5d935febf 100644
@@ -109,7 +109,7 @@ Sphinx. Se lo script riesce a riconoscere la vostra distribuzione, allora
 sarà in grado di darvi dei suggerimenti su come procedere per completare
 l'installazione::
 
-	$ ./scripts/sphinx-pre-install
+	$ ./tools/docs/sphinx-pre-install
 	Checking if the needed tools for Fedora release 26 (Twenty Six) are available
 	Warning: better to also install "texlive-luatex85".
 	You should run:
@@ -119,7 +119,7 @@ l'installazione::
 		. sphinx_2.4.4/bin/activate
 		pip install -r Documentation/sphinx/requirements.txt
 
-	Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468.
+	Can't build as 1 mandatory dependency is missing at ./tools/docs/sphinx-pre-install line 468.
 
 L'impostazione predefinita prevede il controllo dei requisiti per la generazione
 di documenti html e PDF, includendo anche il supporto per le immagini, le
Documentation/translations/zh_CN/doc-guide/sphinx.rst +2 -2
index 23eac67fbc30..3375c6f3a811 100644
@@ -84,7 +84,7 @@ PDF和LaTeX构建
 这有一个脚本可以自动检查Sphinx依赖项。如果它认得您的发行版,还会提示您所用发行
 版的安装命令::
 
-	$ ./scripts/sphinx-pre-install
+	$ ./tools/docs/sphinx-pre-install
 	Checking if the needed tools for Fedora release 26 (Twenty Six) are available
 	Warning: better to also install "texlive-luatex85".
 	You should run:
@@ -94,7 +94,7 @@ PDF和LaTeX构建
 		. sphinx_2.4.4/bin/activate
 		pip install -r Documentation/sphinx/requirements.txt
 
-	Can't build as 1 mandatory dependency is missing at ./scripts/sphinx-pre-install line 468.
+	Can't build as 1 mandatory dependency is missing at ./tools/docs/sphinx-pre-install line 468.
 
 默认情况下,它会检查html和PDF的所有依赖项,包括图像、数学表达式和LaTeX构建的
 需求,并假设将使用虚拟Python环境。html构建所需的依赖项被认为是必需的,其他依
Documentation/translations/zh_CN/how-to.rst +1 -1
index ddd99c0f9b4d..714664fec308 100644
@@ -64,7 +64,7 @@ Linux 发行版和简单地使用 Linux 命令行,那么可以迅速开始了
 ::
 
 	cd linux
-	./scripts/sphinx-pre-install
+	./tools/docs/sphinx-pre-install
 
 以 Fedora 为例,它的输出是这样的::
 
MAINTAINERS +1 -2
index 76dd823bfcc4..16a5d6ab627d 100644
@@ -7309,7 +7309,6 @@ F:	scripts/lib/abi/*
 F:	scripts/lib/kdoc/*
 F:	tools/docs/*
 F:	tools/net/ynl/pyynl/lib/doc_generator.py
-F:	scripts/sphinx-pre-install
 X:	Documentation/ABI/
 X:	Documentation/admin-guide/media/
 X:	Documentation/devicetree/
@@ -7344,7 +7343,7 @@ L:	[email protected]
 S:	Maintained
 F:	Documentation/sphinx/parse-headers.pl
 F:	scripts/documentation-file-ref-check
-F:	scripts/sphinx-pre-install
+F:	tools/docs/sphinx-pre-install
 
 DOCUMENTATION/ITALIAN
 M:	Federico Vaga <[email protected]>
tools/docs/sphinx-pre-install renamed from scripts/sphinx-pre-install +0 -0
similarity index 100%
rename from scripts/sphinx-pre-install
rename to tools/docs/sphinx-pre-install
Signature · 1 line
2.51.0
#10 lore raw

PATCH v8 8/24 tools/docs: sphinx-pre-install: drop a debug print

In reply to Mauro Carvalho Chehab

The version print at the lib was added for debugging purposes. Get rid of it.

---
 tools/docs/sphinx-pre-install | 1 -
 1 file changed, 1 deletion(-)
tools/docs/sphinx-pre-install +0 -1
index 954ed3dc0645..fa12e01fc7fe 100755
@@ -285,7 +285,6 @@ class AncillaryMethods:
         cur_ver = sys.version_info[:3]
         if cur_ver >= MIN_PYTHON_VERSION:
             ver = ver_str(cur_ver)
-            print(f"Python version: {ver}")
 
             # This could be useful for debugging purposes
             if SphinxDependencyChecker.which("docutils"):
Signature · 1 line
2.51.0
#11 lore raw

PATCH v8 9/24 tools/docs: sphinx-pre-install: allow check for alternatives and bail out

In reply to Mauro Carvalho Chehab

The caller script may not want an automatic execution of the new version. Add two parameters to allow showing alternatives and to bail out if version is incompatible.

---
 tools/docs/sphinx-pre-install | 48 +++++++++++++++++++++++++++--------
 1 file changed, 38 insertions(+), 10 deletions(-)
tools/docs/sphinx-pre-install +38 -10
index fa12e01fc7fe..895220c6d4f8 100755
@@ -254,7 +254,7 @@ class AncillaryMethods:
         return (0, 0, 0)
 
     @staticmethod
-    def find_python():
+    def find_python(min_version):
         """
         Detect if are out there any python 3.xy version newer than the
         current one.
@@ -263,21 +263,25 @@ class AncillaryMethods:
         may need to update it one day, hopefully on a distant future.
         """
         patterns = [
-            "python3.[0-9]",
             "python3.[0-9][0-9]",
+            "python3.[0-9]",
         ]
 
         # Seek for a python binary newer than MIN_PYTHON_VERSION
+        python_cmd = []
         for path in os.getenv("PATH", "").split(":"):
             for pattern in patterns:
                 for cmd in glob(os.path.join(path, pattern)):
                     if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
                         version = SphinxDependencyChecker.get_python_version(cmd)
-                        if version >= MIN_PYTHON_VERSION:
-                            return cmd
+                        if version >= min_version:
+                            python_cmd.append((version, cmd))
+
+        return sorted(python_cmd, reverse=True)
 
     @staticmethod
-    def check_python():
+    def check_python(min_version, show_alternatives=False, bail_out=False,
+                     success_on_error=False):
         """
         Check if the current python binary satisfies our minimal requirement
         for Sphinx build. If not, re-run with a newer version if found.
@@ -301,18 +305,42 @@ class AncillaryMethods:
 
         python_ver = ver_str(cur_ver)
 
-        new_python_cmd = SphinxDependencyChecker.find_python()
-        if not new_python_cmd:
+        available_versions = SphinxDependencyChecker.find_python(min_version)
+        if not available_versions:
             print(f"ERROR: Python version {python_ver} is not spported anymore\n")
             print("       Can't find a new version. This script may fail")
             return
 
-        # Restart script using the newer version
         script_path = os.path.abspath(sys.argv[0])
-        args = [new_python_cmd, script_path] + sys.argv[1:]
+
+        # Check possible alternatives
+        if available_versions:
+            new_python_cmd = available_versions[0][1]
+        else:
+            new_python_cmd = None
+
+        if show_alternatives:
+            print("You could run, instead:")
+            for _, cmd in available_versions:
+                args = [cmd, script_path] + sys.argv[1:]
+
+                cmd_str = " ".join(args)
+                print(f"  {cmd_str}")
+            print()
+
+        if bail_out:
+            msg = f"Python {python_ver} not supported. Bailing out"
+            if success_on_error:
+                print(msg, file=sys.stderr)
+                sys.exit(0)
+            else:
+                sys.exit(msg)
 
         print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
 
+        # Restart script using the newer version
+        args = [new_python_cmd, script_path] + sys.argv[1:]
+
         try:
             os.execv(new_python_cmd, args)
         except OSError as e:
@@ -1612,7 +1640,7 @@ def main():
 
     checker = SphinxDependencyChecker(args)
 
-    checker.check_python()
+    checker.check_python(MIN_PYTHON_VERSION)
     checker.check_needs()
 
 # Call main if not used as module
Signature · 1 line
2.51.0
#12 lore raw

PATCH v8 10/24 tools/docs: python_version: move version check from sphinx-pre-install

In reply to Mauro Carvalho Chehab

The sphinx-pre-install code has some logic to deal with Python version, which ensures that a minimal version will be enforced for documentation build logic.

Move it to a separate library to allow re-using its code.

No functional changes.

---
 tools/docs/lib/python_version.py | 158 +++++++++++++++++++++++++++++++
 tools/docs/sphinx-pre-install    | 147 +++-------------------------
 2 files changed, 171 insertions(+), 134 deletions(-)
 create mode 100644 tools/docs/lib/python_version.py
tools/docs/lib/python_version.py new file +158 -0
new file mode 100644
index 000000000000..a9fda2470a26
@@ -0,0 +1,158 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2017-2025 Mauro Carvalho Chehab <[email protected]>
+
+"""
+Handle Python version check logic.
+
+Not all Python versions are supported by scripts. Yet, on some cases,
+like during documentation build, a newer version of python could be
+available.
+
+This class allows checking if the minimal requirements are followed.
+
+Better than that, PythonVersion.check_python() not only checks the minimal
+requirements, but it automatically switches to a the newest available
+Python version if present.
+
+"""
+
+import os
+import re
+import subprocess
+import sys
+
+from glob import glob
+
+class PythonVersion:
+    """
+    Ancillary methods that checks for missing dependencies for different
+    types of types, like binaries, python modules, rpm deps, etc.
+    """
+
+    def __init__(self, version):
+        """Ïnitialize self.version tuple from a version string"""
+        self.version = self.parse_version(version)
+
+    @staticmethod
+    def parse_version(version):
+        """Convert a major.minor.patch version into a tuple"""
+        return tuple(int(x) for x in version.split("."))
+
+    @staticmethod
+    def ver_str(version):
+        """Returns a version tuple as major.minor.patch"""
+        return ".".join([str(x) for x in version])
+
+    def __str__(self):
+        """Returns a version tuple as major.minor.patch from self.version"""
+        return self.ver_str(self.version)
+
+    @staticmethod
+    def get_python_version(cmd):
+        """
+        Get python version from a Python binary. As we need to detect if
+        are out there newer python binaries, we can't rely on sys.release here.
+        """
+
+        kwargs = {}
+        if sys.version_info < (3, 7):
+            kwargs['universal_newlines'] = True
+        else:
+            kwargs['text'] = True
+
+        result = subprocess.run([cmd, "--version"],
+                                stdout = subprocess.PIPE,
+                                stderr = subprocess.PIPE,
+                                **kwargs, check=False)
+
+        version = result.stdout.strip()
+
+        match = re.search(r"(\d+\.\d+\.\d+)", version)
+        if match:
+            return PythonVersion.parse_version(match.group(1))
+
+        print(f"Can't parse version {version}")
+        return (0, 0, 0)
+
+    @staticmethod
+    def find_python(min_version):
+        """
+        Detect if are out there any python 3.xy version newer than the
+        current one.
+
+        Note: this routine is limited to up to 2 digits for python3. We
+        may need to update it one day, hopefully on a distant future.
+        """
+        patterns = [
+            "python3.[0-9][0-9]",
+            "python3.[0-9]",
+        ]
+
+        python_cmd = []
+
+        # Seek for a python binary newer than min_version
+        for path in os.getenv("PATH", "").split(":"):
+            for pattern in patterns:
+                for cmd in glob(os.path.join(path, pattern)):
+                    if os.path.isfile(cmd) and os.access(cmd, os.X_OK):
+                        version = PythonVersion.get_python_version(cmd)
+                        if version >= min_version:
+                            python_cmd.append((version, cmd))
+
+        return sorted(python_cmd, reverse=True)
+
+    @staticmethod
+    def check_python(min_version, show_alternatives=False, bail_out=False,
+                     success_on_error=False):
+        """
+        Check if the current python binary satisfies our minimal requirement
+        for Sphinx build. If not, re-run with a newer version if found.
+        """
+        cur_ver = sys.version_info[:3]
+        if cur_ver >= min_version:
+            ver = PythonVersion.ver_str(cur_ver)
+            return
+
+        python_ver = PythonVersion.ver_str(cur_ver)
+
+        available_versions = PythonVersion.find_python(min_version)
+        if not available_versions:
+            print(f"ERROR: Python version {python_ver} is not spported anymore\n")
+            print("       Can't find a new version. This script may fail")
+            return
+
+        script_path = os.path.abspath(sys.argv[0])
+
+        # Check possible alternatives
+        if available_versions:
+            new_python_cmd = available_versions[0][1]
+        else:
+            new_python_cmd = None
+
+        if show_alternatives:
+            print("You could run, instead:")
+            for _, cmd in available_versions:
+                args = [cmd, script_path] + sys.argv[1:]
+
+                cmd_str = " ".join(args)
+                print(f"  {cmd_str}")
+            print()
+
+        if bail_out:
+            msg = f"Python {python_ver} not supported. Bailing out"
+            if success_on_error:
+                print(msg, file=sys.stderr)
+                sys.exit(0)
+            else:
+                sys.exit(msg)
+
+        print(f"Python {python_ver} not supported. Changing to {new_python_cmd}")
+
+        # Restart script using the newer version
+        args = [new_python_cmd, script_path] + sys.argv[1:]
+
+        try:
+            os.execv(new_python_cmd, args)
+        except OSError as e:
+            sys.exit(f"Failed to restart with {new_python_cmd}: {e}")
tools/docs/sphinx-pre-install +13 -134

209 lines not on this page · raw message

Signature · 1 line
2.51.0
#13 lore raw

PATCH v8 11/24 tools/docs: sphinx-build-wrapper: add a wrapper for sphinx-build

In reply to Mauro Carvalho Chehab

There are too much magic inside docs Makefile to properly run sphinx-build. Create an ancillary script that contains all kernel-related sphinx-build call logic currently at Makefile.

Such script is designed to work both as an standalone command and as part of a Makefile. As such, it properly handles POSIX jobserver used by GNU make.

On a side note, there was a line number increase due to the conversion (ignoring comments) is:

 Documentation/Makefile          |  131 +++----------
 tools/docs/sphinx-build-wrapper |  293 +++++++++++++++++++++++++++++++
 2 files changed, 323 insertions(+), 101 deletions(-)

Comments and descriptions adds:

 tools/docs/sphinx-build-wrapper | 261 +++++++++++++++++++++++++++++++-

So, about half of the script are comments/descriptions.

This is because some things are more verbosed on Python and because it requires reading env vars from Makefile. Besides it, this script has some extra features that don't exist at the Makefile:

- It can be called directly from command line;
- It properly return PDF build errors.

When running the script alone, it will only take handle sphinx-build targets. On other words, it won't runn make rustdoc after building htmlfiles, nor it will run the extra check scripts.

---
 Documentation/Makefile          | 129 ++-----
 tools/docs/sphinx-build-wrapper | 599 ++++++++++++++++++++++++++++++++
 2 files changed, 627 insertions(+), 101 deletions(-)
 create mode 100755 tools/docs/sphinx-build-wrapper
Documentation/Makefile +28 -101
index fd6399c79fab..380284026c13 100644
@@ -23,21 +23,22 @@ SPHINXOPTS    =
 SPHINXDIRS    = .
 DOCS_THEME    =
 DOCS_CSS      =
-_SPHINXDIRS   = $(sort $(patsubst $(srctree)/Documentation/%/index.rst,%,$(wildcard $(srctree)/Documentation/*/index.rst)))
 SPHINX_CONF   = conf.py
 PAPER         =
 BUILDDIR      = $(obj)/output
 PDFLATEX      = xelatex
 LATEXOPTS     = -interaction=batchmode -no-shell-escape
 
+PYTHONPYCACHEPREFIX ?= $(abspath $(BUILDDIR)/__pycache__)
+
+# Wrapper for sphinx-build
+
+BUILD_WRAPPER = $(srctree)/tools/docs/sphinx-build-wrapper
+
 # For denylisting "variable font" files
 # Can be overridden by setting as an env variable
 FONTS_CONF_DENY_VF ?= $(HOME)/deny-vf
 
-ifeq ($(findstring 1, $(KBUILD_VERBOSE)),)
-SPHINXOPTS    += "-q"
-endif
-
 # User-friendly check for sphinx-build
 HAVE_SPHINX := $(shell if which $(SPHINXBUILD) >/dev/null 2>&1; then echo 1; else echo 0; fi)
 
@@ -51,63 +52,29 @@ ifeq ($(HAVE_SPHINX),0)
 
 else # HAVE_SPHINX
 
-# User-friendly check for pdflatex and latexmk
-HAVE_PDFLATEX := $(shell if which $(PDFLATEX) >/dev/null 2>&1; then echo 1; else echo 0; fi)
-HAVE_LATEXMK := $(shell if which latexmk >/dev/null 2>&1; then echo 1; else echo 0; fi)
+# Common documentation targets
+infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
+	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
+	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
+		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
+		--builddir="$(BUILDDIR)" --deny-vf=$(FONTS_CONF_DENY_VF) \
+		--theme=$(DOCS_THEME) --css=$(DOCS_CSS) --paper=$(PAPER)
 
-ifeq ($(HAVE_LATEXMK),1)
-	PDFLATEX := latexmk -$(PDFLATEX)
-endif #HAVE_LATEXMK
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_elements.papersize=a4paper
-PAPEROPT_letter = -D latex_elements.papersize=letterpaper
-ALLSPHINXOPTS   = -D kerneldoc_srctree=$(srctree) -D kerneldoc_bin=$(KERNELDOC)
-ALLSPHINXOPTS   += $(PAPEROPT_$(PAPER)) $(SPHINXOPTS)
-ifneq ($(wildcard $(srctree)/.config),)
-ifeq ($(CONFIG_RUST),y)
-	# Let Sphinx know we will include rustdoc
-	ALLSPHINXOPTS   +=  -t rustdoc
-endif
+# Special handling for pdfdocs
+ifneq ($(shell which $(PDFLATEX) >/dev/null 2>&1; echo $$?),0)
+pdfdocs:
+	$(warning The '$(PDFLATEX)' command was not found. Make sure you have it installed and in PATH to produce PDF output.)
+	@echo "  SKIP    Sphinx $@ target."
 endif
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-# commands; the 'cmd' from scripts/Kbuild.include is not *loopable*
-loop_cmd = $(echo-cmd) $(cmd_$(1)) || exit;
-
-# $2 sphinx builder e.g. "html"
-# $3 name of the build subfolder / e.g. "userspace-api/media", used as:
-#    * dest folder relative to $(BUILDDIR) and
-#    * cache folder relative to $(BUILDDIR)/.doctrees
-# $4 dest subfolder e.g. "man" for man pages at userspace-api/media/man
-# $5 reST source folder relative to $(src),
-#    e.g. "userspace-api/media" for the linux-tv book-set at ./Documentation/userspace-api/media
-
-PYTHONPYCACHEPREFIX ?= $(abspath $(BUILDDIR)/__pycache__)
-
-quiet_cmd_sphinx = SPHINX  $@ --> file://$(abspath $(BUILDDIR)/$3/$4)
-      cmd_sphinx = \
-	PYTHONPYCACHEPREFIX="$(PYTHONPYCACHEPREFIX)" \
-	BUILDDIR=$(abspath $(BUILDDIR)) SPHINX_CONF=$(abspath $(src)/$5/$(SPHINX_CONF)) \
-	$(PYTHON3) $(srctree)/scripts/jobserver-exec \
-	$(CONFIG_SHELL) $(srctree)/Documentation/sphinx/parallel-wrapper.sh \
-	$(SPHINXBUILD) \
-	-b $2 \
-	-c $(abspath $(src)) \
-	-d $(abspath $(BUILDDIR)/.doctrees/$3) \
-	-D version=$(KERNELVERSION) -D release=$(KERNELRELEASE) \
-	$(ALLSPHINXOPTS) \
-	$(abspath $(src)/$5) \
-	$(abspath $(BUILDDIR)/$3/$4) && \
-	if [ "x$(DOCS_CSS)" != "x" ]; then \
-		cp $(if $(patsubst /%,,$(DOCS_CSS)),$(abspath $(srctree)/$(DOCS_CSS)),$(DOCS_CSS)) $(BUILDDIR)/$3/_static/; \
-	fi
 
+# HTML main logic is identical to other targets. However, if rust is enabled,
+# an extra step at the end is required to generate rustdoc.
 htmldocs:
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,html,$(var),,$(var)))
-
+	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
+	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
+		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
+		--builddir="$(BUILDDIR)" \
+		--theme=$(DOCS_THEME) --css=$(DOCS_CSS) --paper=$(PAPER)
 # If Rust support is available and .config exists, add rustdoc generated contents.
 # If there are any, the errors from this make rustdoc will be displayed but
 # won't stop the execution of htmldocs
@@ -118,49 +85,6 @@ ifeq ($(CONFIG_RUST),y)
 endif
 endif
 
-texinfodocs:
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,texinfo,$(var),texinfo,$(var)))
-
-# Note: the 'info' Make target is generated by sphinx itself when
-# running the texinfodocs target define above.
-infodocs: texinfodocs
-	$(MAKE) -C $(BUILDDIR)/texinfo info
-
-linkcheckdocs:
-	@$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,linkcheck,$(var),,$(var)))
-
-latexdocs:
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,latex,$(var),latex,$(var)))
-
-ifeq ($(HAVE_PDFLATEX),0)
-
-pdfdocs:
-	$(warning The '$(PDFLATEX)' command was not found. Make sure you have it installed and in PATH to produce PDF output.)
-	@echo "  SKIP    Sphinx $@ target."
-
-else # HAVE_PDFLATEX
-
-pdfdocs: DENY_VF = XDG_CONFIG_HOME=$(FONTS_CONF_DENY_VF)
-pdfdocs: latexdocs
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	$(foreach var,$(SPHINXDIRS), \
-	   $(MAKE) PDFLATEX="$(PDFLATEX)" LATEXOPTS="$(LATEXOPTS)" $(DENY_VF) -C $(BUILDDIR)/$(var)/latex || $(PYTHON3) $(srctree)/tools/docs/check-variable-fonts.py || exit; \
-	   mkdir -p $(BUILDDIR)/$(var)/pdf; \
-	   mv $(subst .tex,.pdf,$(wildcard $(BUILDDIR)/$(var)/latex/*.tex)) $(BUILDDIR)/$(var)/pdf/; \
-	)
-
-endif # HAVE_PDFLATEX
-
-epubdocs:
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,epub,$(var),epub,$(var)))
-
-xmldocs:
-	@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	@+$(foreach var,$(SPHINXDIRS),$(call loop_cmd,sphinx,xml,$(var),xml,$(var)))
-
 endif # HAVE_SPHINX
 
 # The following targets are independent of HAVE_SPHINX, and the rules should
@@ -172,6 +96,9 @@ refcheckdocs:
 cleandocs:
 	$(Q)rm -rf $(BUILDDIR)
 
+# Used only on help
+_SPHINXDIRS   = $(sort $(patsubst $(srctree)/Documentation/%/index.rst,%,$(wildcard $(srctree)/Documentation/*/index.rst)))
+
 dochelp:
 	@echo  ' Linux kernel internal documentation in different formats from ReST:'
 	@echo  '  htmldocs        - HTML'
tools/docs/sphinx-build-wrapper new file +599 -0

599 lines not on this page · raw message

Signature · 1 line
2.51.0
#14 lore raw

PATCH v8 12/24 docs: parallel-wrapper.sh: remove script

In reply to Mauro Carvalho Chehab

The only usage of this script was docs Makefile. Now that it is using the new sphinx-build-wrapper, which has inside the code from parallel-wrapper.sh, we can drop this script.

---
 Documentation/sphinx/parallel-wrapper.sh | 33 ------------------------
 1 file changed, 33 deletions(-)
 delete mode 100644 Documentation/sphinx/parallel-wrapper.sh
Documentation/sphinx/parallel-wrapper.sh deleted +0 -33
deleted file mode 100644
index e54c44ce117d..000000000000
@@ -1,33 +0,0 @@
-#!/bin/sh
-# SPDX-License-Identifier: GPL-2.0+
-#
-# Figure out if we should follow a specific parallelism from the make
-# environment (as exported by scripts/jobserver-exec), or fall back to
-# the "auto" parallelism when "-jN" is not specified at the top-level
-# "make" invocation.
-
-sphinx="$1"
-shift || true
-
-parallel="$PARALLELISM"
-if [ -z "$parallel" ] ; then
-	# If no parallelism is specified at the top-level make, then
-	# fall back to the expected "-jauto" mode that the "htmldocs"
-	# target has had.
-	auto=$(perl -e 'open IN,"'"$sphinx"' --version 2>&1 |";
-			while (<IN>) {
-				if (m/([\d\.]+)/) {
-					print "auto" if ($1 >= "1.7")
-				}
-			}
-			close IN')
-	if [ -n "$auto" ] ; then
-		parallel="$auto"
-	fi
-fi
-# Only if some parallelism has been determined do we add the -jN option.
-if [ -n "$parallel" ] ; then
-	parallel="-j$parallel"
-fi
-
-exec "$sphinx" $parallel "$@"
Signature · 1 line
2.51.0
#15 lore raw

PATCH v8 13/24 docs: Makefile: document latex/PDF PAPER= parameter

In reply to Mauro Carvalho Chehab

While the build system supports this for a long time, this was never documented. Add a documentation for it.

---
 Documentation/Makefile | 2 ++
 1 file changed, 2 insertions(+)
Documentation/Makefile +2 -0
index 380284026c13..a52b311cdadc 100644
@@ -124,4 +124,6 @@ dochelp:
 	@echo
 	@echo  '  make DOCS_CSS={a .css file} adds a DOCS_CSS override file for html/epub output.'
 	@echo
+	@echo  '  make PAPER={a4|letter} Specifies the paper size used for LaTeX/PDF output.'
+	@echo
 	@echo  '  Default location for the generated documents is Documentation/output'
Signature · 1 line
2.51.0
#16 lore raw

PATCH v8 14/24 docs: Makefile: document FONTS_CONF_DENY_VF= parameter

In reply to Mauro Carvalho Chehab

This parameter is there for some time, but it doesn't have anything documenting it at make help.

Add some documentation, pointing to the place where one can find more details.

---
 Documentation/Makefile | 3 +++
 1 file changed, 3 insertions(+)
Documentation/Makefile +3 -0
index a52b311cdadc..05397b9d844d 100644
@@ -126,4 +126,7 @@ dochelp:
 	@echo
 	@echo  '  make PAPER={a4|letter} Specifies the paper size used for LaTeX/PDF output.'
 	@echo
+	@echo  '  make FONTS_CONF_DENY_VF={path} sets a deny list to block variable Noto CJK fonts'
+	@echo  '  for PDF build. See tools/docs/lib/latex_fonts.py for more details'
+	@echo
 	@echo  '  Default location for the generated documents is Documentation/output'
Signature · 1 line
2.51.0
#17 lore raw

PATCH v8 15/24 tools/docs: sphinx-build-wrapper: add an argument for LaTeX interactive mode

In reply to Mauro Carvalho Chehab

By default, we use LaTeX batch mode to build docs. This way, when an error happens, the build fails. This is good for normal builds, but when debugging problems with pdf generation, the best is to use interactive mode.

We already support it via LATEXOPTS, but having a command line argument makes it easier:

Interactive mode:

	./scripts/sphinx-build-wrapper pdfdocs --sphinxdirs peci -v -i
	...
	Running 'xelatex --no-pdf  -no-pdf -recorder  ".../Documentation/output/peci/latex/peci.tex"'
	...

Default batch mode:

        ./scripts/sphinx-build-wrapper pdfdocs --sphinxdirs peci -v
	...
	Running 'xelatex --no-pdf  -no-pdf -interaction=batchmode -no-shell-escape -recorder  ".../Documentation/output/peci/latex/peci.tex"'
	...
---
 tools/docs/sphinx-build-wrapper | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)
tools/docs/sphinx-build-wrapper +11 -3
index c57c732b879c..50761f125bc9 100755
@@ -173,7 +173,7 @@ class SphinxBuilder:
         if not verbose:
             self.sphinxopts += ["-q"]
 
-    def __init__(self, builddir, verbose=False, n_jobs=None):
+    def __init__(self, builddir, verbose=False, n_jobs=None, interactive=None):
         """Initialize internal variables"""
         self.verbose = None
 
@@ -183,7 +183,11 @@ class SphinxBuilder:
         self.kernelversion = os.environ.get("KERNELVERSION", "unknown")
         self.kernelrelease = os.environ.get("KERNELRELEASE", "unknown")
         self.pdflatex = os.environ.get("PDFLATEX", "xelatex")
-        self.latexopts = os.environ.get("LATEXOPTS", "-interaction=batchmode -no-shell-escape")
+
+        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", "") != "")
@@ -584,12 +588,16 @@ def main():
     parser.add_argument('-j', '--jobs', type=jobs_type,
                         help="Sets number of jobs to use with sphinx-build")
 
+    parser.add_argument('-i', '--interactive', action='store_true',
+                        help="Change latex default to run in interactive mode")
+
     args = parser.parse_args()
 
     PythonVersion.check_python(MIN_PYTHON_VERSION)
 
     builder = SphinxBuilder(builddir=args.builddir,
-                            verbose=args.verbose, n_jobs=args.jobs)
+                            verbose=args.verbose, n_jobs=args.jobs,
+                            interactive=args.interactive)
 
     builder.build(args.target, sphinxdirs=args.sphinxdirs, conf=args.conf,
                   theme=args.theme, css=args.css, paper=args.paper,
Signature · 1 line
2.51.0
#18 lore raw

PATCH v8 16/24 tools/docs: sphinx-build-wrapper: allow building PDF files in parallel

In reply to Mauro Carvalho Chehab

Use POSIX jobserver when available or -j<number> to run PDF builds in parallel, restoring pdf build performance. Yet, running it when debugging troubles is a bad idea, so, when calling directly via command line, except if "-j" is splicitly requested, it will serialize the build.

With such change, a PDF doc builds now takes around 5 minutes on a Ryzen 9 machine with 32 cpu threads:

	# Explicitly paralelize both Sphinx and LaTeX pdf builds
	$ make cleandocs; time scripts/sphinx-build-wrapper pdfdocs -j 33

	real	5m17.901s
	user	15m1.499s
	sys	2m31.482s

	# Use POSIX jobserver to paralelize both sphinx-build and LaTeX
	$ make cleandocs; time make pdfdocs

	real	5m22.369s
	user	15m9.076s
	sys	2m31.419s

	# Serializes PDF build, while keeping Sphinx parallelized.
	# it is equivalent of passing -jauto via command line
	$ make cleandocs; time scripts/sphinx-build-wrapper pdfdocs

	real	11m20.901s
	user	13m2.910s
	sys	1m44.553s
---
 tools/docs/sphinx-build-wrapper | 218 +++++++++++++++++++++++---------
 1 file changed, 156 insertions(+), 62 deletions(-)
tools/docs/sphinx-build-wrapper +156 -62
index 50761f125bc9..0fe21b343e7a 100755
@@ -51,6 +51,8 @@ import shutil
 import subprocess
 import sys
 
+from concurrent import futures
+
 from lib.python_version import PythonVersion
 from lib.latex_fonts import LatexFontChecker
 
@@ -291,6 +293,87 @@ class SphinxBuilder:
         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)]
+
+                max_len = max(max_len, len(name))
+
+                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)
+
+                try:
+                    success = future.result()
+
+                    if success and os.path.exists(pdf_from):
+                        pdf_to = os.path.join(pdf_dir, pdf_name)
+
+                        os.rename(pdf_from, pdf_to)
+
+                        #
+                        # if verbose, get the name of built PDF file
+                        #
+                        if self.verbose:
+                           builds[name] = os.path.relpath(pdf_to, self.builddir)
+                    else:
+                        builds[name] = "FAILED"
+                        build_failed = True
+                except futures.Error as e:
+                    builds[name] = f"FAILED ({repr(e)})"
+                    build_failed = True
+
+        #
+        # Handle case where no .tex files were found
+        #
+        if not has_tex:
+            name = "Sphinx LaTeX builder"
+            max_len = max(max_len, len(name))
+            builds[name] = "FAILED (no .tex file was generated)"
+            build_failed = True
+
+        return builds, build_failed, max_len
+
     def handle_pdf(self, output_dirs, deny_vf):
         """
         Extra steps for PDF output.
@@ -301,6 +384,8 @@ class SphinxBuilder:
         """
         builds = {}
         max_len = 0
+        tex_suffix = ".tex"
+        tex_files = []
 
         #
         # Since early 2024, Fedora and openSUSE tumbleweed have started
@@ -327,74 +412,83 @@ class SphinxBuilder:
 
             latex_cmd.extend(shlex.split(self.latexopts))
 
-            tex_suffix = ".tex"
-
-            #
-            # Process each .tex file
-            #
-
-            has_tex = False
-            build_failed = False
+            # Get a list of tex files to process
             with os.scandir(from_dir) as it:
                 for entry in it:
-                    if not entry.name.endswith(tex_suffix):
-                        continue
-
-                    name = entry.name[:-len(tex_suffix)]
-                    has_tex = True
-
-                    #
-                    # 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: check if all PDF
-                    # files were built. If they're, print a summary and
-                    # return 0 at the end of this function
-                    #
-                    try:
-                        subprocess.run(latex_cmd + [entry.path],
-                                       cwd=from_dir, check=True, env=self.env)
-                    except subprocess.CalledProcessError:
-                        pass
-
-                    pdf_name = name + ".pdf"
-                    pdf_from = os.path.join(from_dir, pdf_name)
-                    pdf_to = os.path.join(pdf_dir, pdf_name)
-
-                    if os.path.exists(pdf_from):
-                        os.rename(pdf_from, pdf_to)
-                        builds[name] = os.path.relpath(pdf_to, self.builddir)
-                    else:
-                        builds[name] = "FAILED"
-                        build_failed = True
-
-                    name = entry.name.removesuffix(".tex")
-                    max_len = max(max_len, len(name))
-
-            if not has_tex:
-                name = os.path.basename(from_dir)
-                max_len = max(max_len, len(name))
-                builds[name] = "FAILED (no .tex)"
-                build_failed = True
-
-        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:
+                    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("PDF build failed: not all PDF files were created.")
-        else:
-            print("All PDF files were built.")
+            sys.exit(f"Error: Can't build {n_failures} PDF file(s): {failures}")
 
     def handle_info(self, output_dirs):
         """
Signature · 1 line
2.51.0
#19 lore raw

PATCH v8 17/24 tools/docs,scripts: sphinx-*: prevent sphinx-build crashes

In reply to Mauro Carvalho Chehab

On a properly set system, LANG and LC_ALL is always defined. However, some distros like Debian, Gentoo and their variants start with those undefioned.

When Sphinx tries to set a locale with:

locale.setlocale(locale.LC_ALL, '')

It raises an exception, making Sphinx fail. This is more likely to happen with test containers.

Add a logic to detect and workaround such issue by setting locale to C.

---
 tools/docs/sphinx-build-wrapper | 11 +++++++++++
 tools/docs/sphinx-pre-install   | 14 +++++++++++++-
 2 files changed, 24 insertions(+), 1 deletion(-)
tools/docs/sphinx-build-wrapper +11 -0
index 0fe21b343e7a..6c2580303e8e 100755
@@ -45,6 +45,7 @@ the newer version.
 """
 
 import argparse
+import locale
 import os
 import shlex
 import shutil
@@ -565,6 +566,16 @@ class SphinxBuilder:
         if not sphinxdirs:
             sphinxdirs = os.environ.get("SPHINXDIRS", ".")
 
+        #
+        # 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"
+
         #
         # sphinxdirs can be a list or a whitespace-separated string
         #
tools/docs/sphinx-pre-install +13 -1
index d6d673b7945c..663d4e2a3f57 100755
@@ -26,6 +26,7 @@ system pacage install is recommended.
 """
 
 import argparse
+import locale
 import os
 import re
 import subprocess
@@ -422,8 +423,19 @@ class MissingCheckers(AncillaryMethods):
         """
         Gets sphinx-build version.
         """
+        env = os.environ.copy()
+
+        # 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:
-            result = self.run([cmd, "--version"],
+            locale.setlocale(locale.LC_ALL, '')
+        except Exception:
+            env["LC_ALL"] = "C"
+            env["LANG"] = "C"
+
+        try:
+            result = self.run([cmd, "--version"], env=env,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT,
                               text=True, check=True)
Signature · 1 line
2.51.0
#20 lore raw

PATCH v8 18/24 tools/docs: sphinx-build-wrapper: Fix output for duplicated names

In reply to Mauro Carvalho Chehab

When SPHINXDIRS is used, basename may be identical for different files. If this happens, the summary and error detection won't be accurate.

Fix it by using relative names from builddir.

While here, don't duplicate names. Report, instead:

- SUCCESS
output PDF file was built
- FAILED
latexmk/xelatex didn't build any PDF output
- FAILED: no .tex files were generated
Sphinx didn't build any tex file for SPHINXDIRS directories
- FAILED ({python exception})
When a concurrent.futures is catched. Usually indicates an internal error at the build logic.

With that, building multiple dirs with the same name is reported properly:

    $ make V=1 SPHINXDIRS="admin-guide/media driver-api/media userspace-api/media" pdfdocs

Summary
=======
admin-guide/media/pdf/media.pdf : SUCCESS
driver-api/media/pdf/media.pdf : SUCCESS
userspace-api/media/pdf/media.pdf: SUCCESS

And if at least one of them fails, return code will be 1.

---
 tools/docs/sphinx-build-wrapper | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)
tools/docs/sphinx-build-wrapper +9 -11
index 6c2580303e8e..8d1f77c4a880 100755
@@ -329,9 +329,6 @@ class SphinxBuilder:
                     continue
 
                 name = name[:-len(tex_suffix)]
-
-                max_len = max(max_len, len(name))
-
                 has_tex = True
 
                 future = executor.submit(self.build_pdf_file, latex_cmd,
@@ -343,34 +340,35 @@ class SphinxBuilder:
 
                 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):
-                        pdf_to = os.path.join(pdf_dir, pdf_name)
-
                         os.rename(pdf_from, pdf_to)
 
                         #
                         # if verbose, get the name of built PDF file
                         #
                         if self.verbose:
-                           builds[name] = os.path.relpath(pdf_to, self.builddir)
+                           builds[out_name] = "SUCCESS"
                     else:
-                        builds[name] = "FAILED"
+                        builds[out_name] = "FAILED"
                         build_failed = True
                 except futures.Error as e:
-                    builds[name] = f"FAILED ({repr(e)})"
+                    builds[out_name] = f"FAILED ({repr(e)})"
                     build_failed = True
 
         #
         # Handle case where no .tex files were found
         #
         if not has_tex:
-            name = "Sphinx LaTeX builder"
-            max_len = max(max_len, len(name))
-            builds[name] = "FAILED (no .tex file was generated)"
+            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
Signature · 1 line
2.51.0
#21 lore raw

PATCH v8 19/24 docs: add support to build manpages from kerneldoc output

In reply to Mauro Carvalho Chehab

Generating man files currently requires running a separate script. The target also doesn't appear at the docs Makefile.

Add support for mandocs at the Makefile, adding the build logic inside sphinx-build-wrapper, updating documentation and dropping the ancillary script.

---
 Documentation/Makefile                 |  3 +-
 Documentation/doc-guide/kernel-doc.rst | 29 ++++-----
 Makefile                               |  2 +-
 scripts/split-man.pl                   | 28 ---------
 tools/docs/sphinx-build-wrapper        | 81 ++++++++++++++++++++++++--
 5 files changed, 95 insertions(+), 48 deletions(-)
 delete mode 100755 scripts/split-man.pl
Documentation/Makefile +2 -1
index 05397b9d844d..aa42b2cb7030 100644
@@ -53,7 +53,7 @@ ifeq ($(HAVE_SPHINX),0)
 else # HAVE_SPHINX
 
 # Common documentation targets
-infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
+mandocs infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
 	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
 		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
@@ -104,6 +104,7 @@ dochelp:
 	@echo  '  htmldocs        - HTML'
 	@echo  '  texinfodocs     - Texinfo'
 	@echo  '  infodocs        - Info'
+	@echo  '  mandocs         - Man pages'
 	@echo  '  latexdocs       - LaTeX'
 	@echo  '  pdfdocs         - PDF'
 	@echo  '  epubdocs        - EPUB'
Documentation/doc-guide/kernel-doc.rst +16 -13
index af9697e60165..4370cc8fbcf5 100644
@@ -579,20 +579,23 @@ source.
 How to use kernel-doc to generate man pages
 -------------------------------------------
 
-If you just want to use kernel-doc to generate man pages you can do this
-from the kernel git tree::
+To generate man pages for all files that contain kernel-doc markups, run::
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- :^Documentation :^tools) \
-    | scripts/split-man.pl /tmp/man
+  $ make mandocs
 
-Some older versions of git do not support some of the variants of syntax for
-path exclusion.  One of the following commands may work for those versions::
+Or calling ``script-build-wrapper`` directly::
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ':!Documentation' ':!tools') \
-    | scripts/split-man.pl /tmp/man
+  $ ./tools/docs/sphinx-build-wrapper mandocs
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ":(exclude)Documentation" ":(exclude)tools") \
-    | scripts/split-man.pl /tmp/man
+The output will be at ``/man`` directory inside the output directory
+(by default: ``Documentation/output``).
+
+Optionally, it is possible to generate a partial set of man pages by
+using SPHINXDIRS:
+
+  $ make SPHINXDIRS=driver-api/media mandocs
+
+.. note::
+
+   When SPHINXDIRS={subdir} is used, it will only generate man pages for
+   the files explicitly inside a ``Documentation/{subdir}/.../*.rst`` file.
Makefile +1 -1
index 6bfe776bf3c5..9bd44afeda26 100644
@@ -1800,7 +1800,7 @@ $(help-board-dirs): help-%:
 # Documentation targets
 # ---------------------------------------------------------------------------
 DOC_TARGETS := xmldocs latexdocs pdfdocs htmldocs epubdocs cleandocs \
-	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs
+	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs mandocs
 PHONY += $(DOC_TARGETS)
 $(DOC_TARGETS):
 	$(Q)$(MAKE) $(build)=Documentation $@
scripts/split-man.pl deleted +0 -28
deleted file mode 100755
index 96bd99dc977a..000000000000
@@ -1,28 +0,0 @@
-#!/usr/bin/env perl
-# SPDX-License-Identifier: GPL-2.0
-#
-# Author: Mauro Carvalho Chehab <[email protected]>
-#
-# Produce manpages from kernel-doc.
-# See Documentation/doc-guide/kernel-doc.rst for instructions
-
-if ($#ARGV < 0) {
-   die "where do I put the results?\n";
-}
-
-mkdir $ARGV[0],0777;
-$state = 0;
-while (<STDIN>) {
-    if (/^\.TH \"[^\"]*\" 9 \"([^\"]*)\"/) {
-	if ($state == 1) { close OUT }
-	$state = 1;
-	$fn = "$ARGV[0]/$1.9";
-	print STDERR "Creating $fn\n";
-	open OUT, ">$fn" or die "can't open $fn: $!\n";
-	print OUT $_;
-    } elsif ($state != 0) {
-	print OUT $_;
-    }
-}
-
-close OUT;
tools/docs/sphinx-build-wrapper +76 -5

117 lines not on this page · raw message

Signature · 1 line
2.51.0
maintainer Acked-by on v8 19/24
#22 lore raw

Re: [PATCH v8 19/24] docs: add support to build manpages from kerneldoc output

Please note that @kernel.org receipients are still not addressed correctly:

Quoted 10 lines

Date: Thu, 18 Sep 2025 13:54:53 +0200
From: Mauro Carvalho Chehab <[email protected]>
To: Linux Doc Mailing List <[email protected]>, Jonathan Corbet <[email protected]>
Cc: Mauro Carvalho Chehab <[email protected]>, Akira Yokosawa <[email protected]>,
Mauro Carvalho Chehab <[email protected]>, Thomas Weißschuh <[email protected]>, Alice Ryhl <[email protected]>, Masahiro Yamada <[email protected]>, Miguel Ojeda <[email protected]>, Nathan Chancellor <[email protected]>, Nicolas Schier <[email protected]>, Randy Dunlap <[email protected]>, Tamir Duberstein <[email protected]>, [email protected], [email protected]

Quoted 48 lines · Mauro Carvalho Chehab

Generating man files currently requires running a separate script. The target also doesn't appear at the docs Makefile.

Add support for mandocs at the Makefile, adding the build logic inside sphinx-build-wrapper, updating documentation and dropping the ancillary script.

---
 Documentation/Makefile                 |  3 +-
 Documentation/doc-guide/kernel-doc.rst | 29 ++++-----
 Makefile                               |  2 +-
 scripts/split-man.pl                   | 28 ---------
 tools/docs/sphinx-build-wrapper        | 81 ++++++++++++++++++++++++--
 5 files changed, 95 insertions(+), 48 deletions(-)
 delete mode 100755 scripts/split-man.pl
Documentation/Makefile +2 -1
index 05397b9d844d..aa42b2cb7030 100644
@@ -53,7 +53,7 @@ ifeq ($(HAVE_SPHINX),0)
 else # HAVE_SPHINX
 
 # Common documentation targets
-infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
+mandocs infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
 	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
 		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
@@ -104,6 +104,7 @@ dochelp:
 	@echo  '  htmldocs        - HTML'
 	@echo  '  texinfodocs     - Texinfo'
 	@echo  '  infodocs        - Info'
+	@echo  '  mandocs         - Man pages'
 	@echo  '  latexdocs       - LaTeX'
 	@echo  '  pdfdocs         - PDF'
 	@echo  '  epubdocs        - EPUB'
Documentation/doc-guide/kernel-doc.rst +0 -2
index af9697e60165..4370cc8fbcf5 100644
@@ -579,20 +579,23 @@ source.
 How to use kernel-doc to generate man pages
 -------------------------------------------
 
-If you just want to use kernel-doc to generate man pages you can do this
-from the kernel git tree::

Documentation/doc-guide/kernel-doc.rst source. · line 586

Quoted 10 lines
Documentation/doc-guide/kernel-doc.rst +3 -5
+To generate man pages for all files that contain kernel-doc markups, run::
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- :^Documentation :^tools) \
-    | scripts/split-man.pl /tmp/man
+  $ make mandocs
 
-Some older versions of git do not support some of the variants of syntax for
-path exclusion.  One of the following commands may work for those versions::
+Or calling ``script-build-wrapper`` directly::

For this very patch, there is no 'scripts-build-wrapper' but 'sphinx-build-wrapper'. Or am I missing something?

Quoted 26 lines
Documentation/doc-guide/kernel-doc.rst +13 -6
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ':!Documentation' ':!tools') \
-    | scripts/split-man.pl /tmp/man
+  $ ./tools/docs/sphinx-build-wrapper mandocs
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ":(exclude)Documentation" ":(exclude)tools") \
-    | scripts/split-man.pl /tmp/man
+The output will be at ``/man`` directory inside the output directory
+(by default: ``Documentation/output``).
+
+Optionally, it is possible to generate a partial set of man pages by
+using SPHINXDIRS:
+
+  $ make SPHINXDIRS=driver-api/media mandocs
+
+.. note::
+
+   When SPHINXDIRS={subdir} is used, it will only generate man pages for
+   the files explicitly inside a ``Documentation/{subdir}/.../*.rst`` file.
Makefile +0 -0
index 6bfe776bf3c5..9bd44afeda26 100644

Makefile $(help-board-dirs): help-%: · line 1806

Quoted 8 lines
Makefile +1 -1
@@ -1800,7 +1800,7 @@ $(help-board-dirs): help-%:
 # Documentation targets
 # ---------------------------------------------------------------------------
 DOC_TARGETS := xmldocs latexdocs pdfdocs htmldocs epubdocs cleandocs \
-	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs
+	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs mandocs
 PHONY += $(DOC_TARGETS)
 $(DOC_TARGETS):
 	$(Q)$(MAKE) $(build)=Documentation $@
#23 lore raw

Re: [PATCH v8 19/24] docs: add support to build manpages from kerneldoc output

Em Sat, 27 Sep 2025 22:35:00 +0200
Nicolas Schier <[email protected]> escreveu:

Quoted 13 lines

Please note that @kernel.org receipients are still not addressed correctly:

Quoted 10 lines

Date: Thu, 18 Sep 2025 13:54:53 +0200
From: Mauro Carvalho Chehab <[email protected]>
To: Linux Doc Mailing List <[email protected]>, Jonathan Corbet <[email protected]>
Cc: Mauro Carvalho Chehab <[email protected]>, Akira Yokosawa <[email protected]>,
Mauro Carvalho Chehab <[email protected]>, Thomas Weißschuh <[email protected]>, Alice Ryhl <[email protected]>, Masahiro Yamada <[email protected]>, Miguel Ojeda <[email protected]>, Nathan Chancellor <[email protected]>, Nicolas Schier <[email protected]>, Randy Dunlap <[email protected]>, Tamir Duberstein <[email protected]>, [email protected], [email protected]

Just Found the issue: the problem were on a rewrite rule on my emailer which I added ages ago. Dropped it. It should be ok next time.

Quoted 56 lines · Mauro Carvalho Chehab
Quoted 55 lines

Generating man files currently requires running a separate script. The target also doesn't appear at the docs Makefile.

Add support for mandocs at the Makefile, adding the build logic inside sphinx-build-wrapper, updating documentation and dropping the ancillary script.

---
 Documentation/Makefile                 |  3 +-
 Documentation/doc-guide/kernel-doc.rst | 29 ++++-----
 Makefile                               |  2 +-
 scripts/split-man.pl                   | 28 ---------
 tools/docs/sphinx-build-wrapper        | 81 ++++++++++++++++++++++++--
 5 files changed, 95 insertions(+), 48 deletions(-)
 delete mode 100755 scripts/split-man.pl
Documentation/Makefile +2 -1
index 05397b9d844d..aa42b2cb7030 100644
@@ -53,7 +53,7 @@ ifeq ($(HAVE_SPHINX),0)
 else # HAVE_SPHINX
 
 # Common documentation targets
-infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
+mandocs infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
 	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
 	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
 		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
@@ -104,6 +104,7 @@ dochelp:
 	@echo  '  htmldocs        - HTML'
 	@echo  '  texinfodocs     - Texinfo'
 	@echo  '  infodocs        - Info'
+	@echo  '  mandocs         - Man pages'
 	@echo  '  latexdocs       - LaTeX'
 	@echo  '  pdfdocs         - PDF'
 	@echo  '  epubdocs        - EPUB'
Documentation/doc-guide/kernel-doc.rst +2 -5
index af9697e60165..4370cc8fbcf5 100644
@@ -579,20 +579,23 @@ source.
 How to use kernel-doc to generate man pages
 -------------------------------------------
 
-If you just want to use kernel-doc to generate man pages you can do this
-from the kernel git tree::
+To generate man pages for all files that contain kernel-doc markups, run::
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- :^Documentation :^tools) \
-    | scripts/split-man.pl /tmp/man
+  $ make mandocs
 
Quoted 6 lines

Documentation/doc-guide/kernel-doc.rst source. · line 586

Quoted 3 lines
Documentation/doc-guide/kernel-doc.rst +1 -2
-Some older versions of git do not support some of the variants of syntax for
-path exclusion.  One of the following commands may work for those versions::
+Or calling ``script-build-wrapper`` directly::  

For this very patch, there is no 'scripts-build-wrapper' but 'sphinx-build-wrapper'. Or am I missing something?

Sorry! yes, sphinx-build-wrapper is the right name.

Quoted 29 lines
Quoted 29 lines
Documentation/doc-guide/kernel-doc.rst +13 -6
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ':!Documentation' ':!tools') \
-    | scripts/split-man.pl /tmp/man
+  $ ./tools/docs/sphinx-build-wrapper mandocs
 
-  $ scripts/kernel-doc -man \
-    $(git grep -l '/\*\*' -- . ":(exclude)Documentation" ":(exclude)tools") \
-    | scripts/split-man.pl /tmp/man
+The output will be at ``/man`` directory inside the output directory
+(by default: ``Documentation/output``).
+
+Optionally, it is possible to generate a partial set of man pages by
+using SPHINXDIRS:
+
+  $ make SPHINXDIRS=driver-api/media mandocs
+
+.. note::
+
+   When SPHINXDIRS={subdir} is used, it will only generate man pages for
+   the files explicitly inside a ``Documentation/{subdir}/.../*.rst`` file.
Makefile +0 -0
index 6bfe776bf3c5..9bd44afeda26 100644
@@ -1800,7 +1800,7 @@ $(help-board-dirs): help-%:
 # Documentation targets
 # ---------------------------------------------------------------------------
Quoted 8 lines

Makefile $(help-board-dirs): help-%: · line 1806

Quoted 6 lines
Makefile +1 -1
 DOC_TARGETS := xmldocs latexdocs pdfdocs htmldocs epubdocs cleandocs \
-	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs
+	       linkcheckdocs dochelp refcheckdocs texinfodocs infodocs mandocs
 PHONY += $(DOC_TARGETS)
 $(DOC_TARGETS):
 	$(Q)$(MAKE) $(build)=Documentation $@  

Thanks!

Mauro

#24 lore raw

PATCH v8 20/24 tools: kernel-doc: add a see also section at man pages

In reply to Mauro Carvalho Chehab

While cross-references are complex, as related ones can be on different files, we can at least correlate the ones that belong to the same file, adding a SEE ALSO section for them.

The result is not bad. See for instance:

	$ tools/docs/sphinx-build-wrapper --sphinxdirs driver-api/media -- mandocs
	$ man Documentation/output/driver-api/man/edac_pci_add_device.9

edac_pci_add_device(9) Kernel Hacker's Manual edac_pci_add_device(9)

NAME
edac_pci_add_device - Insert the 'edac_dev' structure into the edac_pci global list and create sysfs entries associated with edac_pci structure.

SYNOPSIS
int edac_pci_add_device (struct edac_pci_ctl_info *pci , int edac_idx );

ARGUMENTS
pci pointer to the edac_device structure to be added to
the list

edac_idx A unique numeric identifier to be assigned to the

RETURN
0 on Success, or an error code on failure

SEE ALSO
edac_pci_alloc_ctl_info(9), edac_pci_free_ctl_info(9),
edac_pci_alloc_index(9), edac_pci_del_device(9), edac_pci_cre‐
ate_generic_ctl(9), edac_pci_release_generic_ctl(9),
edac_pci_create_sysfs(9), edac_pci_remove_sysfs(9)

August 2025 edac_pci_add_device edac_pci_add_device(9)

---
 scripts/lib/kdoc/kdoc_files.py  |  5 +-
 scripts/lib/kdoc/kdoc_output.py | 84 +++++++++++++++++++++++++++++++--
 2 files changed, 83 insertions(+), 6 deletions(-)
scripts/lib/kdoc/kdoc_files.py +4 -1
index 9e09b45b02fa..061c033f32da 100644
@@ -275,7 +275,10 @@ class KernelFiles():
                 self.config.log.warning("No kernel-doc for file %s", fname)
                 continue
 
-            for arg in self.results[fname]:
+            symbols = self.results[fname]
+            self.out_style.set_symbols(symbols)
+
+            for arg in symbols:
                 m = self.out_msg(fname, arg.name, arg)
 
                 if m is None:
scripts/lib/kdoc/kdoc_output.py +79 -5

134 lines not on this page · raw message

Signature · 1 line
2.51.0
#25 lore raw

PATCH v8 21/24 scripts: kdoc_parser.py: warn about Python version only once

In reply to Mauro Carvalho Chehab

When running kernel-doc over multiple documents, it emits one error message per file with is not what we want:

	$ python3.6 scripts/kernel-doc.py . --none
	...
	Warning: ./include/trace/events/swiotlb.h:0 Python 3.7 or later is required for correct results
	Warning: ./include/trace/events/iommu.h:0 Python 3.7 or later is required for correct results
	Warning: ./include/trace/events/sock.h:0 Python 3.7 or later is required for correct results
	...

Change the logic to warn it only once at the library:

	$ python3.6 scripts/kernel-doc.py . --none
	Warning: Python 3.7 or later is required for correct results
	Warning: ./include/cxl/features.h:0 Python 3.7 or later is required for correct results

When running from command line, it warns twice, but that sounds ok.

---
 scripts/lib/kdoc/kdoc_parser.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
scripts/lib/kdoc/kdoc_parser.py +6 -1
index a560546c1867..574972e1f741 100644
@@ -314,6 +314,7 @@ class KernelEntry:
             self.section = SECTION_DEFAULT
             self._contents = []
 
+python_warning = False
 
 class KernelDoc:
     """
@@ -347,9 +348,13 @@ class KernelDoc:
         # We need Python 3.7 for its "dicts remember the insertion
         # order" guarantee
         #
-        if sys.version_info.major == 3 and sys.version_info.minor < 7:
+        global python_warning
+        if (not python_warning and
+            sys.version_info.major == 3 and sys.version_info.minor < 7):
+
             self.emit_msg(0,
                           'Python 3.7 or later is required for correct results')
+            python_warning = True
 
     def emit_msg(self, ln, msg, warning=True):
         """Emit a message"""
Signature · 1 line
2.51.0
#26 lore raw

PATCH v8 22/24 tools/docs: sphinx-build-wrapper: move rust doc builder to wrapper

In reply to Mauro Carvalho Chehab

Simplify even further the docs Makefile by moving rust build logic to the wrapper.

After this change, running make on an environment with rust enabled works as expected.

With CONFIG_RUST:

    $ make O=/tmp/foo LLVM=1 SPHINXDIRS=peci htmldocs
    make[1]: Entrando no diretório '/tmp/foo'

Using alabaster theme
Using Python kernel-doc
GEN Makefile
DESCEND objtool
CC arch/x86/kernel/asm-offsets.s
INSTALL libsubcmd_headers
CALL /new_devel/docs/scripts/checksyscalls.sh
RUSTC L rust/core.o
BINDGEN rust/bindings/bindings_generated.rs BINDGEN rust/bindings/bindings_helpers_generated.rs
...

Without it:

    $ make SPHINXDIRS=peci htmldocs
    Using alabaster theme
    Using Python kernel-doc

Both work as it is it is supposed to do.

After the change, it is also possible to build directly with the script by passing "--rustodoc".

if CONFIG_RUST, this works fine:

    $ ./tools/docs/sphinx-build-wrapper --sphinxdirs peci --rustdoc -- htmldocs
    Using alabaster theme
    Using Python kernel-doc
      SYNC    include/config/auto.conf
    ...
      RUSTC L rust/core.o
    ...

If not, it will produce a warning that RUST may be disabled:

    $ ./tools/docs/sphinx-build-wrapper --sphinxdirs peci --rustdoc -- htmldocs
    Using alabaster theme
    Using Python kernel-doc
    ***
    *** Configuration file ".config" not found!
    ***
    *** Please run some configurator (e.g. "make oldconfig" or
    *** "make menuconfig" or "make xconfig").
    ***
    make[1]: *** [/new_devel/docs/Makefile:829: .config] Error 1
    make: *** [Makefile:248: __sub-make] Error 2
    Ignored errors when building rustdoc: Command '['make', 'LLVM=1', 'rustdoc']' returned non-zero exit status 2.. Is RUST enabled?
---
 Documentation/Makefile          | 29 +++++-----------
 tools/docs/sphinx-build-wrapper | 59 ++++++++++++++++++---------------
 2 files changed, 41 insertions(+), 47 deletions(-)
Documentation/Makefile +8 -21
index aa42b2cb7030..b32f68387dfc 100644
@@ -23,6 +23,7 @@ SPHINXOPTS    =
 SPHINXDIRS    = .
 DOCS_THEME    =
 DOCS_CSS      =
+RUSTDOC       =
 SPHINX_CONF   = conf.py
 PAPER         =
 BUILDDIR      = $(obj)/output
@@ -42,6 +43,10 @@ FONTS_CONF_DENY_VF ?= $(HOME)/deny-vf
 # User-friendly check for sphinx-build
 HAVE_SPHINX := $(shell if which $(SPHINXBUILD) >/dev/null 2>&1; then echo 1; else echo 0; fi)
 
+ifeq ($(CONFIG_RUST),y)
+	RUSTDOC="--rustdoc"
+endif
+
 ifeq ($(HAVE_SPHINX),0)
 
 .DEFAULT:
@@ -53,10 +58,10 @@ ifeq ($(HAVE_SPHINX),0)
 else # HAVE_SPHINX
 
 # Common documentation targets
-mandocs infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
+htmldocs mandocs infodocs texinfodocs latexdocs epubdocs xmldocs pdfdocs linkcheckdocs:
 	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
-		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
+	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ $(RUSTDOC)\
+		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" $(RUSTDOC)\
 		--builddir="$(BUILDDIR)" --deny-vf=$(FONTS_CONF_DENY_VF) \
 		--theme=$(DOCS_THEME) --css=$(DOCS_CSS) --paper=$(PAPER)
 
@@ -67,24 +72,6 @@ pdfdocs:
 	@echo "  SKIP    Sphinx $@ target."
 endif
 
-# HTML main logic is identical to other targets. However, if rust is enabled,
-# an extra step at the end is required to generate rustdoc.
-htmldocs:
-	$(Q)@$(srctree)/tools/docs/sphinx-pre-install --version-check
-	+$(Q)$(PYTHON3) $(BUILD_WRAPPER) $@ \
-		--sphinxdirs="$(SPHINXDIRS)" --conf="$(SPHINX_CONF)" \
-		--builddir="$(BUILDDIR)" \
-		--theme=$(DOCS_THEME) --css=$(DOCS_CSS) --paper=$(PAPER)
-# If Rust support is available and .config exists, add rustdoc generated contents.
-# If there are any, the errors from this make rustdoc will be displayed but
-# won't stop the execution of htmldocs
-
-ifneq ($(wildcard $(srctree)/.config),)
-ifeq ($(CONFIG_RUST),y)
-	$(Q)$(MAKE) rustdoc || true
-endif
-endif
-
 endif # HAVE_SPHINX
 
 # The following targets are independent of HAVE_SPHINX, and the rules should
tools/docs/sphinx-build-wrapper +33 -26

116 lines not on this page · raw message

Signature · 1 line
2.51.0
#27 lore raw

PATCH v8 23/24 tools/docs: sphinx-* break documentation bulds on openSUSE

In reply to Mauro Carvalho Chehab

Before this patch, building htmldocs on opensuseLEAP works fine:

    # make htmldocs
    Available Python versions:
      /usr/bin/python3.11

Python 3.6.15 not supported. Changing to /usr/bin/python3.11 Python 3.6.15 not supported. Changing to /usr/bin/python3.11 Using alabaster theme
Using Python kernel-doc

...

As the logic detects that Python 3.6 is too old and recommends intalling python311-Sphinx. If installed, documentation builds work like a charm.

Yet, some develpers complained that running python3.11 instead of python3 should not happen. So, let's break the build to make them happier:

    $ make htmldocs
    Python 3.6.15 not supported. Bailing out
    You could run, instead:
      /usr/bin/python3.11 tools/docs/sphinx-build-wrapper htmldocs \
        --sphinxdirs=. --conf=conf.py --builddir=Documentation/output --theme= --css= \
        --paper=
    Python 3.6.15 not supported. Bailing out
    make[2]: *** [Documentation/Makefile:76: htmldocs] Error 1
    make[1]: *** [Makefile:1806: htmldocs] Error 2
    make: *** [Makefile:248: __sub-make] Error 2

It should be noticed that:

1. after this change, sphinx-pre-install needs to be called by hand:

    $ /usr/bin/python3.11 tools/docs/sphinx-pre-install
    Detected OS: openSUSE Leap 15.6.
    Sphinx version: 7.2.6

All optional dependencies are met.
Needed package dependencies are met.

2. sphinx-build-wrapper will auto-detect python3.11 and suggest a way to build the docs using the parameters passed via make variables. In this specific example:

   /usr/bin/python3.11 tools/docs/sphinx-build-wrapper htmldocs --sphinxdirs=. --conf=conf.py --theme= --css= --paper=

3. As this needs to be executed outside docs Makefile, it won't run the validation check scripts nor build Rust documentation if enabled, as the extra scripts are part of the docs Makefile.

---
 tools/docs/lib/python_version.py | 28 ++++++++++++++++++++++++----
 tools/docs/sphinx-build-wrapper  |  3 ++-
 tools/docs/sphinx-pre-install    |  3 ++-
 3 files changed, 28 insertions(+), 6 deletions(-)
tools/docs/lib/python_version.py +24 -4
index a9fda2470a26..4fde1b882164 100644
@@ -20,9 +20,11 @@ Python version if present.
 import os
 import re
 import subprocess
+import shlex
 import sys
 
 from glob import glob
+from textwrap import indent
 
 class PythonVersion:
     """
@@ -44,6 +46,25 @@ class PythonVersion:
         """Returns a version tuple as major.minor.patch"""
         return ".".join([str(x) for x in version])
 
+    @staticmethod
+    def cmd_print(cmd, max_len=80):
+        cmd_line = []
+
+        for w in cmd:
+            w = shlex.quote(w)
+
+            if cmd_line:
+                if not max_len or len(cmd_line[-1]) + len(w) < max_len:
+                    cmd_line[-1] += " " + w
+                    continue
+                else:
+                    cmd_line[-1] += " \\"
+                    cmd_line.append(w)
+            else:
+                cmd_line.append(w)
+
+        return "\n  ".join(cmd_line)
+
     def __str__(self):
         """Returns a version tuple as major.minor.patch from self.version"""
         return self.ver_str(self.version)
@@ -130,14 +151,13 @@ class PythonVersion:
         else:
             new_python_cmd = None
 
-        if show_alternatives:
+        if show_alternatives and available_versions:
             print("You could run, instead:")
             for _, cmd in available_versions:
                 args = [cmd, script_path] + sys.argv[1:]
 
-                cmd_str = " ".join(args)
-                print(f"  {cmd_str}")
-            print()
+                cmd_str = indent(PythonVersion.cmd_print(args), "  ")
+                print(f"{cmd_str}\n")
 
         if bail_out:
             msg = f"Python {python_ver} not supported. Bailing out"
tools/docs/sphinx-build-wrapper +2 -1
index e24486dede76..103b0044bb0b 100755
@@ -774,7 +774,8 @@ def main():
 
     args = parser.parse_args()
 
-    PythonVersion.check_python(MIN_PYTHON_VERSION)
+    PythonVersion.check_python(MIN_PYTHON_VERSION, show_alternatives=True,
+                               bail_out=True)
 
     builder = SphinxBuilder(builddir=args.builddir,
                             verbose=args.verbose, n_jobs=args.jobs,
tools/docs/sphinx-pre-install +2 -1
index 663d4e2a3f57..698989584b6a 100755
@@ -1531,7 +1531,8 @@ def main():
 
     checker = SphinxDependencyChecker(args)
 
-    PythonVersion.check_python(MIN_PYTHON_VERSION)
+    PythonVersion.check_python(MIN_PYTHON_VERSION,
+                               bail_out=True, success_on_error=True)
     checker.check_needs()
 
 # Call main if not used as module
Signature · 1 line
2.51.0
#28 lore raw

PATCH v8 24/24 tools/docs: sphinx-build-wrapper: add support to run inside venv

In reply to Mauro Carvalho Chehab

Sometimes, it is desired to run Sphinx from a virtual environment. Add a command line parameter to automatically build Sphinx from such environment.

---
 tools/docs/sphinx-build-wrapper | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)
tools/docs/sphinx-build-wrapper +30 -3
index 103b0044bb0b..6ed3d58ec277 100755
@@ -69,6 +69,7 @@ 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"]
 
@@ -171,8 +172,10 @@ class SphinxBuilder:
         if not verbose:
             self.sphinxopts += ["-q"]
 
-    def __init__(self, builddir, verbose=False, n_jobs=None, interactive=None):
+    def __init__(self, builddir, venv=None, verbose=False, n_jobs=None,
+                 interactive=None):
         """Initialize internal variables"""
+        self.venv = venv
         self.verbose = None
 
         #
@@ -220,6 +223,21 @@ class SphinxBuilder:
 
         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.
@@ -254,7 +272,12 @@ class SphinxBuilder:
             if self.n_jobs:
                 n_jobs = str(self.n_jobs)
 
-            cmd = [sys.executable, sphinx_build]
+            if self.venv:
+                cmd = ["python"]
+            else:
+                cmd = [sys.executable,]
+
+            cmd += [sphinx_build]
             cmd += [f"-j{n_jobs}"]
             cmd += self.sphinxopts
             cmd += build_args
@@ -772,12 +795,16 @@ def main():
     parser.add_argument('-i', '--interactive', action='store_true',
                         help="Change latex default to run in interactive mode")
 
+    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,
+    builder = SphinxBuilder(builddir=args.builddir, venv=args.venv,
                             verbose=args.verbose, n_jobs=args.jobs,
                             interactive=args.interactive)
 
Signature · 1 line
2.51.0
maintainer
#29 lore raw
In reply to Mauro Carvalho Chehab
Quoted 6 lines · Mauro Carvalho Chehab

Hi Jon,

v8 contains a bug fix for ./tools/docs/check-variable-fonts.py command line together with a new --deny-vf argument to it, plus addresses a couple checkpatch warnings. Only 4 patches changed: patches 1, 2, 4 and 5.

OK. I have applied this to a branch called build-script in my tree. Applying it to current docs-mw (or docs-next) was a bit of a challenge, due to merge conflicts with the makefiles, but I got there.

This work is now merged in docs-next (and will thus show in linux-next), but it's not yet in docs-mw, so we're not committed to putting it into 6.18. My current thinking, if all goes well, is to shift it to docs-mw just after the merge window.

It all seems to work for me, with one little oddity: the "Indices" section in Documentation/rust/index.rst (which is protected by the usual ".. only::" block) is being included in the htmldocs build, leading to a spurious "Indices" entry in the left column. Something about the way the rust directory is being build sets "subproject" maybe? I haven't had the time to figure it out.

Thanks,

jon

#30 lore raw

Em Thu, 18 Sep 2025 11:47:59 -0600
Jonathan Corbet <[email protected]> escreveu:

Quoted 10 lines
Quoted 6 lines · Mauro Carvalho Chehab

Hi Jon,

v8 contains a bug fix for ./tools/docs/check-variable-fonts.py command line together with a new --deny-vf argument to it, plus addresses a couple checkpatch warnings. Only 4 patches changed: patches 1, 2, 4 and 5.

OK. I have applied this to a branch called build-script in my tree.

Thanks!

Quoted 7 lines

Applying it to current docs-mw (or docs-next) was a bit of a challenge, due to merge conflicts with the makefiles, but I got there.

This work is now merged in docs-next (and will thus show in linux-next), but it's not yet in docs-mw, so we're not committed to putting it into 6.18. My current thinking, if all goes well, is to shift it to docs-mw just after the merge window.

Sounds like a plan.

Quoted 5 lines

It all seems to work for me, with one little oddity: the "Indices" section in Documentation/rust/index.rst (which is protected by the usual ".. only::" block) is being included in the htmldocs build, leading to a spurious "Indices" entry in the left column. Something about the way the rust directory is being build sets "subproject" maybe?

The only differences with rust is that:

1) it calls sphinx-build with:

       if rustdoc:
            args.extend(["-t", "rustdoc"])

where -t is:

--tag, -t TAG define tag: include "only" blocks with TAG

2) it calls rust makefile at the end of the build with:

        if rustdoc:
            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)

Both are there to mimic the original behavior, but maybe we need to use cwd=<some_dir> to simulate the exact makefile behavior (although, on my tests, not setting it seems to be the right choice, due to O=build_dir).

Quoted 2 lines

I haven't
had the time to figure it out.

I don't remember anymore what "subproject" really means inside ".. only::", block, but I guess it is meant to be used when one passes SPHINXDIRS.

Anyway, if I have to guess, I would try commenting out the "-t" logic and see how it affects the output. If nobody steps up, I'll try to do it probably next week, as I'm a little busy tomorrow.

Thanks,
Mauro

maintainer
#31 lore raw
Quoted 6 lines · Mauro Carvalho Chehab
Quoted 2 lines

I haven't
had the time to figure it out.

I don't remember anymore what "subproject" really means inside ".. only::", block, but I guess it is meant to be used when one passes SPHINXDIRS.

I found myself in a similar position... it is set in Documentation/sphinx/load_config.py, it seems a certain Mauro added that in 2019 :) Since then, it has seemingly been cargo-culted all over the place; I wonder if we really need it.

jo

#32 lore raw

Em Thu, 18 Sep 2025 13:33:53 -0600
Jonathan Corbet <[email protected]> escreveu:

Quoted 5 lines
Quoted 3 lines · Mauro Carvalho Chehab
Quoted 2 lines

I haven't
had the time to figure it out.

Quoted 7 lines
Quoted 3 lines

I don't remember anymore what "subproject" really means inside ".. only::", block, but I guess it is meant to be used when one passes SPHINXDIRS.

I found myself in a similar position... it is set in Documentation/sphinx/load_config.py, it seems a certain Mauro added that in 2019 :)

LOL!

Quoted 2 lines

Since then, it has seemingly been cargo-culted all over the place; I wonder if we really need it.

It looks to me that it have been ages since last touched load_config.py, as it is hard to remember about its dirty secrets.... Maybe it is part of some traumatic experiences that we shared ;-)

Anyway, looking at:
a84d9e899683 ("docs: load_config.py: avoid needing a conf.py just due to LaTeX docs")

and seeking for what we had before, you might noticed that, in the early days, someone called Jonathan wrote patches for instance, adding core-api docs, including this one:

commit 22917b992d3713157e759f23a5a090687d004331
Author: Jonathan Corbet <[email protected]>
Date: Wed Nov 16 16:07:02 2016 -0700

docs: Add more manuals to the PDF build

There were a few manuals that weren't being built in PDF format, but there's no reason not to...

diff --git a/Documentation/core-api/conf.py b/Documentation/core-api/conf.py index fed87ab7f486..db1f7659f3da 100644
--- a/Documentation/core-api/conf.py
+++ b/Documentation/core-api/conf.py
@@ -3,3 +3,8 @@
project = "Core-API Documentation"

tags.add("subproject")
+
+latex_documents = [
+ ('index', 'core-api.tex', project,
+ 'The kernel development community', 'manual'),
+]

there you can see that our mistic "subproject" tag was already present :-D

What happens is that, before a84d9e899683, for every single book we wanted to build via SPHINXDIRS, a conf.py file was required.

The actual logic is a way more complex than that, but what it does is similar to (untested):

    #
    # namespace comes from conf.py: loadConfig(globals())
    #
    def loadConfig(namespace):
	if os.path.basename(os.getcwd()) != "Documentation":
		config = namespace.copy()
	        config['tags'].add("subproject")
	        namespace.update(config)

(it also has some logic there for latex_documents)

On other words:

- subproject exists only on Documentation subdirs. It is basically used to tell *.rst files that SPHINXDIRS=<subdirs>

- its goal is to run genindex when SPHINXDIRS is used to build html documentation.

And that explains why when rust is built, it has a duplicated indexes:
neither the current docs Makefile nor sphinx-build-wrapper should be manually adding "-t subproject".

-

That's said, as part of our doc build cleanups, IMHO we should add on our todo to drop load_config.py, adding the missing bits on conf.py while properly documenting it.

Thanks,
Mauro

maintainer
#33 lore raw
In reply to Mauro Carvalho Chehab
Quoted 6 lines · Mauro Carvalho Chehab

Hi Jon,

v8 contains a bug fix for ./tools/docs/check-variable-fonts.py command line together with a new --deny-vf argument to it, plus addresses a couple checkpatch warnings. Only 4 patches changed: patches 1, 2, 4 and 5.

I have just merged this series, along with the accumulated fixes, into docs-mw; after a month of separate exposure in docs-next, I don't think that there is a lot of benefit in waiting longer.

It still makes me a bit nervous, hopefully all this will work out!

jon

#34 lore raw

Em Fri, 17 Oct 2025 14:13:41 -0600
Jonathan Corbet <[email protected]> escreveu:

Quoted 4 lines
Quoted 2 lines · Mauro Carvalho Chehab

Hi Jon,

Quoted 8 lines
Quoted 4 lines

v8 contains a bug fix for ./tools/docs/check-variable-fonts.py command line together with a new --deny-vf argument to it, plus addresses a couple checkpatch warnings. Only 4 patches changed: patches 1, 2, 4 and 5.

I have just merged this series, along with the accumulated fixes, into docs-mw; after a month of separate exposure in docs-next, I don't think that there is a lot of benefit in waiting longer.

Thanks!

Quoted 2 lines

It still makes me a bit nervous, hopefully all this will work out!

If something breaks, we can fix it ;-)

Placing everything altogether and callable via command line and documented IMHO makes the docs build easier to maintain.

Regards,
Mauro