aboutsummaryrefslogtreecommitdiff
path: root/tools/docs
diff options
context:
space:
mode:
Diffstat (limited to 'tools/docs')
-rwxr-xr-xtools/docs/checktransupdate.py22
-rwxr-xr-xtools/docs/find-unused-docs.sh4
-rwxr-xr-xtools/docs/kdoc_diff508
-rwxr-xr-xtools/docs/kernel-doc397
-rwxr-xr-xtools/docs/sphinx-build-wrapper142
5 files changed, 1014 insertions, 59 deletions
diff --git a/tools/docs/checktransupdate.py b/tools/docs/checktransupdate.py
index e894652369a5..d7b98753e35f 100755
--- a/tools/docs/checktransupdate.py
+++ b/tools/docs/checktransupdate.py
@@ -13,6 +13,8 @@ The usage is as follows:
This will print all the files that need to be updated or translated in the zh_CN locale.
- tools/docs/checktransupdate.py Documentation/translations/zh_CN/dev-tools/testing-overview.rst
This will only print the status of the specified file.
+- tools/docs/checktransupdate.py Documentation/translations/zh_CN/dev-tools
+This will print the status of all files under the directory.
The output is something like:
Documentation/dev-tools/kfence.rst
@@ -76,11 +78,11 @@ def get_origin_from_trans_smartly(origin_path, t_from_head):
(2) Update the translation through commit HASH (TITLE)
"""
# catch flag for 12-bit commit hash
- HASH = r'([0-9a-f]{12})'
+ hash_re = r'([0-9a-f]{12})'
# pattern 1: contains "update to commit HASH"
- pat_update_to = re.compile(rf'update to commit {HASH}')
+ pat_update_to = re.compile(rf'update to commit {hash_re}')
# pattern 2: contains "Update the translation through commit HASH"
- pat_update_translation = re.compile(rf'Update the translation through commit {HASH}')
+ pat_update_translation = re.compile(rf'Update the translation through commit {hash_re}')
origin_commit_hash = None
for line in t_from_head["message"]:
@@ -131,7 +133,7 @@ def check_per_file(file_path):
opath = get_origin_path(file_path)
if not os.path.isfile(opath):
- logging.error("Cannot find the origin path for {file_path}")
+ logging.error("Cannot find the origin path for %s", file_path)
return
o_from_head = get_latest_commit_from(opath, "HEAD")
@@ -262,7 +264,7 @@ def main():
help='Set the logging file (default: checktransupdate.log)')
parser.add_argument(
- "files", nargs="*", help="Files to check, if not specified, check all files"
+ "files", nargs="*", help="Files or directories to check, if not specified, check all files"
)
args = parser.parse_args()
@@ -293,6 +295,16 @@ def main():
if args.print_missing_translations:
logging.info(os.path.relpath(os.path.abspath(file), linux_path))
logging.info("No translation in the locale of %s\n", args.locale)
+ else:
+ # check if the files are directories or files
+ new_files = []
+ for file in files:
+ if os.path.isfile(file):
+ new_files.append(file)
+ elif os.path.isdir(file):
+ # for directories, list all files in the directory and its subfolders
+ new_files.extend(list_files_with_excluding_folders(file, [], "rst"))
+ files = new_files
files = list(map(lambda x: os.path.relpath(os.path.abspath(x), linux_path), files))
diff --git a/tools/docs/find-unused-docs.sh b/tools/docs/find-unused-docs.sh
index 05552dbda5bc..53514c759dc1 100755
--- a/tools/docs/find-unused-docs.sh
+++ b/tools/docs/find-unused-docs.sh
@@ -28,7 +28,7 @@ if ! [ -d "$1" ]; then
fi
cd "$( dirname "${BASH_SOURCE[0]}" )"
-cd ..
+cd ../..
cd Documentation/
@@ -54,7 +54,7 @@ for file in `find $1 -name '*.c'`; do
if [[ ${FILES_INCLUDED[$file]+_} ]]; then
continue;
fi
- str=$(PYTHONDONTWRITEBYTECODE=1 scripts/kernel-doc -export "$file" 2>/dev/null)
+ str=$(PYTHONDONTWRITEBYTECODE=1 tools/docs/kernel-doc -export "$file" 2>/dev/null)
if [[ -n "$str" ]]; then
echo "$file"
fi
diff --git a/tools/docs/kdoc_diff b/tools/docs/kdoc_diff
new file mode 100755
index 000000000000..1aa16bdccaa3
--- /dev/null
+++ b/tools/docs/kdoc_diff
@@ -0,0 +1,508 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
+#
+# pylint: disable=R0903,R0912,R0913,R0914,R0915,R0917
+
+"""
+docdiff - Check differences between kernel‑doc output between two different
+commits.
+
+Examples
+--------
+
+Compare the kernel‑doc output between the last two 5.15 releases::
+
+ $ kdoc_diff v6.18..v6.19
+
+Both outputs are cached
+
+Force a complete documentation scan and clean any previous cache from
+6.19 to the current HEAD::
+
+ $ kdoc_diff 6.19.. --full --clean
+
+Check differences only on a single driver since origin/main::
+
+ $ kdoc_diff origin/main drivers/media
+
+Generate an YAML file and use it to check for regressions::
+
+ $ kdoc_diff HEAD~ drivers/media --regression
+
+
+"""
+
+import os
+import sys
+import argparse
+import subprocess
+import shutil
+import re
+import signal
+
+from glob import iglob
+
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+WORK_DIR = os.path.abspath(os.path.join(SRC_DIR, "../.."))
+
+KDOC_BINARY = os.path.join(SRC_DIR, "kernel-doc")
+KDOC_PARSER_TEST = os.path.join(WORK_DIR, "tools/unittests/test_kdoc_parser.py")
+
+CACHE_DIR = ".doc_diff_cache"
+YAML_NAME = "out.yaml"
+
+DIR_NAME = {
+ "full": os.path.join(CACHE_DIR, "full"),
+ "partial": os.path.join(CACHE_DIR, "partial"),
+ "no-cache": os.path.join(CACHE_DIR, "no_cache"),
+ "tmp": os.path.join(CACHE_DIR, "__tmp__"),
+}
+
+class GitHelper:
+ """Handles all Git operations"""
+
+ def __init__(self, work_dir=None):
+ self.work_dir = work_dir
+
+ def is_inside_repository(self):
+ """Check if we're inside a Git repository"""
+ try:
+ output = subprocess.check_output(["git", "rev-parse",
+ "--is-inside-work-tree"],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+
+ return output.strip() == "true"
+ except subprocess.CalledProcessError:
+ return False
+
+ def is_valid_commit(self, commit_hash):
+ """
+ Validate that a ref (branch, tag, commit hash, etc.) can be
+ resolved to a commit.
+ """
+ try:
+ subprocess.check_output(["git", "rev-parse", commit_hash],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+ return True
+ except subprocess.CalledProcessError:
+ return False
+
+ def get_short_hash(self, commit_hash):
+ """Get short commit hash"""
+ try:
+ return subprocess.check_output(["git", "rev-parse", "--short",
+ commit_hash],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True).strip()
+ except subprocess.CalledProcessError:
+ return ""
+
+ def has_uncommitted_changes(self):
+ """Check for uncommitted changes"""
+ try:
+ subprocess.check_output(["git", "diff-index",
+ "--quiet", "HEAD", "--"],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+ return False
+ except subprocess.CalledProcessError:
+ return True
+
+ def get_current_branch(self):
+ """Get current branch name"""
+ return subprocess.check_output(["git", "branch", "--show-current"],
+ cwd=self.work_dir,
+ universal_newlines=True).strip()
+
+ def checkout_commit(self, commit_hash, quiet=True):
+ """Checkout a commit safely"""
+ args = ["git", "checkout", "-f"]
+ if quiet:
+ args.append("-q")
+ args.append(commit_hash)
+ try:
+ subprocess.check_output(args, cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+
+ # Double-check if branch actually switched
+ branch = self.get_short_hash("HEAD")
+ if commit_hash != branch:
+ raise RuntimeError(f"Branch changed to '{branch}' instead of '{commit_hash}'")
+
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"ERROR: Failed to checkout {commit_hash}: {e}",
+ file=sys.stderr)
+ return False
+
+
+class CacheManager:
+ """Manages persistent cache directories"""
+
+ def __init__(self, work_dir):
+ self.work_dir = work_dir
+
+ def initialize(self):
+ """Create cache directories if they don't exist"""
+ for dir_path in DIR_NAME.values():
+ abs_path = os.path.join(self.work_dir, dir_path)
+ if not os.path.exists(abs_path):
+ os.makedirs(abs_path, exist_ok=True, mode=0o755)
+
+ def get_commit_cache(self, commit_hash, path):
+ """Generate cache path for a commit"""
+ hash_short = GitHelper(self.work_dir).get_short_hash(commit_hash)
+ if not hash_short:
+ hash_short = commit_hash
+
+ return os.path.join(path, hash_short)
+
+class KernelDocRunner:
+ """Runs kernel-doc documentation generator"""
+
+ def __init__(self, work_dir, kdoc_binary):
+ self.work_dir = work_dir
+ self.kdoc_binary = kdoc_binary
+ self.kdoc_files = None
+
+ def find_kdoc_references(self):
+ """Find all files marked with kernel-doc:: directives"""
+ if self.kdoc_files:
+ print("Using cached Kdoc refs")
+ return self.kdoc_files
+
+ print("Finding kernel-doc entries in Documentation...")
+
+ files = os.path.join(self.work_dir, 'Documentation/**/*.rst')
+ pattern = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
+ kdoc_files = set()
+
+ for file_path in iglob(files, recursive=True):
+ try:
+ with open(file_path, 'r', encoding='utf-8') as fp:
+ for line in fp:
+ match = pattern.match(line.strip())
+ if match:
+ kdoc_files.add(match.group(1))
+
+ except OSError:
+ continue
+
+ self.kdoc_files = list(kdoc_files)
+
+ return self.kdoc_files
+
+ def gen_yaml(self, yaml_file, kdoc_files):
+ """Runs kernel-doc to generate a yaml file with man and rst."""
+ cmd = [self.kdoc_binary, "--man", "--rst", "--yaml", yaml_file]
+ cmd += kdoc_files
+
+ print(f"YAML regression test file will be stored at: {yaml_file}")
+
+ try:
+ subprocess.check_call(cmd, cwd=self.work_dir,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ return False
+
+ return True
+
+ def run_unittest(self, yaml_file):
+ """Run unit tests with the generated yaml file"""
+ cmd = [KDOC_PARSER_TEST, "-q", "--yaml", yaml_file]
+ result = subprocess.run(cmd, cwd=self.work_dir)
+
+ if result.returncode:
+ print("To check for problems, try to run it again with -v\n")
+ print("Use -k <regex> to filter results\n\n\t$", end="")
+ print(" ".join(cmd) + "\n")
+
+ return True
+
+ def normal_run(self, tmp_dir, output_dir, kdoc_files):
+ """Generate man, rst and errors, storing them at tmp_dir."""
+ os.makedirs(tmp_dir, exist_ok=True)
+
+ try:
+ with open(os.path.join(tmp_dir, "man.log"), "w", encoding="utf-8") as out:
+ subprocess.check_call([self.kdoc_binary, "--man"] + kdoc_files,
+ cwd=self.work_dir,
+ stdout=out, stderr=subprocess.DEVNULL)
+
+ with open(os.path.join(tmp_dir, "rst.log"), "w", encoding="utf-8") as out:
+ with open(os.path.join(tmp_dir, "err.log"), "w", encoding="utf-8") as err:
+ subprocess.check_call([self.kdoc_binary, "--rst"] + kdoc_files,
+ cwd=self.work_dir,
+ stdout=out, stderr=err)
+ except subprocess.CalledProcessError:
+ return False
+
+ if output_dir:
+ os.replace(tmp_dir, output_dir)
+
+ return True
+
+ def run(self, commit_hash, tmp_dir, output_dir, kdoc_files, is_regression,
+ is_end):
+ """Run kernel-doc on its several ways"""
+ if not kdoc_files:
+ raise RuntimeError("No kernel-doc references found")
+
+ git_helper = GitHelper(self.work_dir)
+ if not git_helper.checkout_commit(commit_hash, quiet=True):
+ raise RuntimeError(f"ERROR: can't checkout commit {commit_hash}")
+
+ print(f"Processing {commit_hash}...")
+
+ if not is_regression:
+ return self.normal_run(tmp_dir, output_dir, kdoc_files)
+
+ yaml_file = os.path.join(tmp_dir, YAML_NAME)
+
+ if not is_end:
+ return self.gen_yaml(yaml_file, kdoc_files)
+
+ return self.run_unittest(yaml_file)
+
+class DiffManager:
+ """Compare documentation output directories with an external diff."""
+ def __init__(self, diff_tool="diff", diff_args=None):
+ self.diff_tool = diff_tool
+ # default: unified, no context, ignore whitespace changes
+ self.diff_args = diff_args or ["-u0", "-w"]
+
+ def diff_directories(self, dir1, dir2):
+ """Compare two directories using an external diff."""
+ print(f"\nDiffing {dir1} and {dir2}:")
+
+ dir1_files = set()
+ dir2_files = set()
+ has_diff = False
+
+ for root, _, files in os.walk(dir1):
+ for file in files:
+ dir1_files.add(os.path.relpath(os.path.join(root, file), dir1))
+ for root, _, files in os.walk(dir2):
+ for file in files:
+ dir2_files.add(os.path.relpath(os.path.join(root, file), dir2))
+
+ common_files = sorted(dir1_files & dir2_files)
+ for file in common_files:
+ f1 = os.path.join(dir1, file)
+ f2 = os.path.join(dir2, file)
+
+ cmd = [self.diff_tool] + self.diff_args + [f1, f2]
+ try:
+ result = subprocess.run(
+ cmd, capture_output=True, text=True, check=False
+ )
+ if result.stdout:
+ has_diff = True
+ print(f"\n{file}")
+ print(result.stdout, end="")
+ except FileNotFoundError:
+ print(f"ERROR: {self.diff_tool} not found")
+ sys.exit(1)
+
+ # Show files that exist only in one directory
+ only_in_dir1 = dir1_files - dir2_files
+ only_in_dir2 = dir2_files - dir1_files
+ if only_in_dir1 or only_in_dir2:
+ has_diff = True
+ print("\nDifferential files:")
+ for f in sorted(only_in_dir1):
+ print(f" - {f} (only in {dir1})")
+ for f in sorted(only_in_dir2):
+ print(f" + {f} (only in {dir2})")
+
+ if not has_diff:
+ print("\nNo differences between those two commits")
+
+
+class SignalHandler():
+ """Signal handler class."""
+
+ def restore(self, force_exit=False):
+ """Restore original HEAD state."""
+ if self.restored:
+ return
+
+ print(f"Restoring original branch: {self.original_head}")
+ try:
+ subprocess.check_call(
+ ["git", "checkout", "-f", self.original_head],
+ cwd=self.git_helper.work_dir,
+ stderr=subprocess.STDOUT,
+ )
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to restore: {e}", file=sys.stderr)
+
+ for sig, handler in self.old_handler.items():
+ signal.signal(sig, handler)
+
+ self.restored = True
+
+ if force_exit:
+ sys.exit(1)
+
+ def signal_handler(self, sig, _):
+ """Handle interrupt signals."""
+ print(f"\nSignal {sig} received. Restoring original state...")
+
+ self.restore(force_exit=True)
+
+ def __enter__(self):
+ """Allow using it via with command."""
+ for sig in [signal.SIGINT, signal.SIGTERM]:
+ self.old_handler[sig] = signal.getsignal(sig)
+ signal.signal(sig, self.signal_handler)
+
+ return self
+
+ def __exit__(self, *args):
+ """Restore signals at the end of with block."""
+ self.restore()
+
+ def __init__(self, git_helper, original_head):
+ self.git_helper = git_helper
+ self.original_head = original_head
+ self.old_handler = {}
+ self.restored = False
+
+def parse_commit_range(value):
+ """Handle a commit range."""
+ if ".." not in value:
+ begin = value
+ end = "HEAD"
+ else:
+ begin, _, end = value.partition("..")
+ if not end:
+ end = "HEAD"
+
+ if not begin:
+ raise argparse.ArgumentTypeError("Need a commit begginning")
+
+
+ print(f"Range: {begin} to {end}")
+
+ return begin, end
+
+
+def main():
+ """Main code"""
+ parser = argparse.ArgumentParser(description="Compare kernel documentation between commits")
+ parser.add_argument("commits", type=parse_commit_range,
+ help="commit range like old..new")
+ parser.add_argument("files", nargs="*",
+ help="files to process – if supplied the --full flag is ignored")
+
+ parser.add_argument("--full", "-f", action="store_true",
+ help="Force a full scan of Documentation/*")
+
+ parser.add_argument("--regression", "-r", action="store_true",
+ help="Use YAML format to check for regressions")
+
+ parser.add_argument("--work-dir", "-w", default=WORK_DIR,
+ help="work dir (default: %(default)s)")
+
+ parser.add_argument("--clean", "-c", action="store_true",
+ help="Clean caches")
+
+ args = parser.parse_args()
+
+ if args.files and args.full:
+ raise argparse.ArgumentError(args.full,
+ "cannot combine '--full' with an explicit file list")
+
+ work_dir = os.path.abspath(args.work_dir)
+
+ # Initialize cache
+ cache = CacheManager(work_dir)
+ cache.initialize()
+
+ # Validate git repository
+ git_helper = GitHelper(work_dir)
+ if not git_helper.is_inside_repository():
+ raise RuntimeError("Must run inside Git repository")
+
+ old_commit, new_commit = args.commits
+
+ old_commit = git_helper.get_short_hash(old_commit)
+ new_commit = git_helper.get_short_hash(new_commit)
+
+ # Validate commits
+ for commit in [old_commit, new_commit]:
+ if not git_helper.is_valid_commit(commit):
+ raise RuntimeError(f"Commit '{commit}' does not exist")
+
+ # Check for uncommitted changes
+ if git_helper.has_uncommitted_changes():
+ raise RuntimeError("Uncommitted changes present. Commit or stash first.")
+
+ runner = KernelDocRunner(git_helper.work_dir, KDOC_BINARY)
+
+ # Get files to be parsed
+ cache_msg = " (results will be cached)"
+ if args.full:
+ kdoc_files = ["."]
+ diff_type = "full"
+ print(f"Parsing all files at {work_dir}")
+ if not args.files:
+ diff_type = "partial"
+ kdoc_files = runner.find_kdoc_references()
+ print(f"Parsing files with kernel-doc markups at {work_dir}/Documentation")
+ else:
+ diff_type = "no-cache"
+ cache_msg = ""
+ kdoc_files = args.files
+
+ tmp_dir = DIR_NAME["tmp"]
+ out_path = DIR_NAME[diff_type]
+
+ if not args.regression:
+ print(f"Output will be stored at: {out_path}{cache_msg}")
+
+ # Just in case - should never happen in practice
+ if not kdoc_files:
+ raise argparse.ArgumentError(args.files,
+ "No kernel-doc references found")
+
+ original_head = git_helper.get_current_branch()
+
+ old_cache = cache.get_commit_cache(old_commit, out_path)
+ new_cache = cache.get_commit_cache(new_commit, out_path)
+
+ with SignalHandler(git_helper, original_head):
+ if args.clean or diff_type == "no-cache":
+ for cache_dir in [old_cache, new_cache]:
+ if cache_dir and os.path.exists(cache_dir):
+ shutil.rmtree(cache_dir)
+
+ if args.regression or not os.path.exists(old_cache):
+ old_success = runner.run(old_commit, tmp_dir, old_cache, kdoc_files,
+ args.regression, False)
+ else:
+ old_success = True
+
+ if args.regression or not os.path.exists(new_cache):
+ new_success = runner.run(new_commit, tmp_dir, new_cache, kdoc_files,
+ args.regression, True)
+ else:
+ new_success = True
+
+ if not (old_success and new_success):
+ raise RuntimeError("Failed to generate documentation")
+
+ if not args.regression:
+ diff_manager = DiffManager()
+ diff_manager.diff_directories(old_cache, new_cache)
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/docs/kernel-doc b/tools/docs/kernel-doc
new file mode 100755
index 000000000000..d9192c3f1645
--- /dev/null
+++ b/tools/docs/kernel-doc
@@ -0,0 +1,397 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
+#
+# pylint: disable=C0103,R0912,R0914,R0915
+#
+# NOTE: While kernel-doc requires at least version 3.6 to run, the
+# command line should work with Python 3.2+ (tested with 3.4).
+# The rationale is that it shall fail gracefully during Kernel
+# compilation with older Kernel versions. Due to that:
+# - encoding line is needed here;
+# - f-strings cannot be used in this file.
+# - libraries that require newer versions can only be included
+# after the Python version has been checked.
+#
+# Converted from the kernel-doc script originally written in Perl
+# under GPLv2, copyrighted since 1998 by the following authors:
+#
+# Aditya Srivastava <yashsri421@gmail.com>
+# Akira Yokosawa <akiyks@gmail.com>
+# Alexander A. Klimov <grandmaster@al2klimov.de>
+# Alexander Lobakin <aleksander.lobakin@intel.com>
+# André Almeida <andrealmeid@igalia.com>
+# Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+# Anna-Maria Behnsen <anna-maria@linutronix.de>
+# Armin Kuster <akuster@mvista.com>
+# Bart Van Assche <bart.vanassche@sandisk.com>
+# Ben Hutchings <ben@decadent.org.uk>
+# Borislav Petkov <bbpetkov@yahoo.de>
+# Chen-Yu Tsai <wenst@chromium.org>
+# Coco Li <lixiaoyan@google.com>
+# Conchúr Navid <conchur@web.de>
+# Daniel Santos <daniel.santos@pobox.com>
+# Danilo Cesar Lemes de Paula <danilo.cesar@collabora.co.uk>
+# Dan Luedtke <mail@danrl.de>
+# Donald Hunter <donald.hunter@gmail.com>
+# Gabriel Krisman Bertazi <krisman@collabora.co.uk>
+# Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+# Harvey Harrison <harvey.harrison@gmail.com>
+# Horia Geanta <horia.geanta@freescale.com>
+# Ilya Dryomov <idryomov@gmail.com>
+# Jakub Kicinski <kuba@kernel.org>
+# Jani Nikula <jani.nikula@intel.com>
+# Jason Baron <jbaron@redhat.com>
+# Jason Gunthorpe <jgg@nvidia.com>
+# Jérémy Bobbio <lunar@debian.org>
+# Johannes Berg <johannes.berg@intel.com>
+# Johannes Weiner <hannes@cmpxchg.org>
+# Jonathan Cameron <Jonathan.Cameron@huawei.com>
+# Jonathan Corbet <corbet@lwn.net>
+# Jonathan Neuschäfer <j.neuschaefer@gmx.net>
+# Kamil Rytarowski <n54@gmx.com>
+# Kees Cook <kees@kernel.org>
+# Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+# Levin, Alexander (Sasha Levin) <alexander.levin@verizon.com>
+# Linus Torvalds <torvalds@linux-foundation.org>
+# Lucas De Marchi <lucas.demarchi@profusion.mobi>
+# Mark Rutland <mark.rutland@arm.com>
+# Markus Heiser <markus.heiser@darmarit.de>
+# Martin Waitz <tali@admingilde.org>
+# Masahiro Yamada <masahiroy@kernel.org>
+# Matthew Wilcox <willy@infradead.org>
+# Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
+# Michal Wajdeczko <michal.wajdeczko@intel.com>
+# Michael Zucchi
+# Mike Rapoport <rppt@linux.ibm.com>
+# Niklas Söderlund <niklas.soderlund@corigine.com>
+# Nishanth Menon <nm@ti.com>
+# Paolo Bonzini <pbonzini@redhat.com>
+# Pavan Kumar Linga <pavan.kumar.linga@intel.com>
+# Pavel Pisa <pisa@cmp.felk.cvut.cz>
+# Peter Maydell <peter.maydell@linaro.org>
+# Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+# Randy Dunlap <rdunlap@infradead.org>
+# Richard Kennedy <richard@rsk.demon.co.uk>
+# Rich Walker <rw@shadow.org.uk>
+# Rolf Eike Beer <eike-kernel@sf-tec.de>
+# Sakari Ailus <sakari.ailus@linux.intel.com>
+# Silvio Fricke <silvio.fricke@gmail.com>
+# Simon Huggins
+# Tim Waugh <twaugh@redhat.com>
+# Tomasz Warniełło <tomasz.warniello@gmail.com>
+# Utkarsh Tripathi <utripathi2002@gmail.com>
+# valdis.kletnieks@vt.edu <valdis.kletnieks@vt.edu>
+# Vegard Nossum <vegard.nossum@oracle.com>
+# Will Deacon <will.deacon@arm.com>
+# Yacine Belkadi <yacine.belkadi.1@gmail.com>
+# Yujie Liu <yujie.liu@intel.com>
+
+"""
+Print formatted kernel documentation to stdout.
+
+Read C language source or header FILEs, extract embedded
+documentation comments, and print formatted documentation
+to standard output.
+
+The documentation comments are identified by the ``/**``
+opening comment mark.
+
+See Documentation/doc-guide/kernel-doc.rst for the
+documentation comment syntax.
+"""
+
+import argparse
+import logging
+import os
+import sys
+
+# Import Python modules
+
+LIB_DIR = "../lib/python"
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+
+sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR))
+
+WERROR_RETURN_CODE = 3
+
+DESC = """
+Read C language source or header FILEs, extract embedded documentation comments,
+and print formatted documentation to standard output.
+
+The documentation comments are identified by the "/**" opening comment mark.
+
+See Documentation/doc-guide/kernel-doc.rst for the documentation comment syntax.
+"""
+
+EXPORT_FILE_DESC = """
+Specify an additional FILE in which to look for EXPORT_SYMBOL information.
+
+May be used multiple times.
+"""
+
+EXPORT_DESC = """
+Only output documentation for symbols that have been
+exported using EXPORT_SYMBOL() and related macros in any input
+FILE or -export-file FILE.
+"""
+
+INTERNAL_DESC = """
+Only output documentation for symbols that have NOT been
+exported using EXPORT_SYMBOL() and related macros in any input
+FILE or -export-file FILE.
+"""
+
+FUNCTION_DESC = """
+Only output documentation for the given function or DOC: section
+title. All other functions and DOC: sections are ignored.
+
+May be used multiple times.
+"""
+
+NOSYMBOL_DESC = """
+Exclude the specified symbol from the output documentation.
+
+May be used multiple times.
+"""
+
+FILES_DESC = """
+Header and C source files to be parsed.
+"""
+
+WARN_CONTENTS_BEFORE_SECTIONS_DESC = """
+Warn if there are contents before sections (deprecated).
+
+This option is kept just for backward-compatibility, but it does nothing,
+neither here nor at the original Perl script.
+"""
+
+EPILOG = """
+The return value is:
+
+- 0: success or Python version is not compatible with
+kernel-doc. If -Werror is not used, it will also
+return 0 if there are issues at kernel-doc markups;
+
+- 1: an abnormal condition happened;
+
+- 2: argparse issued an error;
+
+- 3: When -Werror is used, it means that one or more unfiltered parse
+ warnings happened.
+"""
+
+class MsgFormatter(logging.Formatter):
+ """
+ Helper class to capitalize errors and warnings, the same way
+ the venerable (now retired) kernel-doc.pl used to do.
+ """
+
+ def format(self, record):
+ record.levelname = record.levelname.capitalize()
+ return logging.Formatter.format(self, record)
+
+def main():
+ """
+ Main program.
+
+ """
+
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
+ description=DESC, epilog=EPILOG)
+
+ #
+ # Normal arguments
+ #
+ parser.add_argument("-v", "-verbose", "--verbose", action="store_true",
+ help="Verbose output, more warnings and other information.")
+
+ parser.add_argument("-d", "-debug", "--debug", action="store_true",
+ help="Enable debug messages")
+
+ parser.add_argument("-M", "-modulename", "--modulename",
+ help="Allow setting a module name at the output.")
+
+ parser.add_argument("-l", "-enable-lineno", "--enable_lineno",
+ action="store_true",
+ help="Enable line number output (only in ReST mode)")
+
+ #
+ # Arguments to control the warning behavior
+ #
+ parser.add_argument("-Wreturn", "--wreturn", action="store_true",
+ help="Warns about the lack of a return markup on functions.")
+
+ parser.add_argument("-Wshort-desc", "-Wshort-description", "--wshort-desc",
+ action="store_true",
+ help="Warns if initial short description is missing")
+
+ parser.add_argument("-Wcontents-before-sections",
+ "--wcontents-before-sections", action="store_true",
+ help=WARN_CONTENTS_BEFORE_SECTIONS_DESC)
+
+ parser.add_argument("-Wall", "--wall", action="store_true",
+ help="Enable all types of warnings")
+
+ parser.add_argument("-Werror", "--werror", action="store_true",
+ help="Treat warnings as errors.")
+
+ parser.add_argument("-export-file", "--export-file", action='append',
+ help=EXPORT_FILE_DESC)
+
+ #
+ # Output format
+ #
+ out_fmt = parser.add_argument_group("Output format selection (mutually exclusive)")
+
+ out_fmt.add_argument("-m", "-man", "--man", action="store_true",
+ help="Output troff manual page format.")
+ out_fmt.add_argument("-r", "-rst", "--rst", action="store_true",
+ help="Output reStructuredText format (default).")
+ out_fmt.add_argument("-N", "-none", "--none", action="store_true",
+ help="Do not output documentation, only warnings.")
+
+ out_fmt.add_argument("-y", "--yaml-file", "--yaml",
+ help="Stores kernel-doc output on a yaml file.")
+ out_fmt.add_argument("-k", "--kdoc-item", "--kdoc", action="store_true",
+ help="Store KdocItem inside yaml file. Ued together with --yaml.")
+
+
+ #
+ # Output selection mutually-exclusive group
+ #
+ sel_group = parser.add_argument_group("Output selection (mutually exclusive)")
+ sel_mut = sel_group.add_mutually_exclusive_group()
+
+ sel_mut.add_argument("-e", "-export", "--export", action='store_true',
+ help=EXPORT_DESC)
+
+ sel_mut.add_argument("-i", "-internal", "--internal", action='store_true',
+ help=INTERNAL_DESC)
+
+ sel_mut.add_argument("-s", "-function", "--symbol", action='append',
+ help=FUNCTION_DESC)
+
+ #
+ # Those are valid for all 3 types of filter
+ #
+ parser.add_argument("-n", "-nosymbol", "--nosymbol", action='append',
+ help=NOSYMBOL_DESC)
+
+ parser.add_argument("-D", "-no-doc-sections", "--no-doc-sections",
+ action='store_true', help="Don't output DOC sections")
+
+ parser.add_argument("files", metavar="FILE",
+ nargs="+", help=FILES_DESC)
+
+ args = parser.parse_args()
+
+ if args.wall:
+ args.wreturn = True
+ args.wshort_desc = True
+ args.wcontents_before_sections = True
+
+ logger = logging.getLogger()
+
+ if not args.debug:
+ logger.setLevel(logging.INFO)
+ else:
+ logger.setLevel(logging.DEBUG)
+
+ formatter = MsgFormatter('%(levelname)s: %(message)s')
+
+ handler = logging.StreamHandler()
+ handler.setFormatter(formatter)
+
+ logger.addHandler(handler)
+
+ python_ver = sys.version_info[:2]
+ if python_ver < (3,6):
+ #
+ # Depending on the Kernel configuration, kernel-doc --none is called at
+ # build time. As we don't want to break compilation due to the
+ # usage of an old Python version, return 0 here.
+ #
+ if args.none:
+ logger.error("Python 3.6 or later is required by kernel-doc. Skipping checks")
+ sys.exit(0)
+
+ sys.exit("Python 3.6 or later is required by kernel-doc. Aborting.")
+
+ if python_ver < (3,7):
+ logger.warning("Python 3.7 or later is required for correct results")
+
+ #
+ # Import kernel-doc libraries only after checking the Python version
+ #
+ from kdoc.kdoc_files import KernelFiles # pylint: disable=C0415
+ from kdoc.kdoc_output import RestFormat, ManFormat # pylint: disable=C0415
+
+ yaml_content = set()
+ if args.yaml_file:
+ out_style = None
+
+ if args.man:
+ yaml_content |= {"man"}
+
+ if args.rst:
+ yaml_content |= {"rst"}
+
+ if args.kdoc_item or not yaml_content:
+ yaml_content |= {"KdocItem"}
+
+ else:
+ n_outputs = 0
+
+ if args.man:
+ out_style = ManFormat(modulename=args.modulename)
+ n_outputs += 1
+
+ if args.none:
+ out_style = None
+ n_outputs += 1
+
+ if args.rst or n_outputs == 0:
+ n_outputs += 1
+ out_style = RestFormat()
+
+ if n_outputs > 1:
+ parser.error("Those arguments are muttually exclusive: --man, --rst, --none, except when generating a YAML file.")
+
+ elif not n_outputs:
+ out_style = RestFormat()
+
+ kfiles = KernelFiles(verbose=args.verbose,
+ yaml_file=args.yaml_file, yaml_content=yaml_content,
+ out_style=out_style, werror=args.werror,
+ wreturn=args.wreturn, wshort_desc=args.wshort_desc,
+ wcontents_before_sections=args.wcontents_before_sections)
+
+ kfiles.parse(args.files, export_file=args.export_file)
+
+ for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export,
+ internal=args.internal, symbol=args.symbol,
+ nosymbol=args.nosymbol, export_file=args.export_file,
+ no_doc_sections=args.no_doc_sections):
+ msg = t[1]
+ if msg:
+ print(msg)
+
+ error_count = kfiles.errors
+ if not erro