mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 08:38:45 +02:00 
			
		
		
		
	 703f9074a8
			
		
	
	
		703f9074a8
		
	
	
	
	
		
			
			Since our output items contain their name, we don't need to pass it separately. Reviewed-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> Signed-off-by: Jonathan Corbet <corbet@lwn.net>
		
			
				
	
	
		
			291 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			291 lines
		
	
	
	
		
			8.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python3
 | |
| # SPDX-License-Identifier: GPL-2.0
 | |
| # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
 | |
| #
 | |
| # pylint: disable=R0903,R0913,R0914,R0917
 | |
| 
 | |
| """
 | |
| Parse lernel-doc tags on multiple kernel source files.
 | |
| """
 | |
| 
 | |
| import argparse
 | |
| import logging
 | |
| import os
 | |
| import re
 | |
| 
 | |
| from kdoc_parser import KernelDoc
 | |
| from kdoc_output import OutputFormat
 | |
| 
 | |
| 
 | |
| 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
 | |
|         """
 | |
| 
 | |
|         if not file_list:
 | |
|             return
 | |
| 
 | |
|         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 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)
 | |
|         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
 | |
| 
 | |
|         self.results[fname] = entries
 | |
| 
 | |
|     def process_export_file(self, fname):
 | |
|         """
 | |
|         Parses EXPORT_SYMBOL* macros from a single Kernel source file.
 | |
|         """
 | |
| 
 | |
|         # 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.error(f"Cannot find file {fname}")
 | |
| 
 | |
|     def __init__(self, verbose=False, out_style=None,
 | |
|                  werror=False, wreturn=False, wshort_desc=False,
 | |
|                  wcontents_before_sections=False,
 | |
|                  logger=None):
 | |
|         """
 | |
|         Initialize startup variables and parse all files
 | |
|         """
 | |
| 
 | |
|         if not verbose:
 | |
|             verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
 | |
| 
 | |
|         if out_style is None:
 | |
|             out_style = OutputFormat()
 | |
| 
 | |
|         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
 | |
| 
 | |
|         # 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
 | |
|         self.config.werror = werror
 | |
|         self.config.wreturn = wreturn
 | |
|         self.config.wshort_desc = wshort_desc
 | |
|         self.config.wcontents_before_sections = wcontents_before_sections
 | |
| 
 | |
|         if not logger:
 | |
|             self.config.log = logging.getLogger("kernel-doc")
 | |
|         else:
 | |
|             self.config.log = logger
 | |
| 
 | |
|         self.config.warning = self.warning
 | |
| 
 | |
|         self.config.src_tree = os.environ.get("SRCTREE", None)
 | |
| 
 | |
|         # Initialize variables that are internal to KernelFiles
 | |
| 
 | |
|         self.out_style = out_style
 | |
| 
 | |
|         self.errors = 0
 | |
|         self.results = {}
 | |
| 
 | |
|         self.files = set()
 | |
|         self.export_files = set()
 | |
|         self.export_table = {}
 | |
| 
 | |
|     def parse(self, file_list, export_file=None):
 | |
|         """
 | |
|         Parse all files
 | |
|         """
 | |
| 
 | |
|         glob = GlobSourceFiles(srctree=self.config.src_tree)
 | |
| 
 | |
|         for fname in glob.parse_files(file_list, self.file_not_found_cb):
 | |
|             self.parse_file(fname)
 | |
| 
 | |
|         for fname in glob.parse_files(export_file, self.file_not_found_cb):
 | |
|             self.process_export_file(fname)
 | |
| 
 | |
|     def out_msg(self, fname, name, arg):
 | |
|         """
 | |
|         Return output messages from a file name using the output style
 | |
|         filtering.
 | |
| 
 | |
|         If output type was not handled by the syler, return None.
 | |
|         """
 | |
| 
 | |
|         # 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, no_doc_sections=False,
 | |
|             filenames=None, export_file=None):
 | |
|         """
 | |
|         Interacts over the kernel-doc results and output messages,
 | |
|         returning kernel-doc markups on each interaction
 | |
|         """
 | |
| 
 | |
|         self.out_style.set_config(self.config)
 | |
| 
 | |
|         if not filenames:
 | |
|             filenames = sorted(self.results.keys())
 | |
| 
 | |
|         glob = GlobSourceFiles(srctree=self.config.src_tree)
 | |
| 
 | |
|         for fname in filenames:
 | |
|             function_table = set()
 | |
| 
 | |
|             if internal or export:
 | |
|                 if not export_file:
 | |
|                     export_file = [fname]
 | |
| 
 | |
|                 for f in glob.parse_files(export_file, self.file_not_found_cb):
 | |
|                     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 = ""
 | |
|             if fname not in self.results:
 | |
|                 self.config.log.warning("No kernel-doc for file %s", fname)
 | |
|                 continue
 | |
| 
 | |
|             for arg in self.results[fname]:
 | |
|                 m = self.out_msg(fname, arg.name, arg)
 | |
| 
 | |
|                 if m is None:
 | |
|                     ln = arg.get("ln", 0)
 | |
|                     dtype = arg.get('type', "")
 | |
| 
 | |
|                     self.config.log.warning("%s:%d Can't handle %s",
 | |
|                                             fname, ln, dtype)
 | |
|                 else:
 | |
|                     msg += m
 | |
| 
 | |
|             if msg:
 | |
|                 yield fname, msg
 |