From ee13b3f35c7238eff323450599185a775d726462 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:12 +0800 Subject: scripts/kernel-doc.py: move KernelFiles class to a separate file The KernelFiles class is the main dispatcher which parses each source file. In preparation for letting kerneldoc Sphinx extension to import Python libraries, move regex ancillary classes to a separate file. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/80bc855e128a9ff0a11df5afe9ba71775dfc9a0f.1744106241.git.mchehab+huawei@kernel.org --- scripts/lib/kdoc/kdoc_files.py | 270 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100755 scripts/lib/kdoc/kdoc_files.py (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py new file mode 100755 index 000000000000..8bcdc7ead984 --- /dev/null +++ b/scripts/lib/kdoc/kdoc_files.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright(c) 2025: Mauro Carvalho Chehab . +# +# pylint: disable=R0903,R0913,R0914,R0917 + +# TODO: implement warning filtering + +""" +Parse lernel-doc tags on multiple kernel source files. +""" + +import argparse +import logging +import os +import re +import sys +from datetime import datetime + +from dateutil import tz + +from kdoc_parser import KernelDoc + + +class GlobSourceFiles: + """ + Parse C source code file names and directories via an Interactor. + """ + + def __init__(self, srctree=None, valid_extensions=None): + """ + Initialize valid extensions with a tuple. + + If not defined, assume default C extensions (.c and .h) + + It would be possible to use python's glob function, but it is + very slow, and it is not interactive. So, it would wait to read all + directories before actually do something. + + So, let's use our own implementation. + """ + + if not valid_extensions: + self.extensions = (".c", ".h") + else: + self.extensions = valid_extensions + + self.srctree = srctree + + def _parse_dir(self, dirname): + """Internal function to parse files recursively""" + + with os.scandir(dirname) as obj: + for entry in obj: + name = os.path.join(dirname, entry.name) + + if entry.is_dir(): + yield from self._parse_dir(name) + + if not entry.is_file(): + continue + + basename = os.path.basename(name) + + if not basename.endswith(self.extensions): + continue + + yield name + + def parse_files(self, file_list, file_not_found_cb): + """ + Define an interator to parse all source files from file_list, + handling directories if any + """ + + for fname in file_list: + if self.srctree: + f = os.path.join(self.srctree, fname) + else: + f = fname + + if os.path.isdir(f): + yield from self._parse_dir(f) + elif os.path.isfile(f): + yield f + elif file_not_found_cb: + file_not_found_cb(fname) + + +class KernelFiles(): + """ + Parse lernel-doc tags on multiple kernel source files. + """ + + def parse_file(self, fname): + """ + Parse a single Kernel source. + """ + + doc = KernelDoc(self.config, fname) + doc.run() + + return doc + + def process_export_file(self, fname): + """ + Parses EXPORT_SYMBOL* macros from a single Kernel source file. + """ + try: + with open(fname, "r", encoding="utf8", + errors="backslashreplace") as fp: + for line in fp: + KernelDoc.process_export(self.config.function_table, line) + + except IOError: + print(f"Error: Cannot open fname {fname}", fname=sys.stderr) + self.config.errors += 1 + + def file_not_found_cb(self, fname): + """ + Callback to warn if a file was not found. + """ + + self.config.log.error("Cannot find file %s", fname) + self.config.errors += 1 + + def __init__(self, files=None, verbose=False, out_style=None, + werror=False, wreturn=False, wshort_desc=False, + wcontents_before_sections=False, + logger=None, modulename=None, export_file=None): + """ + Initialize startup variables and parse all files + """ + + if not verbose: + verbose = bool(os.environ.get("KBUILD_VERBOSE", 0)) + + if not modulename: + modulename = "Kernel API" + + dt = datetime.now() + if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): + # use UTC TZ + to_zone = tz.gettz('UTC') + dt = dt.astimezone(to_zone) + + if not werror: + kcflags = os.environ.get("KCFLAGS", None) + if kcflags: + match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags) + if match: + werror = True + + # reading this variable is for backwards compat just in case + # someone was calling it with the variable from outside the + # kernel's build system + kdoc_werror = os.environ.get("KDOC_WERROR", None) + if kdoc_werror: + werror = kdoc_werror + + # Set global config data used on all files + self.config = argparse.Namespace + + self.config.verbose = verbose + self.config.werror = werror + self.config.wreturn = wreturn + self.config.wshort_desc = wshort_desc + self.config.wcontents_before_sections = wcontents_before_sections + self.config.modulename = modulename + + self.config.function_table = set() + self.config.source_map = {} + + if not logger: + self.config.log = logging.getLogger("kernel-doc") + else: + self.config.log = logger + + self.config.kernel_version = os.environ.get("KERNELVERSION", + "unknown kernel version'") + self.config.src_tree = os.environ.get("SRCTREE", None) + + self.out_style = out_style + self.export_file = export_file + + # Initialize internal variables + + self.config.errors = 0 + self.results = [] + + self.file_list = files + self.files = set() + + def parse(self): + """ + Parse all files + """ + + glob = GlobSourceFiles(srctree=self.config.src_tree) + + # Let's use a set here to avoid duplicating files + + for fname in glob.parse_files(self.file_list, self.file_not_found_cb): + if fname in self.files: + continue + + self.files.add(fname) + + res = self.parse_file(fname) + self.results.append((res.fname, res.entries)) + + if not self.files: + sys.exit(1) + + # If a list of export files was provided, parse EXPORT_SYMBOL* + # from the ones not already parsed + + if self.export_file: + files = self.files + + glob = GlobSourceFiles(srctree=self.config.src_tree) + + for fname in glob.parse_files(self.export_file, + self.file_not_found_cb): + if fname not in files: + files.add(fname) + + self.process_export_file(fname) + + def out_msg(self, fname, name, arg): + """ + Output messages from a file name using the output style filtering. + + If output type was not handled by the syler, return False. + """ + + # NOTE: we can add rules here to filter out unwanted parts, + # although OutputFormat.msg already does that. + + return self.out_style.msg(fname, name, arg) + + def msg(self, enable_lineno=False, export=False, internal=False, + symbol=None, nosymbol=None): + """ + Interacts over the kernel-doc results and output messages. + """ + + function_table = self.config.function_table + + if symbol: + for s in symbol: + function_table.add(s) + + # Output none mode: only warnings will be shown + if not self.out_style: + return + + self.out_style.set_config(self.config) + + self.out_style.set_filter(export, internal, symbol, nosymbol, + function_table, enable_lineno) + + for fname, arg_tuple in self.results: + for name, arg in arg_tuple: + if self.out_msg(fname, name, arg): + ln = arg.get("ln", 0) + dtype = arg.get('type', "") + + self.config.log.warning("%s:%d Can't handle %s", + fname, ln, dtype) -- cgit v1.2.3 From 4fa5e411379af1baabdff088196da977799fd46e Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:14 +0800 Subject: scripts/kernel-doc.py: convert message output to an interactor Instead of directly printing output messages, change kdoc classes to return an interactor with the output message, letting the actual display to happen at the command-line command. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/557304c8458f1fb4aa2e833f4bdaff953094ddcb.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 9 ++- scripts/lib/kdoc/kdoc_files.py | 15 +++- scripts/lib/kdoc/kdoc_output.py | 171 ++++++++++++++++++++-------------------- 3 files changed, 104 insertions(+), 91 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index abff78e9160f..63efec4b3f4b 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -283,9 +283,12 @@ def main(): kfiles.parse() - kfiles.msg(enable_lineno=args.enable_lineno, export=args.export, - internal=args.internal, symbol=args.symbol, - nosymbol=args.nosymbol) + for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export, + internal=args.internal, symbol=args.symbol, + nosymbol=args.nosymbol): + msg = t[1] + if msg: + print(msg) # Call main method diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 8bcdc7ead984..817ed98b2727 100755 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -229,9 +229,10 @@ class KernelFiles(): def out_msg(self, fname, name, arg): """ - Output messages from a file name using the output style filtering. + Return output messages from a file name using the output style + filtering. - If output type was not handled by the syler, return False. + If output type was not handled by the syler, return None. """ # NOTE: we can add rules here to filter out unwanted parts, @@ -242,7 +243,8 @@ class KernelFiles(): def msg(self, enable_lineno=False, export=False, internal=False, symbol=None, nosymbol=None): """ - Interacts over the kernel-doc results and output messages. + Interacts over the kernel-doc results and output messages, + returning kernel-doc markups on each interaction """ function_table = self.config.function_table @@ -261,10 +263,15 @@ class KernelFiles(): function_table, enable_lineno) for fname, arg_tuple in self.results: + msg = "" for name, arg in arg_tuple: - if self.out_msg(fname, name, arg): + msg += self.out_msg(fname, name, arg) + + if msg is None: ln = arg.get("ln", 0) dtype = arg.get('type', "") self.config.log.warning("%s:%d Can't handle %s", fname, ln, dtype) + if msg: + yield fname, msg diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 24e40b3e7d1d..fda07049ecf7 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -71,6 +71,8 @@ class OutputFormat: self.function_table = set() self.config = None + self.data = "" + def set_config(self, config): self.config = config @@ -157,37 +159,38 @@ class OutputFormat: return True def msg(self, fname, name, args): + self.data = "" dtype = args.get('type', "") if dtype == "doc": self.out_doc(fname, name, args) - return False + return self.data if not self.check_declaration(dtype, name): - return False + return self.data if dtype == "function": self.out_function(fname, name, args) - return False + return self.data if dtype == "enum": self.out_enum(fname, name, args) - return False + return self.data if dtype == "typedef": self.out_typedef(fname, name, args) - return False + return self.data if dtype in ["struct", "union"]: self.out_struct(fname, name, args) - return False + return self.data # Warn if some type requires an output logic self.config.log.warning("doesn't now how to output '%s' block", dtype) - return True + return None # Virtual methods to be overridden by inherited classes def out_doc(self, fname, name, args): @@ -248,7 +251,7 @@ class RestFormat(OutputFormat): """Outputs a line number""" if self.enable_lineno and ln: - print(f".. LINENO {ln}") + self.data += f".. LINENO {ln}\n" def output_highlight(self, args): input_text = args @@ -295,7 +298,7 @@ class RestFormat(OutputFormat): # Print the output with the line prefix for line in output.strip("\n").split("\n"): - print(self.lineprefix + line) + self.data += self.lineprefix + line + "\n" def out_section(self, args, out_reference=False): """ @@ -317,15 +320,15 @@ class RestFormat(OutputFormat): if not self.out_mode == self.OUTPUT_INCLUDE: if out_reference: - print(f".. _{section}:\n") + self.data += f".. _{section}:\n\n" if not self.symbol: - print(f'{self.lineprefix}**{section}**\n') + self.data += f'{self.lineprefix}**{section}**\n\n' self.print_lineno(section_start_lines.get(section, 0)) self.output_highlight(sections[section]) - print() - print() + self.data += "\n" + self.data += "\n" def out_doc(self, fname, name, args): if not self.check_doc(name): @@ -368,42 +371,42 @@ class RestFormat(OutputFormat): signature += ")" if args.get('typedef') or not args.get('functiontype'): - print(f".. c:macro:: {args['function']}\n") + self.data += f".. c:macro:: {args['function']}\n\n" if args.get('typedef'): self.print_lineno(ln) - print(" **Typedef**: ", end="") + self.data += " **Typedef**: " self.lineprefix = "" self.output_highlight(args.get('purpose', "")) - print("\n\n**Syntax**\n") - print(f" ``{signature}``\n") + self.data += "\n\n**Syntax**\n\n" + self.data += f" ``{signature}``\n\n" else: - print(f"``{signature}``\n") + self.data += f"``{signature}``\n\n" else: - print(f".. c:function:: {signature}\n") + self.data += f".. c:function:: {signature}\n\n" if not args.get('typedef'): self.print_lineno(ln) self.lineprefix = " " self.output_highlight(args.get('purpose', "")) - print() + self.data += "\n" # Put descriptive text into a container (HTML
) to help set # function prototypes apart self.lineprefix = " " if parameterlist: - print(".. container:: kernelindent\n") - print(f"{self.lineprefix}**Parameters**\n") + self.data += ".. container:: kernelindent\n\n" + self.data += f"{self.lineprefix}**Parameters**\n\n" for parameter in parameterlist: parameter_name = Re(r'\[.*').sub('', parameter) dtype = args['parametertypes'].get(parameter, "") if dtype: - print(f"{self.lineprefix}``{dtype}``") + self.data += f"{self.lineprefix}``{dtype}``\n" else: - print(f"{self.lineprefix}``{parameter}``") + self.data += f"{self.lineprefix}``{parameter}``\n" self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) @@ -412,9 +415,9 @@ class RestFormat(OutputFormat): parameterdescs[parameter_name] != KernelDoc.undescribed: self.output_highlight(parameterdescs[parameter_name]) - print() + self.data += "\n" else: - print(f"{self.lineprefix}*undescribed*\n") + self.data += f"{self.lineprefix}*undescribed*\n\n" self.lineprefix = " " self.out_section(args) @@ -428,26 +431,26 @@ class RestFormat(OutputFormat): parameterdescs = args.get('parameterdescs', {}) ln = args.get('ln', 0) - print(f"\n\n.. c:enum:: {name}\n") + self.data += f"\n\n.. c:enum:: {name}\n\n" self.print_lineno(ln) self.lineprefix = " " self.output_highlight(args.get('purpose', '')) - print() + self.data += "\n" - print(".. container:: kernelindent\n") + self.data += ".. container:: kernelindent\n\n" outer = self.lineprefix + " " self.lineprefix = outer + " " - print(f"{outer}**Constants**\n") + self.data += f"{outer}**Constants**\n\n" for parameter in parameterlist: - print(f"{outer}``{parameter}``") + self.data += f"{outer}``{parameter}``\n" if parameterdescs.get(parameter, '') != KernelDoc.undescribed: self.output_highlight(parameterdescs[parameter]) else: - print(f"{self.lineprefix}*undescribed*\n") - print() + self.data += f"{self.lineprefix}*undescribed*\n\n" + self.data += "\n" self.lineprefix = oldprefix self.out_section(args) @@ -458,14 +461,14 @@ class RestFormat(OutputFormat): name = args.get('typedef', '') ln = args.get('ln', 0) - print(f"\n\n.. c:type:: {name}\n") + self.data += f"\n\n.. c:type:: {name}\n\n" self.print_lineno(ln) self.lineprefix = " " self.output_highlight(args.get('purpose', '')) - print() + self.data += "\n" self.lineprefix = oldprefix self.out_section(args) @@ -482,7 +485,7 @@ class RestFormat(OutputFormat): parameterdescs = args.get('parameterdescs', {}) parameterdesc_start_lines = args.get('parameterdesc_start_lines', {}) - print(f"\n\n.. c:{dtype}:: {name}\n") + self.data += f"\n\n.. c:{dtype}:: {name}\n\n" self.print_lineno(ln) @@ -490,20 +493,20 @@ class RestFormat(OutputFormat): self.lineprefix += " " self.output_highlight(purpose) - print() + self.data += "\n" - print(".. container:: kernelindent\n") - print(f"{self.lineprefix}**Definition**::\n") + self.data += ".. container:: kernelindent\n\n" + self.data += f"{self.lineprefix}**Definition**::\n\n" self.lineprefix = self.lineprefix + " " declaration = declaration.replace("\t", self.lineprefix) - print(f"{self.lineprefix}{dtype} {name}" + ' {') - print(f"{declaration}{self.lineprefix}" + "};\n") + self.data += f"{self.lineprefix}{dtype} {name}" + ' {' + "\n" + self.data += f"{declaration}{self.lineprefix}" + "};\n\n" self.lineprefix = " " - print(f"{self.lineprefix}**Members**\n") + self.data += f"{self.lineprefix}**Members**\n\n" for parameter in parameterlist: if not parameter or parameter.startswith("#"): continue @@ -515,15 +518,15 @@ class RestFormat(OutputFormat): self.print_lineno(parameterdesc_start_lines.get(parameter_name, 0)) - print(f"{self.lineprefix}``{parameter}``") + self.data += f"{self.lineprefix}``{parameter}``\n" self.lineprefix = " " self.output_highlight(parameterdescs[parameter_name]) self.lineprefix = " " - print() + self.data += "\n" - print() + self.data += "\n" self.lineprefix = oldprefix self.out_section(args) @@ -576,19 +579,19 @@ class ManFormat(OutputFormat): line = Re(r"^\s*").sub("", line) if line and line[0] == ".": - print("\\&" + line) + self.data += "\\&" + line + "\n" else: - print(line) + self.data += line + "\n" def out_doc(self, fname, name, args): module = args.get('module') sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) - print(f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual" LINUX') + self.data += f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual" LINUX' + "\n" for section in sectionlist: - print(f'.SH "{section}"') + self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) def out_function(self, fname, name, args): @@ -599,16 +602,16 @@ class ManFormat(OutputFormat): sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) - print(f'.TH "{args['function']}" 9 "{args['function']}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX') + self.data += f'.TH "{args['function']}" 9 "{args['function']}" "{self.man_date}" "Kernel Hacker\'s Manual" LINUX' + "\n" - print(".SH NAME") - print(f"{args['function']} \\- {args['purpose']}") + self.data += ".SH NAME\n" + self.data += f"{args['function']} \\- {args['purpose']}\n" - print(".SH SYNOPSIS") + self.data += ".SH SYNOPSIS\n" if args.get('functiontype', ''): - print(f'.B "{args['functiontype']}" {args['function']}') + self.data += f'.B "{args['functiontype']}" {args['function']}' + "\n" else: - print(f'.B "{args['function']}') + self.data += f'.B "{args['function']}' + "\n" count = 0 parenth = "(" @@ -621,25 +624,25 @@ class ManFormat(OutputFormat): dtype = args['parametertypes'].get(parameter, "") if function_pointer.match(dtype): # Pointer-to-function - print(f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"') + self.data += f'".BI "{parenth}{function_pointer.group(1)}" " ") ({function_pointer.group(2)}){post}"' + "\n" else: dtype = Re(r'([^\*])$').sub(r'\1 ', dtype) - print(f'.BI "{parenth}{dtype}" "{post}"') + self.data += f'.BI "{parenth}{dtype}" "{post}"' + "\n" count += 1 parenth = "" if parameterlist: - print(".SH ARGUMENTS") + self.data += ".SH ARGUMENTS\n" for parameter in parameterlist: parameter_name = re.sub(r'\[.*', '', parameter) - print(f'.IP "{parameter}" 12') + self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name, "")) for section in sectionlist: - print(f'.SH "{section.upper()}"') + self.data += f'.SH "{section.upper()}"' + "\n" self.output_highlight(sections[section]) def out_enum(self, fname, name, args): @@ -649,33 +652,33 @@ class ManFormat(OutputFormat): sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) - print(f'.TH "{args['module']}" 9 "enum {args['enum']}" "{self.man_date}" "API Manual" LINUX') + self.data += f'.TH "{args['module']}" 9 "enum {args['enum']}" "{self.man_date}" "API Manual" LINUX' + "\n" - print(".SH NAME") - print(f"enum {args['enum']} \\- {args['purpose']}") + self.data += ".SH NAME\n" + self.data += f"enum {args['enum']} \\- {args['purpose']}\n" - print(".SH SYNOPSIS") - print(f"enum {args['enum']}" + " {") + self.data += ".SH SYNOPSIS\n" + self.data += f"enum {args['enum']}" + " {\n" count = 0 for parameter in parameterlist: - print(f'.br\n.BI " {parameter}"') + self.data += f'.br\n.BI " {parameter}"' + "\n" if count == len(parameterlist) - 1: - print("\n};") + self.data += "\n};\n" else: - print(", \n.br") + self.data += ", \n.br\n" count += 1 - print(".SH Constants") + self.data += ".SH Constants\n" for parameter in parameterlist: parameter_name = Re(r'\[.*').sub('', parameter) - print(f'.IP "{parameter}" 12') + self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(args['parameterdescs'].get(parameter_name, "")) for section in sectionlist: - print(f'.SH "{section}"') + self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections[section]) def out_typedef(self, fname, name, args): @@ -685,13 +688,13 @@ class ManFormat(OutputFormat): sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) - print(f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX') + self.data += f'.TH "{module}" 9 "{typedef}" "{self.man_date}" "API Manual" LINUX' + "\n" - print(".SH NAME") - print(f"typedef {typedef} \\- {purpose}") + self.data += ".SH NAME\n" + self.data += f"typedef {typedef} \\- {purpose}\n" for section in sectionlist: - print(f'.SH "{section}"') + self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) def out_struct(self, fname, name, args): @@ -705,20 +708,20 @@ class ManFormat(OutputFormat): sections = args.get('sections', {}) parameterdescs = args.get('parameterdescs', {}) - print(f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX') + self.data += f'.TH "{module}" 9 "{struct_type} {struct_name}" "{self.man_date}" "API Manual" LINUX' + "\n" - print(".SH NAME") - print(f"{struct_type} {struct_name} \\- {purpose}") + self.data += ".SH NAME\n" + self.data += f"{struct_type} {struct_name} \\- {purpose}\n" # Replace tabs with two spaces and handle newlines declaration = definition.replace("\t", " ") declaration = Re(r"\n").sub('"\n.br\n.BI "', declaration) - print(".SH SYNOPSIS") - print(f"{struct_type} {struct_name} " + "{" + "\n.br") - print(f'.BI "{declaration}\n' + "};\n.br\n") + self.data += ".SH SYNOPSIS\n" + self.data += f"{struct_type} {struct_name} " + "{" + "\n.br\n" + self.data += f'.BI "{declaration}\n' + "};\n.br\n\n" - print(".SH Members") + self.data += ".SH Members\n" for parameter in parameterlist: if parameter.startswith("#"): continue @@ -728,9 +731,9 @@ class ManFormat(OutputFormat): if parameterdescs.get(parameter_name) == KernelDoc.undescribed: continue - print(f'.IP "{parameter}" 12') + self.data += f'.IP "{parameter}" 12' + "\n" self.output_highlight(parameterdescs.get(parameter_name)) for section in sectionlist: - print(f'.SH "{section}"') + self.data += f'.SH "{section}"' + "\n" self.output_highlight(sections.get(section)) -- cgit v1.2.3 From 799b0d2a2a24f09f3800678f55ad48d8cc9c069d Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:15 +0800 Subject: scripts/kernel-doc.py: move file lists to the parser function Instead of setting file lists at __init__ time, move it to the actual parsing function. This allows adding more files to be parsed in real time, by calling parse function multiple times. With the new way, the export_files logic was rewritten to avoid parsing twice EXPORT_SYMBOL for partial matches. Please notice that, with this logic, it can still read the same file twice when export_file is used. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/ab10bc94050406ce6536d4944b5d718ecd70812f.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 7 +++---- scripts/lib/kdoc/kdoc_files.py | 37 +++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 24 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index 63efec4b3f4b..e258a9df7f78 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -274,14 +274,13 @@ def main(): else: out_style = RestFormat() - kfiles = KernelFiles(files=args.files, verbose=args.verbose, + kfiles = KernelFiles(verbose=args.verbose, out_style=out_style, werror=args.werror, wreturn=args.wreturn, wshort_desc=args.wshort_desc, wcontents_before_sections=args.wcontents_before_sections, - modulename=args.modulename, - export_file=args.export_file) + modulename=args.modulename) - kfiles.parse() + 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, diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 817ed98b2727..47dab46c89fe 100755 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -124,7 +124,7 @@ class KernelFiles(): self.config.log.error("Cannot find file %s", fname) self.config.errors += 1 - def __init__(self, files=None, verbose=False, out_style=None, + def __init__(self, verbose=False, out_style=None, werror=False, wreturn=False, wshort_desc=False, wcontents_before_sections=False, logger=None, modulename=None, export_file=None): @@ -181,51 +181,48 @@ class KernelFiles(): self.config.src_tree = os.environ.get("SRCTREE", None) self.out_style = out_style - self.export_file = export_file # Initialize internal variables self.config.errors = 0 self.results = [] - self.file_list = files self.files = set() + self.export_files = set() - def parse(self): + def parse(self, file_list, export_file=None): """ Parse all files """ glob = GlobSourceFiles(srctree=self.config.src_tree) - # Let's use a set here to avoid duplicating files + # Prevent parsing the same file twice to speedup parsing and + # avoid reporting errors multiple times - for fname in glob.parse_files(self.file_list, self.file_not_found_cb): + for fname in glob.parse_files(file_list, self.file_not_found_cb): if fname in self.files: continue - self.files.add(fname) - res = self.parse_file(fname) - self.results.append((res.fname, res.entries)) - if not self.files: - sys.exit(1) + self.results.append((res.fname, res.entries)) + self.files.add(fname) # If a list of export files was provided, parse EXPORT_SYMBOL* - # from the ones not already parsed + # from files that weren't fully parsed - if self.export_file: - files = self.files + if not export_file: + return - glob = GlobSourceFiles(srctree=self.config.src_tree) + self.export_files |= self.files - for fname in glob.parse_files(self.export_file, - self.file_not_found_cb): - if fname not in files: - files.add(fname) + glob = GlobSourceFiles(srctree=self.config.src_tree) - self.process_export_file(fname) + for fname in glob.parse_files(export_file, self.file_not_found_cb): + if fname not in self.export_files: + self.process_export_file(fname) + self.export_files.add(fname) def out_msg(self, fname, name, arg): """ -- cgit v1.2.3 From 0873e55433769210c0ba26227f0080dde408e15e Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:16 +0800 Subject: scripts/kernel-doc.py: implement support for -no-doc-sections The venerable kernel-doc Perl script has a number of options that aren't properly documented. Among them, there is -no-doc-sections, which is used by the Sphinx extension. Implement support for it. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/06b18a32142b44d5ba8b41ac64a76c02b03b4969.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 8 ++++++-- scripts/lib/kdoc/kdoc_files.py | 5 +++-- scripts/lib/kdoc/kdoc_output.py | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index e258a9df7f78..90aacd17499a 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -239,10 +239,13 @@ def main(): sel_mut.add_argument("-s", "-function", "--symbol", action='append', help=FUNCTION_DESC) - # This one is valid for all 3 types of filter + # 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 outputt DOC sections") + parser.add_argument("files", metavar="FILE", nargs="+", help=FILES_DESC) @@ -284,7 +287,8 @@ def main(): for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export, internal=args.internal, symbol=args.symbol, - nosymbol=args.nosymbol): + nosymbol=args.nosymbol, + no_doc_sections=args.no_doc_sections): msg = t[1] if msg: print(msg) diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 47dab46c89fe..4c04546a74fe 100755 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -238,7 +238,7 @@ class KernelFiles(): return self.out_style.msg(fname, name, arg) def msg(self, enable_lineno=False, export=False, internal=False, - symbol=None, nosymbol=None): + symbol=None, nosymbol=None, no_doc_sections=False): """ Interacts over the kernel-doc results and output messages, returning kernel-doc markups on each interaction @@ -257,7 +257,8 @@ class KernelFiles(): self.out_style.set_config(self.config) self.out_style.set_filter(export, internal, symbol, nosymbol, - function_table, enable_lineno) + function_table, enable_lineno, + no_doc_sections) for fname, arg_tuple in self.results: msg = "" diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index fda07049ecf7..a246d213523c 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -70,6 +70,7 @@ class OutputFormat: self.symbol = None self.function_table = set() self.config = None + self.no_doc_sections = False self.data = "" @@ -77,7 +78,7 @@ class OutputFormat: self.config = config def set_filter(self, export, internal, symbol, nosymbol, function_table, - enable_lineno): + enable_lineno, no_doc_sections): """ Initialize filter variables according with the requested mode. @@ -87,6 +88,7 @@ class OutputFormat: """ self.enable_lineno = enable_lineno + self.no_doc_sections = no_doc_sections if symbol: self.out_mode = self.OUTPUT_INCLUDE @@ -117,6 +119,9 @@ class OutputFormat: def check_doc(self, name): """Check if DOC should be output""" + if self.no_doc_sections: + return False + if self.out_mode == self.OUTPUT_ALL: return True -- cgit v1.2.3 From 43ecfe6bc2ae11f99fb4b812c014339c2d6a221a Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:25 +0800 Subject: scripts/kernel-doc.py: Set an output format for --none Now that warnings output is deferred to the output plugin, we need to have an output style for none as well. So, use the OutputFormat base class on such cases. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/caa1089e16f4609f792ff26731ad9e9c3a6f6b1d.1744106242.git.mchehab+huawei@kernel.org --- scripts/lib/kdoc/kdoc_files.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 4c04546a74fe..dd3dbe87520b 100755 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -20,6 +20,7 @@ from datetime import datetime from dateutil import tz from kdoc_parser import KernelDoc +from kdoc_output import OutputFormat class GlobSourceFiles: @@ -138,6 +139,9 @@ class KernelFiles(): if not modulename: modulename = "Kernel API" + if out_style is None: + out_style = OutputFormat() + dt = datetime.now() if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): # use UTC TZ -- cgit v1.2.3 From 485f6f7960c468d9e27665f61517dc5fc097ea98 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:26 +0800 Subject: scripts/kernel-doc.py: adjust some coding style issues Make pylint happier by adding some missing documentation and addressing a couple of pylint warnings. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/0f9d5473105e4c09c6c41e3db72cc63f1d4d55f9.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 12 +++++----- scripts/lib/kdoc/kdoc_files.py | 4 +--- scripts/lib/kdoc/kdoc_output.py | 50 ++++++++++++++++++++++++++++++++--------- scripts/lib/kdoc/kdoc_parser.py | 30 ++++++------------------- scripts/lib/kdoc/kdoc_re.py | 3 ++- 5 files changed, 57 insertions(+), 42 deletions(-) mode change 100755 => 100644 scripts/lib/kdoc/kdoc_files.py (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index 90aacd17499a..eca7e34f9d03 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright(c) 2025: Mauro Carvalho Chehab . # -# pylint: disable=C0103 +# pylint: disable=C0103,R0915 # # Converted from the kernel-doc script originally written in Perl # under GPLv2, copyrighted since 1998 by the following authors: @@ -165,6 +165,8 @@ neither here nor at the original Perl script. class MsgFormatter(logging.Formatter): + """Helper class to format warnings on a similar way to kernel-doc.pl""" + def format(self, record): record.levelname = record.levelname.capitalize() return logging.Formatter.format(self, record) @@ -241,7 +243,7 @@ def main(): # Those are valid for all 3 types of filter parser.add_argument("-n", "-nosymbol", "--nosymbol", action='append', - help=NOSYMBOL_DESC) + help=NOSYMBOL_DESC) parser.add_argument("-D", "-no-doc-sections", "--no-doc-sections", action='store_true', help="Don't outputt DOC sections") @@ -286,9 +288,9 @@ def main(): 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, - no_doc_sections=args.no_doc_sections): + internal=args.internal, symbol=args.symbol, + nosymbol=args.nosymbol, + no_doc_sections=args.no_doc_sections): msg = t[1] if msg: print(msg) diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py old mode 100755 new mode 100644 index dd3dbe87520b..e2221db7022a --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -4,8 +4,6 @@ # # pylint: disable=R0903,R0913,R0914,R0917 -# TODO: implement warning filtering - """ Parse lernel-doc tags on multiple kernel source files. """ @@ -128,7 +126,7 @@ class KernelFiles(): def __init__(self, verbose=False, out_style=None, werror=False, wreturn=False, wshort_desc=False, wcontents_before_sections=False, - logger=None, modulename=None, export_file=None): + logger=None, modulename=None): """ Initialize startup variables and parse all files """ diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 6582d1f64d1e..7f84bf12f1e1 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -2,9 +2,7 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright(c) 2025: Mauro Carvalho Chehab . # -# pylint: disable=C0301,R0911,R0912,R0913,R0914,R0915,R0917 - -# TODO: implement warning filtering +# pylint: disable=C0301,R0902,R0911,R0912,R0913,R0914,R0915,R0917 """ Implement output filters to print kernel-doc documentation. @@ -52,6 +50,11 @@ type_member_func = type_member + Re(r"\(\)", cache=False) class OutputFormat: + """ + Base class for OutputFormat. If used as-is, it means that only + warnings will be displayed. + """ + # output mode. OUTPUT_ALL = 0 # output all symbols and doc sections OUTPUT_INCLUDE = 1 # output only specified symbols @@ -75,6 +78,10 @@ class OutputFormat: self.data = "" def set_config(self, config): + """ + Setup global config variables used by both parser and output. + """ + self.config = config def set_filter(self, export, internal, symbol, nosymbol, function_table, @@ -117,6 +124,10 @@ class OutputFormat: return block def out_warnings(self, args): + """ + Output warnings for identifiers that will be displayed. + """ + warnings = args.get('warnings', []) for warning, log_msg in warnings: @@ -146,6 +157,11 @@ class OutputFormat: return False def check_declaration(self, dtype, name, args): + """ + Checks if a declaration should be output or not based on the + filtering criteria. + """ + if name in self.nosymbol: return False @@ -169,6 +185,10 @@ class OutputFormat: return False def msg(self, fname, name, args): + """ + Handles a single entry from kernel-doc parser + """ + self.data = "" dtype = args.get('type', "") @@ -203,24 +223,25 @@ class OutputFormat: return None # Virtual methods to be overridden by inherited classes + # At the base class, those do nothing. def out_doc(self, fname, name, args): - pass + """Outputs a DOC block""" def out_function(self, fname, name, args): - pass + """Outputs a function""" def out_enum(self, fname, name, args): - pass + """Outputs an enum""" def out_typedef(self, fname, name, args): - pass + """Outputs a typedef""" def out_struct(self, fname, name, args): - pass + """Outputs a struct""" class RestFormat(OutputFormat): - # """Consts and functions used by ReST output""" + """Consts and functions used by ReST output""" highlights = [ (type_constant, r"``\1``"), @@ -265,6 +286,11 @@ class RestFormat(OutputFormat): self.data += f".. LINENO {ln}\n" def output_highlight(self, args): + """ + Outputs a C symbol that may require being converted to ReST using + the self.highlights variable + """ + input_text = args output = "" in_literal = False @@ -579,6 +605,10 @@ class ManFormat(OutputFormat): self.man_date = dt.strftime("%B %Y") def output_highlight(self, block): + """ + Outputs a C symbol that may require being highlighted with + self.highlights variable using troff syntax + """ contents = self.highlight_block(block) @@ -601,7 +631,7 @@ class ManFormat(OutputFormat): sections = args.get('sections', {}) if not self.check_doc(name, args): - return + return self.data += f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual" LINUX' + "\n" diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 3698ef625367..dcb9515fc40b 100755 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -131,7 +131,7 @@ class KernelDoc: # Place all potential outputs into an array self.entries = [] - # TODO: rename to emit_message + # TODO: rename to emit_message after removal of kernel-doc.pl def emit_warning(self, ln, msg, warning=True): """Emit a message""" @@ -157,19 +157,6 @@ class KernelDoc: name = self.entry.section contents = self.entry.contents - # TODO: we can prevent dumping empty sections here with: - # - # if self.entry.contents.strip("\n"): - # if start_new: - # self.entry.section = self.section_default - # self.entry.contents = "" - # - # return - # - # But, as we want to be producing the same output of the - # venerable kernel-doc Perl tool, let's just output everything, - # at least for now - if type_param.match(name): name = type_param.group(1) @@ -205,7 +192,7 @@ class KernelDoc: self.entry.section = self.section_default self.entry.contents = "" - # TODO: rename it to store_declaration + # TODO: rename it to store_declaration after removal of kernel-doc.pl def output_declaration(self, dtype, name, **args): """ Stores the entry into an entry array. @@ -225,13 +212,13 @@ class KernelDoc: args["type"] = dtype args["warnings"] = self.entry.warnings - # TODO: use colletions.OrderedDict + # TODO: use colletions.OrderedDict to remove sectionlist sections = args.get('sections', {}) sectionlist = args.get('sectionlist', []) # Drop empty sections - # TODO: improve it to emit warnings + # TODO: improve empty sections logic to emit warnings for section in ["Description", "Return"]: if section in sectionlist: if not sections[section].rstrip(): @@ -636,7 +623,9 @@ class KernelDoc: # Replace macros # - # TODO: it is better to also move those to the NestedMatch logic, + # TODO: use NestedMatch for FOO($1, $2, ...) matches + # + # it is better to also move those to the NestedMatch logic, # to ensure that parenthesis will be properly matched. (Re(r'__ETHTOOL_DECLARE_LINK_MODE_MASK\s*\(([^\)]+)\)', re.S), r'DECLARE_BITMAP(\1, __ETHTOOL_LINK_MODE_MASK_NBITS)'), @@ -906,7 +895,6 @@ class KernelDoc: self.dump_struct(ln, prototype) return - # TODO: handle other types self.output_declaration(self.entry.decl_type, prototype, entry=self.entry) @@ -1680,10 +1668,6 @@ class KernelDoc: self.st_inline_name[self.inline_doc_state], line) - # TODO: not all states allow EXPORT_SYMBOL*, so this - # can be optimized later on to speedup parsing - self.process_export(self.config.function_table, line) - # Hand this line to the appropriate state handler if self.state == self.STATE_NORMAL: self.process_normal(ln, line) diff --git a/scripts/lib/kdoc/kdoc_re.py b/scripts/lib/kdoc/kdoc_re.py index 512b6521e79d..d28485ff94d6 100755 --- a/scripts/lib/kdoc/kdoc_re.py +++ b/scripts/lib/kdoc/kdoc_re.py @@ -131,7 +131,8 @@ class NestedMatch: will ignore the search string. """ - # TODO: + # TODO: make NestedMatch handle multiple match groups + # # Right now, regular expressions to match it are defined only up to # the start delimiter, e.g.: # -- cgit v1.2.3 From 2ab867a4941de2e9d7804e76ab002ad74c73b078 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:28 +0800 Subject: scripts/kernel-doc.py: move modulename to man class Only man output requires a modulename. Move its definition to the man class. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/583085e3885b0075d16ef9961b4f2ad870f30a55.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 6 +++--- scripts/lib/kdoc/kdoc_files.py | 6 +----- scripts/lib/kdoc/kdoc_output.py | 12 ++++++------ scripts/lib/kdoc/kdoc_parser.py | 9 +-------- 4 files changed, 11 insertions(+), 22 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index eca7e34f9d03..6a6bc81efd31 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -186,6 +186,7 @@ def main(): help="Enable debug messages") parser.add_argument("-M", "-modulename", "--modulename", + default="Kernel API", help="Allow setting a module name at the output.") parser.add_argument("-l", "-enable-lineno", "--enable_lineno", @@ -273,7 +274,7 @@ def main(): logger.addHandler(handler) if args.man: - out_style = ManFormat() + out_style = ManFormat(modulename=args.modulename) elif args.none: out_style = None else: @@ -282,8 +283,7 @@ def main(): kfiles = KernelFiles(verbose=args.verbose, out_style=out_style, werror=args.werror, wreturn=args.wreturn, wshort_desc=args.wshort_desc, - wcontents_before_sections=args.wcontents_before_sections, - modulename=args.modulename) + wcontents_before_sections=args.wcontents_before_sections) kfiles.parse(args.files, export_file=args.export_file) diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index e2221db7022a..5a6e92e34d05 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -126,7 +126,7 @@ class KernelFiles(): def __init__(self, verbose=False, out_style=None, werror=False, wreturn=False, wshort_desc=False, wcontents_before_sections=False, - logger=None, modulename=None): + logger=None): """ Initialize startup variables and parse all files """ @@ -134,9 +134,6 @@ class KernelFiles(): if not verbose: verbose = bool(os.environ.get("KBUILD_VERBOSE", 0)) - if not modulename: - modulename = "Kernel API" - if out_style is None: out_style = OutputFormat() @@ -168,7 +165,6 @@ class KernelFiles(): self.config.wreturn = wreturn self.config.wshort_desc = wshort_desc self.config.wcontents_before_sections = wcontents_before_sections - self.config.modulename = modulename self.config.function_table = set() self.config.source_map = {} diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index e0ed79e4d985..8be69245c0d0 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -586,7 +586,7 @@ class ManFormat(OutputFormat): ) blankline = "" - def __init__(self): + def __init__(self, modulename): """ Creates class variables. @@ -595,6 +595,7 @@ class ManFormat(OutputFormat): """ super().__init__() + self.modulename = modulename dt = datetime.now() if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): @@ -626,14 +627,13 @@ class ManFormat(OutputFormat): self.data += line + "\n" def out_doc(self, fname, name, args): - module = args.get('module') sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) if not self.check_doc(name, args): return - self.data += f'.TH "{module}" 9 "{module}" "{self.man_date}" "API Manual" LINUX' + "\n" + self.data += f'.TH "{self.modulename}" 9 "{self.modulename}" "{self.man_date}" "API Manual" LINUX' + "\n" for section in sectionlist: self.data += f'.SH "{section}"' + "\n" @@ -697,7 +697,7 @@ class ManFormat(OutputFormat): sectionlist = args.get('sectionlist', []) sections = args.get('sections', {}) - self.data += f'.TH "{args["module"]}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" + self.data += f'.TH "{self.modulename}" 9 "enum {args["enum"]}" "{self.man_date}" "API Manual" LINUX' + "\n" self.data += ".SH NAME\n" self.data += f"enum {args['enum']} \\- {args['purpose']}\n" @@ -727,7 +727,7 @@ class ManFormat(OutputFormat): self.output_highlight(sections[section]) def out_typedef(self, fname, name, args): - module = args.get('module') + module = self.modulename typedef = args.get('typedef') purpose = args.get('purpose') sectionlist = args.get('sectionlist', []) @@ -743,7 +743,7 @@ class ManFormat(OutputFormat): self.output_highlight(sections.get(section)) def out_struct(self, fname, name, args): - module = args.get('module') + module = self.modulename struct_type = args.get('type') struct_name = args.get('struct') purpose = args.get('purpose') diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index e48ed128ca04..f923600561f8 100755 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -791,7 +791,6 @@ class KernelDoc: self.output_declaration(decl_type, declaration_name, struct=declaration_name, - module=self.entry.modulename, definition=declaration, parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, @@ -869,7 +868,6 @@ class KernelDoc: self.output_declaration('enum', declaration_name, enum=declaration_name, - module=self.config.modulename, parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, parameterdesc_start_lines=self.entry.parameterdesc_start_lines, @@ -1040,7 +1038,6 @@ class KernelDoc: self.output_declaration(decl_type, declaration_name, function=declaration_name, typedef=True, - module=self.config.modulename, functiontype=return_type, parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, @@ -1055,7 +1052,6 @@ class KernelDoc: self.output_declaration(decl_type, declaration_name, function=declaration_name, typedef=False, - module=self.config.modulename, functiontype=return_type, parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, @@ -1102,7 +1098,6 @@ class KernelDoc: self.output_declaration(decl_type, declaration_name, function=declaration_name, typedef=True, - module=self.entry.modulename, functiontype=return_type, parameterlist=self.entry.parameterlist, parameterdescs=self.entry.parameterdescs, @@ -1130,7 +1125,6 @@ class KernelDoc: self.output_declaration('typedef', declaration_name, typedef=declaration_name, - module=self.entry.modulename, sectionlist=self.entry.sectionlist, sections=self.entry.sections, section_start_lines=self.entry.section_start_lines, @@ -1619,8 +1613,7 @@ class KernelDoc: self.output_declaration("doc", self.entry.identifier, sectionlist=self.entry.sectionlist, sections=self.entry.sections, - section_start_lines=self.entry.section_start_lines, - module=self.config.modulename) + section_start_lines=self.entry.section_start_lines) self.reset_state(ln) elif doc_content.search(line): -- cgit v1.2.3 From 91d00bd54f300b614d48002d4ec8cc28b3f0b2a5 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:29 +0800 Subject: scripts/kernel-doc.py: properly handle KBUILD_BUILD_TIMESTAMP The logic that handles KBUILD_BUILD_TIMESTAMP is wrong, and adds a dependency of a third party module (dateutil). Fix it. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/ffc70a1b741b010365ed82f31611018f24f91ce7.1744106242.git.mchehab+huawei@kernel.org --- scripts/lib/kdoc/kdoc_files.py | 9 --------- scripts/lib/kdoc/kdoc_output.py | 28 +++++++++++++++++++++------- 2 files changed, 21 insertions(+), 16 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 5a6e92e34d05..e52a6d05237e 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -13,9 +13,6 @@ import logging import os import re import sys -from datetime import datetime - -from dateutil import tz from kdoc_parser import KernelDoc from kdoc_output import OutputFormat @@ -137,12 +134,6 @@ class KernelFiles(): if out_style is None: out_style = OutputFormat() - dt = datetime.now() - if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): - # use UTC TZ - to_zone = tz.gettz('UTC') - dt = dt.astimezone(to_zone) - if not werror: kcflags = os.environ.get("KCFLAGS", None) if kcflags: diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index 8be69245c0d0..eb013075da84 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -19,8 +19,6 @@ import os import re from datetime import datetime -from dateutil import tz - from kdoc_parser import KernelDoc, type_param from kdoc_re import Re @@ -586,6 +584,15 @@ class ManFormat(OutputFormat): ) blankline = "" + date_formats = [ + "%a %b %d %H:%M:%S %Z %Y", + "%a %b %d %H:%M:%S %Y", + "%Y-%m-%d", + "%b %d %Y", + "%B %d %Y", + "%m %d %Y", + ] + def __init__(self, modulename): """ Creates class variables. @@ -597,11 +604,18 @@ class ManFormat(OutputFormat): super().__init__() self.modulename = modulename - dt = datetime.now() - if os.environ.get("KBUILD_BUILD_TIMESTAMP", None): - # use UTC TZ - to_zone = tz.gettz('UTC') - dt = dt.astimezone(to_zone) + dt = None + tstamp = os.environ.get("KBUILD_BUILD_TIMESTAMP") + if tstamp: + for fmt in self.date_formats: + try: + dt = datetime.strptime(tstamp, fmt) + break + except ValueError: + pass + + if not dt: + dt = datetime.now() self.man_date = dt.strftime("%B %Y") -- cgit v1.2.3 From 11afeab6d74d1be80420b47113c4893c88dcc04b Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:31 +0800 Subject: scripts/kernel-doc.py: Properly handle Werror and exit codes The original kernel-doc script has a logic to return warnings as errors, and to report the number of warnings found, if in verbose mode. Implement it to be fully compatible with the original script. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/de33b0cebd9fdf82d8b221bcfe41db7269286222.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 18 ++++++++++++++++-- scripts/lib/kdoc/kdoc_files.py | 12 ++++++++++-- scripts/lib/kdoc/kdoc_output.py | 8 +++----- scripts/lib/kdoc/kdoc_parser.py | 15 ++++++--------- 4 files changed, 35 insertions(+), 18 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index 6a6bc81efd31..2f2fad813024 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -78,8 +78,6 @@ # Yacine Belkadi # Yujie Liu -# TODO: implement warning filtering - """ kernel_doc ========== @@ -295,6 +293,22 @@ def main(): if msg: print(msg) + error_count = kfiles.errors + if not error_count: + sys.exit(0) + + if args.werror: + print(f"{error_count} warnings as errors") + sys.exit(error_count) + + if args.verbose: + print(f"{error_count} errors") + + if args.none: + sys.exit(0) + + sys.exit(error_count) + # Call main method if __name__ == "__main__": diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index e52a6d05237e..182d9ed58a72 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -12,7 +12,6 @@ import argparse import logging import os import re -import sys from kdoc_parser import KernelDoc from kdoc_output import OutputFormat @@ -109,7 +108,7 @@ class KernelFiles(): KernelDoc.process_export(self.config.function_table, line) except IOError: - print(f"Error: Cannot open fname {fname}", fname=sys.stderr) + self.config.log.error("Error: Cannot open fname %s", fname) self.config.errors += 1 def file_not_found_cb(self, fname): @@ -262,3 +261,12 @@ class KernelFiles(): fname, ln, dtype) if msg: yield fname, msg + + @property + def errors(self): + """ + Return a count of the number of warnings found, including + the ones displayed while interacting over self.msg. + """ + + return self.config.errors diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index eb013075da84..e9b4d0093084 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -128,11 +128,9 @@ class OutputFormat: warnings = args.get('warnings', []) - for warning, log_msg in warnings: - if warning: - self.config.log.warning(log_msg) - else: - self.config.log.info(log_msg) + for log_msg in warnings: + self.config.log.warning(log_msg) + self.config.errors += 1 def check_doc(self, name, args): """Check if DOC should be output""" diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 77e8bfeccc8e..43e6ffbdcc2c 100755 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -137,17 +137,18 @@ class KernelDoc: log_msg = f"{self.fname}:{ln} {msg}" + if not warning: + self.config.log.info(log_msg) + return + if self.entry: # Delegate warning output to output logic, as this way it # will report warnings/info only for symbols that are output - self.entry.warnings.append((warning, log_msg)) + self.entry.warnings.append(log_msg) return - if warning: - self.config.log.warning(log_msg) - else: - self.config.log.info(log_msg) + self.config.log.warning(log_msg) def dump_section(self, start_new=True): """ @@ -556,7 +557,6 @@ class KernelDoc: if not members: self.emit_warning(ln, f"{proto} error: Cannot parse struct or union!") - self.config.errors += 1 return if self.entry.identifier != declaration_name: @@ -831,7 +831,6 @@ class KernelDoc: if not members: self.emit_warning(ln, f"{proto}: error: Cannot parse enum!") - self.config.errors += 1 return if self.entry.identifier != declaration_name: @@ -1132,7 +1131,6 @@ class KernelDoc: return self.emit_warning(ln, "error: Cannot parse typedef!") - self.config.errors += 1 @staticmethod def process_export(function_table, line): @@ -1677,4 +1675,3 @@ class KernelDoc: self.process_docblock(ln, line) except OSError: self.config.log.error(f"Error: Cannot open file {self.fname}") - self.config.errors += 1 -- cgit v1.2.3 From a566ba5af59524a3bc5cdfb46b248bd70a0972e9 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:33 +0800 Subject: scripts/lib/kdoc/kdoc_files.py: allow filtering output per fname For kerneldoc Sphinx extension, it is useful to display parsed results only from a single file. Change the logic at KernelFiles.msg() to allow such usage. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/9f5c0ff2568f34532ca99465fb378241d831d39f.1744106242.git.mchehab+huawei@kernel.org --- scripts/lib/kdoc/kdoc_files.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 182d9ed58a72..527ab9117268 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -95,7 +95,7 @@ class KernelFiles(): doc = KernelDoc(self.config, fname) doc.run() - return doc + return doc.entries def process_export_file(self, fname): """ @@ -173,7 +173,7 @@ class KernelFiles(): # Initialize internal variables self.config.errors = 0 - self.results = [] + self.results = {} self.files = set() self.export_files = set() @@ -189,13 +189,9 @@ class KernelFiles(): # avoid reporting errors multiple times for fname in glob.parse_files(file_list, self.file_not_found_cb): - if fname in self.files: - continue - - res = self.parse_file(fname) - - self.results.append((res.fname, res.entries)) - self.files.add(fname) + if fname not in self.files: + self.results[fname] = self.parse_file(fname) + self.files.add(fname) # If a list of export files was provided, parse EXPORT_SYMBOL* # from files that weren't fully parsed @@ -226,7 +222,8 @@ class KernelFiles(): return self.out_style.msg(fname, name, arg) def msg(self, enable_lineno=False, export=False, internal=False, - symbol=None, nosymbol=None, no_doc_sections=False): + symbol=None, nosymbol=None, no_doc_sections=False, + filenames=None): """ Interacts over the kernel-doc results and output messages, returning kernel-doc markups on each interaction @@ -248,9 +245,12 @@ class KernelFiles(): function_table, enable_lineno, no_doc_sections) - for fname, arg_tuple in self.results: + if not filenames: + filenames = sorted(self.results.keys()) + + for fname in filenames: msg = "" - for name, arg in arg_tuple: + for name, arg in self.results[fname]: msg += self.out_msg(fname, name, arg) if msg is None: -- cgit v1.2.3 From 16740c29dbf3275a22691d3d7c63701992872898 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 8 Apr 2025 18:09:34 +0800 Subject: scripts/kernel_doc.py: better handle exported symbols Change the logic which detects internal/external symbols in a way that we can re-use it when calling via Sphinx extension. While here, remove an unused self.config var and let it clearer that self.config variables are read-only. This helps to allow handling multiple times in parallel if ever needed. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet Link: https://lore.kernel.org/r/6a69ba8d2b7ee6a6427abb53e60d09bd4d3565ee.1744106242.git.mchehab+huawei@kernel.org --- scripts/kernel-doc.py | 2 +- scripts/lib/kdoc/kdoc_files.py | 140 +++++++++++++++++++++------------------- scripts/lib/kdoc/kdoc_output.py | 9 +-- scripts/lib/kdoc/kdoc_parser.py | 52 +++++++++++++-- 4 files changed, 124 insertions(+), 79 deletions(-) (limited to 'scripts/lib/kdoc/kdoc_files.py') diff --git a/scripts/kernel-doc.py b/scripts/kernel-doc.py index 2f2fad813024..12ae66f40bd7 100755 --- a/scripts/kernel-doc.py +++ b/scripts/kernel-doc.py @@ -287,7 +287,7 @@ def main(): for t in kfiles.msg(enable_lineno=args.enable_lineno, export=args.export, internal=args.internal, symbol=args.symbol, - nosymbol=args.nosymbol, + nosymbol=args.nosymbol, export_file=args.export_file, no_doc_sections=args.no_doc_sections): msg = t[1] if msg: diff --git a/scripts/lib/kdoc/kdoc_files.py b/scripts/lib/kdoc/kdoc_files.py index 527ab9117268..dd003feefd1b 100644 --- a/scripts/lib/kdoc/kdoc_files.py +++ b/scripts/lib/kdoc/kdoc_files.py @@ -68,6 +68,9 @@ class GlobSourceFiles: handling directories if any """ + if not file_list: + return + for fname in file_list: if self.srctree: f = os.path.join(self.srctree, fname) @@ -84,40 +87,70 @@ class GlobSourceFiles: class KernelFiles(): """ - Parse lernel-doc tags on multiple kernel source files. + Parse kernel-doc tags on multiple kernel source files. + + There are two type of parsers defined here: + - self.parse_file(): parses both kernel-doc markups and + EXPORT_SYMBOL* macros; + - self.process_export_file(): parses only EXPORT_SYMBOL* macros. """ + def warning(self, msg): + """Ancillary routine to output a warning and increment error count""" + + self.config.log.warning(msg) + self.errors += 1 + + def error(self, msg): + """Ancillary routine to output an error and increment error count""" + + self.config.log.error(msg) + self.errors += 1 + def parse_file(self, fname): """ Parse a single Kernel source. """ + # Prevent parsing the same file twice if results are cached + if fname in self.files: + return + doc = KernelDoc(self.config, fname) - doc.run() + export_table, entries = doc.parse_kdoc() + + self.export_table[fname] = export_table + + self.files.add(fname) + self.export_files.add(fname) # parse_kdoc() already check exports - return doc.entries + self.results[fname] = entries def process_export_file(self, fname): """ Parses EXPORT_SYMBOL* macros from a single Kernel source file. """ - try: - with open(fname, "r", encoding="utf8", - errors="backslashreplace") as fp: - for line in fp: - KernelDoc.process_export(self.config.function_table, line) - except IOError: - self.config.log.error("Error: Cannot open fname %s", fname) - self.config.errors += 1 + # Prevent parsing the same file twice if results are cached + if fname in self.export_files: + return + + doc = KernelDoc(self.config, fname) + export_table = doc.parse_export() + + if not export_table: + self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}") + export_table = set() + + self.export_table[fname] = export_table + self.export_files.add(fname) def file_not_found_cb(self, fname): """ Callback to warn if a file was not found. """ - self.config.log.error("Cannot find file %s", fname) - self.config.errors += 1 + self.error(f"Cannot find file {fname}") def __init__(self, verbose=False, out_style=None, werror=False, wreturn=False, wshort_desc=False, @@ -147,7 +180,9 @@ class KernelFiles(): if kdoc_werror: werror = kdoc_werror - # Set global config data used on all files + # Some variables are global to the parser logic as a whole as they are + # used to send control configuration to KernelDoc class. As such, + # those variables are read-only inside the KernelDoc. self.config = argparse.Namespace self.config.verbose = verbose @@ -156,27 +191,25 @@ class KernelFiles(): self.config.wshort_desc = wshort_desc self.config.wcontents_before_sections = wcontents_before_sections - self.config.function_table = set() - self.config.source_map = {} - if not logger: self.config.log = logging.getLogger("kernel-doc") else: self.config.log = logger - self.config.kernel_version = os.environ.get("KERNELVERSION", - "unknown kernel version'") + self.config.warning = self.warning + self.config.src_tree = os.environ.get("SRCTREE", None) - self.out_style = out_style + # Initialize variables that are internal to KernelFiles - # Initialize internal variables + self.out_style = out_style - self.config.errors = 0 + self.errors = 0 self.results = {} self.files = set() self.export_files = set() + self.export_table = {} def parse(self, file_list, export_file=None): """ @@ -185,28 +218,11 @@ class KernelFiles(): glob = GlobSourceFiles(srctree=self.config.src_tree) - # Prevent parsing the same file twice to speedup parsing and - # avoid reporting errors multiple times - for fname in glob.parse_files(file_list, self.file_not_found_cb): - if fname not in self.files: - self.results[fname] = self.parse_file(fname) - self.files.add(fname) - - # If a list of export files was provided, parse EXPORT_SYMBOL* - # from files that weren't fully parsed - - if not export_file: - return - - self.export_files |= self.files - - glob = GlobSourceFiles(srctree=self.config.src_tree) + self.parse_file(fname) for fname in glob.parse_files(export_file, self.file_not_found_cb): - if fname not in self.export_files: - self.process_export_file(fname) - self.export_files.add(fname) + self.process_export_file(fname) def out_msg(self, fname, name, arg): """ @@ -223,32 +239,35 @@ class KernelFiles(): def msg(self, enable_lineno=False, export=False, internal=False, symbol=None, nosymbol=None, no_doc_sections=False, - filenames=None): + filenames=None, export_file=None): """ Interacts over the kernel-doc results and output messages, returning kernel-doc markups on each interaction """ - function_table = self.config.function_table - - if symbol: - for s in symbol: - function_table.add(s) - - # Output none mode: only warnings will be shown - if not self.out_style: - return - self.out_style.set_config(self.config) - self.out_style.set_filter(export, internal, symbol, nosymbol, - function_table, enable_lineno, - no_doc_sections) - if not filenames: filenames = sorted(self.results.keys()) for fname in filenames: + function_table = set() + + if internal or export: + if not export_file: + export_file = [fname] + + for f in export_file: + function_table |= self.export_table[f] + + if symbol: + for s in symbol: + function_table.add(s) + + self.out_style.set_filter(export, internal, symbol, nosymbol, + function_table, enable_lineno, + no_doc_sections) + msg = "" for name, arg in self.results[fname]: msg += self.out_msg(fname, name, arg) @@ -261,12 +280,3 @@ class KernelFiles(): fname, ln, dtype) if msg: yield fname, msg - - @property - def errors(self): - """ - Return a count of the number of warnings found, including - the ones displayed while interacting over self.msg. - """ - - return self.config.errors diff --git a/scripts/lib/kdoc/kdoc_output.py b/scripts/lib/kdoc/kdoc_output.py index e9b4d0093084..c352b7f8d3fd 100755 --- a/scripts/lib/kdoc/kdoc_output.py +++ b/scripts/lib/kdoc/kdoc_output.py @@ -69,7 +69,7 @@ class OutputFormat: self.enable_lineno = None self.nosymbol = {} self.symbol = None - self.function_table = set() + self.function_table = None self.config = None self.no_doc_sections = False @@ -94,10 +94,10 @@ class OutputFormat: self.enable_lineno = enable_lineno self.no_doc_sections = no_doc_sections + self.function_table = function_table if symbol: self.out_mode = self.OUTPUT_INCLUDE - function_table = symbol elif export: self.out_mode = self.OUTPUT_EXPORTED elif internal: @@ -108,8 +108,6 @@ class OutputFormat: if nosymbol: self.nosymbol = set(nosymbol) - if function_table: - self.function_table = function_table def highlight_block(self, block): """ @@ -129,8 +127,7 @@ class OutputFormat: warnings = args.get('warnings', []) for log_msg in warnings: - self.config.log.warning(log_msg) - self.config.errors += 1 + self.config.warning(log_msg) def check_doc(self, name, args): """Check if DOC should be output""" diff --git a/scripts/lib/kdoc/kdoc_parser.py b/scripts/lib/kdoc/kdoc_parser.py index 43e6ffbdcc2c..33f00c77dd5f 100755 --- a/scripts/lib/kdoc/kdoc_parser.py +++ b/scripts/lib/kdoc/kdoc_parser.py @@ -1133,21 +1133,25 @@ class KernelDoc: self.emit_warning(ln, "error: Cannot parse typedef!") @staticmethod - def process_export(function_table, line): + def process_export(function_set, line): """ process EXPORT_SYMBOL* tags - This method is called both internally and externally, so, it - doesn't use self. + This method doesn't use any variable from the class, so declare it + with a staticmethod decorator. """ + # Note: it accepts only one EXPORT_SYMBOL* per line, as having + # multiple export lines would violate Kernel coding style. + if export_symbol.search(line): symbol = export_symbol.group(2) - function_table.add(symbol) + function_set.add(symbol) + return if export_symbol_ns.search(line): symbol = export_symbol_ns.group(2) - function_table.add(symbol) + function_set.add(symbol) def process_normal(self, ln, line): """ @@ -1617,17 +1621,39 @@ class KernelDoc: elif doc_content.search(line): self.entry.contents += doc_content.group(1) + "\n" - def run(self): + def parse_export(self): + """ + Parses EXPORT_SYMBOL* macros from a single Kernel source file. + """ + + export_table = set() + + try: + with open(self.fname, "r", encoding="utf8", + errors="backslashreplace") as fp: + + for line in fp: + self.process_export(export_table, line) + + except IOError: + return None + + return export_table + + def parse_kdoc(self): """ Open and process each line of a C source file. - he parsing is controlled via a state machine, and the line is passed + The parsing is controlled via a state machine, and the line is passed to a different process function depending on the state. The process function may update the state as needed. + + Besides parsing kernel-doc tags, it also parses export symbols. """ cont = False prev = "" prev_ln = None + export_table = set() try: with open(self.fname, "r", encoding="utf8", @@ -1659,6 +1685,16 @@ class KernelDoc: self.st_inline_name[self.inline_doc_state], line) + # This is an optimization over the original script. + # There, when export_file was used for the same file, + # it was read twice. Here, we use the already-existing + # loop to parse exported symbols as well. + # + # TODO: It should be noticed that not all states are + # needed here. On a future cleanup, process export only + # at the states that aren't handling comment markups. + self.process_export(export_table, line) + # Hand this line to the appropriate state handler if self.state == self.STATE_NORMAL: self.process_normal(ln, line) @@ -1675,3 +1711,5 @@ class KernelDoc: self.process_docblock(ln, line) except OSError: self.config.log.error(f"Error: Cannot open file {self.fname}") + + return export_table, self.entries -- cgit v1.2.3