1ee13b3f3SMauro Carvalho Chehab#!/usr/bin/env python3 2ee13b3f3SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0 3ee13b3f3SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4ee13b3f3SMauro Carvalho Chehab# 5ee13b3f3SMauro Carvalho Chehab# pylint: disable=R0903,R0913,R0914,R0917 6ee13b3f3SMauro Carvalho Chehab 7ee13b3f3SMauro Carvalho Chehab""" 8ee13b3f3SMauro Carvalho ChehabParse lernel-doc tags on multiple kernel source files. 9ee13b3f3SMauro Carvalho Chehab""" 10ee13b3f3SMauro Carvalho Chehab 11ee13b3f3SMauro Carvalho Chehabimport argparse 12ee13b3f3SMauro Carvalho Chehabimport logging 13ee13b3f3SMauro Carvalho Chehabimport os 14ee13b3f3SMauro Carvalho Chehabimport re 15ee13b3f3SMauro Carvalho Chehab 16ee13b3f3SMauro Carvalho Chehabfrom kdoc_parser import KernelDoc 1743ecfe6bSMauro Carvalho Chehabfrom kdoc_output import OutputFormat 18ee13b3f3SMauro Carvalho Chehab 19ee13b3f3SMauro Carvalho Chehab 20ee13b3f3SMauro Carvalho Chehabclass GlobSourceFiles: 21ee13b3f3SMauro Carvalho Chehab """ 22ee13b3f3SMauro Carvalho Chehab Parse C source code file names and directories via an Interactor. 23ee13b3f3SMauro Carvalho Chehab """ 24ee13b3f3SMauro Carvalho Chehab 25ee13b3f3SMauro Carvalho Chehab def __init__(self, srctree=None, valid_extensions=None): 26ee13b3f3SMauro Carvalho Chehab """ 27ee13b3f3SMauro Carvalho Chehab Initialize valid extensions with a tuple. 28ee13b3f3SMauro Carvalho Chehab 29ee13b3f3SMauro Carvalho Chehab If not defined, assume default C extensions (.c and .h) 30ee13b3f3SMauro Carvalho Chehab 31ee13b3f3SMauro Carvalho Chehab It would be possible to use python's glob function, but it is 32ee13b3f3SMauro Carvalho Chehab very slow, and it is not interactive. So, it would wait to read all 33ee13b3f3SMauro Carvalho Chehab directories before actually do something. 34ee13b3f3SMauro Carvalho Chehab 35ee13b3f3SMauro Carvalho Chehab So, let's use our own implementation. 36ee13b3f3SMauro Carvalho Chehab """ 37ee13b3f3SMauro Carvalho Chehab 38ee13b3f3SMauro Carvalho Chehab if not valid_extensions: 39ee13b3f3SMauro Carvalho Chehab self.extensions = (".c", ".h") 40ee13b3f3SMauro Carvalho Chehab else: 41ee13b3f3SMauro Carvalho Chehab self.extensions = valid_extensions 42ee13b3f3SMauro Carvalho Chehab 43ee13b3f3SMauro Carvalho Chehab self.srctree = srctree 44ee13b3f3SMauro Carvalho Chehab 45ee13b3f3SMauro Carvalho Chehab def _parse_dir(self, dirname): 46ee13b3f3SMauro Carvalho Chehab """Internal function to parse files recursively""" 47ee13b3f3SMauro Carvalho Chehab 48ee13b3f3SMauro Carvalho Chehab with os.scandir(dirname) as obj: 49ee13b3f3SMauro Carvalho Chehab for entry in obj: 50ee13b3f3SMauro Carvalho Chehab name = os.path.join(dirname, entry.name) 51ee13b3f3SMauro Carvalho Chehab 52ee13b3f3SMauro Carvalho Chehab if entry.is_dir(): 53ee13b3f3SMauro Carvalho Chehab yield from self._parse_dir(name) 54ee13b3f3SMauro Carvalho Chehab 55ee13b3f3SMauro Carvalho Chehab if not entry.is_file(): 56ee13b3f3SMauro Carvalho Chehab continue 57ee13b3f3SMauro Carvalho Chehab 58ee13b3f3SMauro Carvalho Chehab basename = os.path.basename(name) 59ee13b3f3SMauro Carvalho Chehab 60ee13b3f3SMauro Carvalho Chehab if not basename.endswith(self.extensions): 61ee13b3f3SMauro Carvalho Chehab continue 62ee13b3f3SMauro Carvalho Chehab 63ee13b3f3SMauro Carvalho Chehab yield name 64ee13b3f3SMauro Carvalho Chehab 65ee13b3f3SMauro Carvalho Chehab def parse_files(self, file_list, file_not_found_cb): 66ee13b3f3SMauro Carvalho Chehab """ 67ee13b3f3SMauro Carvalho Chehab Define an interator to parse all source files from file_list, 68ee13b3f3SMauro Carvalho Chehab handling directories if any 69ee13b3f3SMauro Carvalho Chehab """ 70ee13b3f3SMauro Carvalho Chehab 7116740c29SMauro Carvalho Chehab if not file_list: 7216740c29SMauro Carvalho Chehab return 7316740c29SMauro Carvalho Chehab 74ee13b3f3SMauro Carvalho Chehab for fname in file_list: 75ee13b3f3SMauro Carvalho Chehab if self.srctree: 76ee13b3f3SMauro Carvalho Chehab f = os.path.join(self.srctree, fname) 77ee13b3f3SMauro Carvalho Chehab else: 78ee13b3f3SMauro Carvalho Chehab f = fname 79ee13b3f3SMauro Carvalho Chehab 80ee13b3f3SMauro Carvalho Chehab if os.path.isdir(f): 81ee13b3f3SMauro Carvalho Chehab yield from self._parse_dir(f) 82ee13b3f3SMauro Carvalho Chehab elif os.path.isfile(f): 83ee13b3f3SMauro Carvalho Chehab yield f 84ee13b3f3SMauro Carvalho Chehab elif file_not_found_cb: 85ee13b3f3SMauro Carvalho Chehab file_not_found_cb(fname) 86ee13b3f3SMauro Carvalho Chehab 87ee13b3f3SMauro Carvalho Chehab 88ee13b3f3SMauro Carvalho Chehabclass KernelFiles(): 89ee13b3f3SMauro Carvalho Chehab """ 9016740c29SMauro Carvalho Chehab Parse kernel-doc tags on multiple kernel source files. 9116740c29SMauro Carvalho Chehab 9216740c29SMauro Carvalho Chehab There are two type of parsers defined here: 9316740c29SMauro Carvalho Chehab - self.parse_file(): parses both kernel-doc markups and 9416740c29SMauro Carvalho Chehab EXPORT_SYMBOL* macros; 9516740c29SMauro Carvalho Chehab - self.process_export_file(): parses only EXPORT_SYMBOL* macros. 96ee13b3f3SMauro Carvalho Chehab """ 97ee13b3f3SMauro Carvalho Chehab 9816740c29SMauro Carvalho Chehab def warning(self, msg): 9916740c29SMauro Carvalho Chehab """Ancillary routine to output a warning and increment error count""" 10016740c29SMauro Carvalho Chehab 10116740c29SMauro Carvalho Chehab self.config.log.warning(msg) 10216740c29SMauro Carvalho Chehab self.errors += 1 10316740c29SMauro Carvalho Chehab 10416740c29SMauro Carvalho Chehab def error(self, msg): 10516740c29SMauro Carvalho Chehab """Ancillary routine to output an error and increment error count""" 10616740c29SMauro Carvalho Chehab 10716740c29SMauro Carvalho Chehab self.config.log.error(msg) 10816740c29SMauro Carvalho Chehab self.errors += 1 10916740c29SMauro Carvalho Chehab 110ee13b3f3SMauro Carvalho Chehab def parse_file(self, fname): 111ee13b3f3SMauro Carvalho Chehab """ 112ee13b3f3SMauro Carvalho Chehab Parse a single Kernel source. 113ee13b3f3SMauro Carvalho Chehab """ 114ee13b3f3SMauro Carvalho Chehab 11516740c29SMauro Carvalho Chehab # Prevent parsing the same file twice if results are cached 11616740c29SMauro Carvalho Chehab if fname in self.files: 11716740c29SMauro Carvalho Chehab return 118ee13b3f3SMauro Carvalho Chehab 11916740c29SMauro Carvalho Chehab doc = KernelDoc(self.config, fname) 12016740c29SMauro Carvalho Chehab export_table, entries = doc.parse_kdoc() 12116740c29SMauro Carvalho Chehab 12216740c29SMauro Carvalho Chehab self.export_table[fname] = export_table 12316740c29SMauro Carvalho Chehab 12416740c29SMauro Carvalho Chehab self.files.add(fname) 12516740c29SMauro Carvalho Chehab self.export_files.add(fname) # parse_kdoc() already check exports 12616740c29SMauro Carvalho Chehab 12716740c29SMauro Carvalho Chehab self.results[fname] = entries 128ee13b3f3SMauro Carvalho Chehab 129ee13b3f3SMauro Carvalho Chehab def process_export_file(self, fname): 130ee13b3f3SMauro Carvalho Chehab """ 131ee13b3f3SMauro Carvalho Chehab Parses EXPORT_SYMBOL* macros from a single Kernel source file. 132ee13b3f3SMauro Carvalho Chehab """ 133ee13b3f3SMauro Carvalho Chehab 13416740c29SMauro Carvalho Chehab # Prevent parsing the same file twice if results are cached 13516740c29SMauro Carvalho Chehab if fname in self.export_files: 13616740c29SMauro Carvalho Chehab return 13716740c29SMauro Carvalho Chehab 13816740c29SMauro Carvalho Chehab doc = KernelDoc(self.config, fname) 13916740c29SMauro Carvalho Chehab export_table = doc.parse_export() 14016740c29SMauro Carvalho Chehab 14116740c29SMauro Carvalho Chehab if not export_table: 14216740c29SMauro Carvalho Chehab self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}") 14316740c29SMauro Carvalho Chehab export_table = set() 14416740c29SMauro Carvalho Chehab 14516740c29SMauro Carvalho Chehab self.export_table[fname] = export_table 14616740c29SMauro Carvalho Chehab self.export_files.add(fname) 147ee13b3f3SMauro Carvalho Chehab 148ee13b3f3SMauro Carvalho Chehab def file_not_found_cb(self, fname): 149ee13b3f3SMauro Carvalho Chehab """ 150ee13b3f3SMauro Carvalho Chehab Callback to warn if a file was not found. 151ee13b3f3SMauro Carvalho Chehab """ 152ee13b3f3SMauro Carvalho Chehab 15316740c29SMauro Carvalho Chehab self.error(f"Cannot find file {fname}") 154ee13b3f3SMauro Carvalho Chehab 155799b0d2aSMauro Carvalho Chehab def __init__(self, verbose=False, out_style=None, 156ee13b3f3SMauro Carvalho Chehab werror=False, wreturn=False, wshort_desc=False, 157ee13b3f3SMauro Carvalho Chehab wcontents_before_sections=False, 1582ab867a4SMauro Carvalho Chehab logger=None): 159ee13b3f3SMauro Carvalho Chehab """ 160ee13b3f3SMauro Carvalho Chehab Initialize startup variables and parse all files 161ee13b3f3SMauro Carvalho Chehab """ 162ee13b3f3SMauro Carvalho Chehab 163ee13b3f3SMauro Carvalho Chehab if not verbose: 164ee13b3f3SMauro Carvalho Chehab verbose = bool(os.environ.get("KBUILD_VERBOSE", 0)) 165ee13b3f3SMauro Carvalho Chehab 16643ecfe6bSMauro Carvalho Chehab if out_style is None: 16743ecfe6bSMauro Carvalho Chehab out_style = OutputFormat() 16843ecfe6bSMauro Carvalho Chehab 169ee13b3f3SMauro Carvalho Chehab if not werror: 170ee13b3f3SMauro Carvalho Chehab kcflags = os.environ.get("KCFLAGS", None) 171ee13b3f3SMauro Carvalho Chehab if kcflags: 172ee13b3f3SMauro Carvalho Chehab match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags) 173ee13b3f3SMauro Carvalho Chehab if match: 174ee13b3f3SMauro Carvalho Chehab werror = True 175ee13b3f3SMauro Carvalho Chehab 176ee13b3f3SMauro Carvalho Chehab # reading this variable is for backwards compat just in case 177ee13b3f3SMauro Carvalho Chehab # someone was calling it with the variable from outside the 178ee13b3f3SMauro Carvalho Chehab # kernel's build system 179ee13b3f3SMauro Carvalho Chehab kdoc_werror = os.environ.get("KDOC_WERROR", None) 180ee13b3f3SMauro Carvalho Chehab if kdoc_werror: 181ee13b3f3SMauro Carvalho Chehab werror = kdoc_werror 182ee13b3f3SMauro Carvalho Chehab 18316740c29SMauro Carvalho Chehab # Some variables are global to the parser logic as a whole as they are 18416740c29SMauro Carvalho Chehab # used to send control configuration to KernelDoc class. As such, 18516740c29SMauro Carvalho Chehab # those variables are read-only inside the KernelDoc. 186ee13b3f3SMauro Carvalho Chehab self.config = argparse.Namespace 187ee13b3f3SMauro Carvalho Chehab 188ee13b3f3SMauro Carvalho Chehab self.config.verbose = verbose 189ee13b3f3SMauro Carvalho Chehab self.config.werror = werror 190ee13b3f3SMauro Carvalho Chehab self.config.wreturn = wreturn 191ee13b3f3SMauro Carvalho Chehab self.config.wshort_desc = wshort_desc 192ee13b3f3SMauro Carvalho Chehab self.config.wcontents_before_sections = wcontents_before_sections 193ee13b3f3SMauro Carvalho Chehab 194ee13b3f3SMauro Carvalho Chehab if not logger: 195ee13b3f3SMauro Carvalho Chehab self.config.log = logging.getLogger("kernel-doc") 196ee13b3f3SMauro Carvalho Chehab else: 197ee13b3f3SMauro Carvalho Chehab self.config.log = logger 198ee13b3f3SMauro Carvalho Chehab 19916740c29SMauro Carvalho Chehab self.config.warning = self.warning 20016740c29SMauro Carvalho Chehab 201ee13b3f3SMauro Carvalho Chehab self.config.src_tree = os.environ.get("SRCTREE", None) 202ee13b3f3SMauro Carvalho Chehab 20316740c29SMauro Carvalho Chehab # Initialize variables that are internal to KernelFiles 20416740c29SMauro Carvalho Chehab 205ee13b3f3SMauro Carvalho Chehab self.out_style = out_style 206ee13b3f3SMauro Carvalho Chehab 20716740c29SMauro Carvalho Chehab self.errors = 0 208a566ba5aSMauro Carvalho Chehab self.results = {} 209ee13b3f3SMauro Carvalho Chehab 210ee13b3f3SMauro Carvalho Chehab self.files = set() 211799b0d2aSMauro Carvalho Chehab self.export_files = set() 21216740c29SMauro Carvalho Chehab self.export_table = {} 213ee13b3f3SMauro Carvalho Chehab 214799b0d2aSMauro Carvalho Chehab def parse(self, file_list, export_file=None): 215ee13b3f3SMauro Carvalho Chehab """ 216ee13b3f3SMauro Carvalho Chehab Parse all files 217ee13b3f3SMauro Carvalho Chehab """ 218ee13b3f3SMauro Carvalho Chehab 219ee13b3f3SMauro Carvalho Chehab glob = GlobSourceFiles(srctree=self.config.src_tree) 220ee13b3f3SMauro Carvalho Chehab 221799b0d2aSMauro Carvalho Chehab for fname in glob.parse_files(file_list, self.file_not_found_cb): 22216740c29SMauro Carvalho Chehab self.parse_file(fname) 223ee13b3f3SMauro Carvalho Chehab 224799b0d2aSMauro Carvalho Chehab for fname in glob.parse_files(export_file, self.file_not_found_cb): 225ee13b3f3SMauro Carvalho Chehab self.process_export_file(fname) 226ee13b3f3SMauro Carvalho Chehab 227ee13b3f3SMauro Carvalho Chehab def out_msg(self, fname, name, arg): 228ee13b3f3SMauro Carvalho Chehab """ 2294fa5e411SMauro Carvalho Chehab Return output messages from a file name using the output style 2304fa5e411SMauro Carvalho Chehab filtering. 231ee13b3f3SMauro Carvalho Chehab 2324fa5e411SMauro Carvalho Chehab If output type was not handled by the syler, return None. 233ee13b3f3SMauro Carvalho Chehab """ 234ee13b3f3SMauro Carvalho Chehab 235ee13b3f3SMauro Carvalho Chehab # NOTE: we can add rules here to filter out unwanted parts, 236ee13b3f3SMauro Carvalho Chehab # although OutputFormat.msg already does that. 237ee13b3f3SMauro Carvalho Chehab 238ee13b3f3SMauro Carvalho Chehab return self.out_style.msg(fname, name, arg) 239ee13b3f3SMauro Carvalho Chehab 240ee13b3f3SMauro Carvalho Chehab def msg(self, enable_lineno=False, export=False, internal=False, 241a566ba5aSMauro Carvalho Chehab symbol=None, nosymbol=None, no_doc_sections=False, 24216740c29SMauro Carvalho Chehab filenames=None, export_file=None): 243ee13b3f3SMauro Carvalho Chehab """ 2444fa5e411SMauro Carvalho Chehab Interacts over the kernel-doc results and output messages, 2454fa5e411SMauro Carvalho Chehab returning kernel-doc markups on each interaction 246ee13b3f3SMauro Carvalho Chehab """ 247ee13b3f3SMauro Carvalho Chehab 248ee13b3f3SMauro Carvalho Chehab self.out_style.set_config(self.config) 249ee13b3f3SMauro Carvalho Chehab 250a566ba5aSMauro Carvalho Chehab if not filenames: 251a566ba5aSMauro Carvalho Chehab filenames = sorted(self.results.keys()) 252a566ba5aSMauro Carvalho Chehab 25347c2d416SMauro Carvalho Chehab glob = GlobSourceFiles(srctree=self.config.src_tree) 25447c2d416SMauro Carvalho Chehab 255a566ba5aSMauro Carvalho Chehab for fname in filenames: 25616740c29SMauro Carvalho Chehab function_table = set() 25716740c29SMauro Carvalho Chehab 25816740c29SMauro Carvalho Chehab if internal or export: 25916740c29SMauro Carvalho Chehab if not export_file: 26016740c29SMauro Carvalho Chehab export_file = [fname] 26116740c29SMauro Carvalho Chehab 26247c2d416SMauro Carvalho Chehab for f in glob.parse_files(export_file, self.file_not_found_cb): 26316740c29SMauro Carvalho Chehab function_table |= self.export_table[f] 26416740c29SMauro Carvalho Chehab 26516740c29SMauro Carvalho Chehab if symbol: 26616740c29SMauro Carvalho Chehab for s in symbol: 26716740c29SMauro Carvalho Chehab function_table.add(s) 26816740c29SMauro Carvalho Chehab 26916740c29SMauro Carvalho Chehab self.out_style.set_filter(export, internal, symbol, nosymbol, 27016740c29SMauro Carvalho Chehab function_table, enable_lineno, 27116740c29SMauro Carvalho Chehab no_doc_sections) 27216740c29SMauro Carvalho Chehab 2734fa5e411SMauro Carvalho Chehab msg = "" 274*27565cfcSMauro Carvalho Chehab if fname not in self.results: 275*27565cfcSMauro Carvalho Chehab self.config.log.warning("No kernel-doc for file %s", fname) 276*27565cfcSMauro Carvalho Chehab continue 277*27565cfcSMauro Carvalho Chehab 278a566ba5aSMauro Carvalho Chehab for name, arg in self.results[fname]: 279439111eeSMauro Carvalho Chehab m = self.out_msg(fname, name, arg) 2804fa5e411SMauro Carvalho Chehab 281439111eeSMauro Carvalho Chehab if m is None: 282ee13b3f3SMauro Carvalho Chehab ln = arg.get("ln", 0) 283ee13b3f3SMauro Carvalho Chehab dtype = arg.get('type', "") 284ee13b3f3SMauro Carvalho Chehab 285ee13b3f3SMauro Carvalho Chehab self.config.log.warning("%s:%d Can't handle %s", 286ee13b3f3SMauro Carvalho Chehab fname, ln, dtype) 287439111eeSMauro Carvalho Chehab else: 288439111eeSMauro Carvalho Chehab msg += m 289439111eeSMauro Carvalho Chehab 2904fa5e411SMauro Carvalho Chehab if msg: 2914fa5e411SMauro Carvalho Chehab yield fname, msg 292