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""" 8*8d08c7c6SMauro Carvalho ChehabClasses for navigating through the files that kernel-doc needs to handle 9*8d08c7c6SMauro Carvalho Chehabto generate documentation. 10ee13b3f3SMauro Carvalho Chehab""" 11ee13b3f3SMauro Carvalho Chehab 12ee13b3f3SMauro Carvalho Chehabimport argparse 13ee13b3f3SMauro Carvalho Chehabimport logging 14ee13b3f3SMauro Carvalho Chehabimport os 15ee13b3f3SMauro Carvalho Chehabimport re 16ee13b3f3SMauro Carvalho Chehab 17992a9df4SJonathan Corbetfrom kdoc.kdoc_parser import KernelDoc 18992a9df4SJonathan Corbetfrom kdoc.kdoc_output import OutputFormat 19ee13b3f3SMauro Carvalho Chehab 20ee13b3f3SMauro Carvalho Chehab 21ee13b3f3SMauro Carvalho Chehabclass GlobSourceFiles: 22ee13b3f3SMauro Carvalho Chehab """ 23ee13b3f3SMauro Carvalho Chehab Parse C source code file names and directories via an Interactor. 24ee13b3f3SMauro Carvalho Chehab """ 25ee13b3f3SMauro Carvalho Chehab 26ee13b3f3SMauro Carvalho Chehab def __init__(self, srctree=None, valid_extensions=None): 27ee13b3f3SMauro Carvalho Chehab """ 28ee13b3f3SMauro Carvalho Chehab Initialize valid extensions with a tuple. 29ee13b3f3SMauro Carvalho Chehab 30ee13b3f3SMauro Carvalho Chehab If not defined, assume default C extensions (.c and .h) 31ee13b3f3SMauro Carvalho Chehab 32ee13b3f3SMauro Carvalho Chehab It would be possible to use python's glob function, but it is 33ee13b3f3SMauro Carvalho Chehab very slow, and it is not interactive. So, it would wait to read all 34ee13b3f3SMauro Carvalho Chehab directories before actually do something. 35ee13b3f3SMauro Carvalho Chehab 36ee13b3f3SMauro Carvalho Chehab So, let's use our own implementation. 37ee13b3f3SMauro Carvalho Chehab """ 38ee13b3f3SMauro Carvalho Chehab 39ee13b3f3SMauro Carvalho Chehab if not valid_extensions: 40ee13b3f3SMauro Carvalho Chehab self.extensions = (".c", ".h") 41ee13b3f3SMauro Carvalho Chehab else: 42ee13b3f3SMauro Carvalho Chehab self.extensions = valid_extensions 43ee13b3f3SMauro Carvalho Chehab 44ee13b3f3SMauro Carvalho Chehab self.srctree = srctree 45ee13b3f3SMauro Carvalho Chehab 46ee13b3f3SMauro Carvalho Chehab def _parse_dir(self, dirname): 47*8d08c7c6SMauro Carvalho Chehab """Internal function to parse files recursively.""" 48ee13b3f3SMauro Carvalho Chehab 49ee13b3f3SMauro Carvalho Chehab with os.scandir(dirname) as obj: 50ee13b3f3SMauro Carvalho Chehab for entry in obj: 51ee13b3f3SMauro Carvalho Chehab name = os.path.join(dirname, entry.name) 52ee13b3f3SMauro Carvalho Chehab 53f64c7e11SMauro Carvalho Chehab if entry.is_dir(follow_symlinks=False): 54ee13b3f3SMauro Carvalho Chehab yield from self._parse_dir(name) 55ee13b3f3SMauro Carvalho Chehab 56ee13b3f3SMauro Carvalho Chehab if not entry.is_file(): 57ee13b3f3SMauro Carvalho Chehab continue 58ee13b3f3SMauro Carvalho Chehab 59ee13b3f3SMauro Carvalho Chehab basename = os.path.basename(name) 60ee13b3f3SMauro Carvalho Chehab 61ee13b3f3SMauro Carvalho Chehab if not basename.endswith(self.extensions): 62ee13b3f3SMauro Carvalho Chehab continue 63ee13b3f3SMauro Carvalho Chehab 64ee13b3f3SMauro Carvalho Chehab yield name 65ee13b3f3SMauro Carvalho Chehab 66ee13b3f3SMauro Carvalho Chehab def parse_files(self, file_list, file_not_found_cb): 67ee13b3f3SMauro Carvalho Chehab """ 685f88f44dSRandy Dunlap Define an iterator to parse all source files from file_list, 69*8d08c7c6SMauro Carvalho Chehab handling directories if any. 70ee13b3f3SMauro Carvalho Chehab """ 71ee13b3f3SMauro Carvalho Chehab 7216740c29SMauro Carvalho Chehab if not file_list: 7316740c29SMauro Carvalho Chehab return 7416740c29SMauro Carvalho Chehab 75ee13b3f3SMauro Carvalho Chehab for fname in file_list: 76ee13b3f3SMauro Carvalho Chehab if self.srctree: 77ee13b3f3SMauro Carvalho Chehab f = os.path.join(self.srctree, fname) 78ee13b3f3SMauro Carvalho Chehab else: 79ee13b3f3SMauro Carvalho Chehab f = fname 80ee13b3f3SMauro Carvalho Chehab 81ee13b3f3SMauro Carvalho Chehab if os.path.isdir(f): 82ee13b3f3SMauro Carvalho Chehab yield from self._parse_dir(f) 83ee13b3f3SMauro Carvalho Chehab elif os.path.isfile(f): 84ee13b3f3SMauro Carvalho Chehab yield f 85ee13b3f3SMauro Carvalho Chehab elif file_not_found_cb: 86ee13b3f3SMauro Carvalho Chehab file_not_found_cb(fname) 87ee13b3f3SMauro Carvalho Chehab 88ee13b3f3SMauro Carvalho Chehab 89ee13b3f3SMauro Carvalho Chehabclass KernelFiles(): 90ee13b3f3SMauro Carvalho Chehab """ 9116740c29SMauro Carvalho Chehab Parse kernel-doc tags on multiple kernel source files. 9216740c29SMauro Carvalho Chehab 9316740c29SMauro Carvalho Chehab There are two type of parsers defined here: 9416740c29SMauro Carvalho Chehab - self.parse_file(): parses both kernel-doc markups and 95*8d08c7c6SMauro Carvalho Chehab ``EXPORT_SYMBOL*`` macros; 96*8d08c7c6SMauro Carvalho Chehab - self.process_export_file(): parses only ``EXPORT_SYMBOL*`` macros. 97ee13b3f3SMauro Carvalho Chehab """ 98ee13b3f3SMauro Carvalho Chehab 9916740c29SMauro Carvalho Chehab def warning(self, msg): 100*8d08c7c6SMauro Carvalho Chehab """Ancillary routine to output a warning and increment error count.""" 10116740c29SMauro Carvalho Chehab 10216740c29SMauro Carvalho Chehab self.config.log.warning(msg) 10316740c29SMauro Carvalho Chehab self.errors += 1 10416740c29SMauro Carvalho Chehab 10516740c29SMauro Carvalho Chehab def error(self, msg): 106*8d08c7c6SMauro Carvalho Chehab """Ancillary routine to output an error and increment error count.""" 10716740c29SMauro Carvalho Chehab 10816740c29SMauro Carvalho Chehab self.config.log.error(msg) 10916740c29SMauro Carvalho Chehab self.errors += 1 11016740c29SMauro Carvalho Chehab 111ee13b3f3SMauro Carvalho Chehab def parse_file(self, fname): 112ee13b3f3SMauro Carvalho Chehab """ 113ee13b3f3SMauro Carvalho Chehab Parse a single Kernel source. 114ee13b3f3SMauro Carvalho Chehab """ 115ee13b3f3SMauro Carvalho Chehab 11616740c29SMauro Carvalho Chehab # Prevent parsing the same file twice if results are cached 11716740c29SMauro Carvalho Chehab if fname in self.files: 11816740c29SMauro Carvalho Chehab return 119ee13b3f3SMauro Carvalho Chehab 12016740c29SMauro Carvalho Chehab doc = KernelDoc(self.config, fname) 12116740c29SMauro Carvalho Chehab export_table, entries = doc.parse_kdoc() 12216740c29SMauro Carvalho Chehab 12316740c29SMauro Carvalho Chehab self.export_table[fname] = export_table 12416740c29SMauro Carvalho Chehab 12516740c29SMauro Carvalho Chehab self.files.add(fname) 12616740c29SMauro Carvalho Chehab self.export_files.add(fname) # parse_kdoc() already check exports 12716740c29SMauro Carvalho Chehab 12816740c29SMauro Carvalho Chehab self.results[fname] = entries 129ee13b3f3SMauro Carvalho Chehab 130ee13b3f3SMauro Carvalho Chehab def process_export_file(self, fname): 131ee13b3f3SMauro Carvalho Chehab """ 132*8d08c7c6SMauro Carvalho Chehab Parses ``EXPORT_SYMBOL*`` macros from a single Kernel source file. 133ee13b3f3SMauro Carvalho Chehab """ 134ee13b3f3SMauro Carvalho Chehab 13516740c29SMauro Carvalho Chehab # Prevent parsing the same file twice if results are cached 13616740c29SMauro Carvalho Chehab if fname in self.export_files: 13716740c29SMauro Carvalho Chehab return 13816740c29SMauro Carvalho Chehab 13916740c29SMauro Carvalho Chehab doc = KernelDoc(self.config, fname) 14016740c29SMauro Carvalho Chehab export_table = doc.parse_export() 14116740c29SMauro Carvalho Chehab 14216740c29SMauro Carvalho Chehab if not export_table: 14316740c29SMauro Carvalho Chehab self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}") 14416740c29SMauro Carvalho Chehab export_table = set() 14516740c29SMauro Carvalho Chehab 14616740c29SMauro Carvalho Chehab self.export_table[fname] = export_table 14716740c29SMauro Carvalho Chehab self.export_files.add(fname) 148ee13b3f3SMauro Carvalho Chehab 149ee13b3f3SMauro Carvalho Chehab def file_not_found_cb(self, fname): 150ee13b3f3SMauro Carvalho Chehab """ 151ee13b3f3SMauro Carvalho Chehab Callback to warn if a file was not found. 152ee13b3f3SMauro Carvalho Chehab """ 153ee13b3f3SMauro Carvalho Chehab 15416740c29SMauro Carvalho Chehab self.error(f"Cannot find file {fname}") 155ee13b3f3SMauro Carvalho Chehab 156799b0d2aSMauro Carvalho Chehab def __init__(self, verbose=False, out_style=None, 157ee13b3f3SMauro Carvalho Chehab werror=False, wreturn=False, wshort_desc=False, 158ee13b3f3SMauro Carvalho Chehab wcontents_before_sections=False, 1592ab867a4SMauro Carvalho Chehab logger=None): 160ee13b3f3SMauro Carvalho Chehab """ 161*8d08c7c6SMauro Carvalho Chehab Initialize startup variables and parse all files. 162ee13b3f3SMauro Carvalho Chehab """ 163ee13b3f3SMauro Carvalho Chehab 164ee13b3f3SMauro Carvalho Chehab if not verbose: 165ee13b3f3SMauro Carvalho Chehab verbose = bool(os.environ.get("KBUILD_VERBOSE", 0)) 166ee13b3f3SMauro Carvalho Chehab 16743ecfe6bSMauro Carvalho Chehab if out_style is None: 16843ecfe6bSMauro Carvalho Chehab out_style = OutputFormat() 16943ecfe6bSMauro Carvalho Chehab 170ee13b3f3SMauro Carvalho Chehab if not werror: 171ee13b3f3SMauro Carvalho Chehab kcflags = os.environ.get("KCFLAGS", None) 172ee13b3f3SMauro Carvalho Chehab if kcflags: 173ee13b3f3SMauro Carvalho Chehab match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags) 174ee13b3f3SMauro Carvalho Chehab if match: 175ee13b3f3SMauro Carvalho Chehab werror = True 176ee13b3f3SMauro Carvalho Chehab 177ee13b3f3SMauro Carvalho Chehab # reading this variable is for backwards compat just in case 178ee13b3f3SMauro Carvalho Chehab # someone was calling it with the variable from outside the 179ee13b3f3SMauro Carvalho Chehab # kernel's build system 180ee13b3f3SMauro Carvalho Chehab kdoc_werror = os.environ.get("KDOC_WERROR", None) 181ee13b3f3SMauro Carvalho Chehab if kdoc_werror: 182ee13b3f3SMauro Carvalho Chehab werror = kdoc_werror 183ee13b3f3SMauro Carvalho Chehab 18416740c29SMauro Carvalho Chehab # Some variables are global to the parser logic as a whole as they are 18516740c29SMauro Carvalho Chehab # used to send control configuration to KernelDoc class. As such, 18616740c29SMauro Carvalho Chehab # those variables are read-only inside the KernelDoc. 187ee13b3f3SMauro Carvalho Chehab self.config = argparse.Namespace 188ee13b3f3SMauro Carvalho Chehab 189ee13b3f3SMauro Carvalho Chehab self.config.verbose = verbose 190ee13b3f3SMauro Carvalho Chehab self.config.werror = werror 191ee13b3f3SMauro Carvalho Chehab self.config.wreturn = wreturn 192ee13b3f3SMauro Carvalho Chehab self.config.wshort_desc = wshort_desc 193ee13b3f3SMauro Carvalho Chehab self.config.wcontents_before_sections = wcontents_before_sections 194ee13b3f3SMauro Carvalho Chehab 195ee13b3f3SMauro Carvalho Chehab if not logger: 196ee13b3f3SMauro Carvalho Chehab self.config.log = logging.getLogger("kernel-doc") 197ee13b3f3SMauro Carvalho Chehab else: 198ee13b3f3SMauro Carvalho Chehab self.config.log = logger 199ee13b3f3SMauro Carvalho Chehab 20016740c29SMauro Carvalho Chehab self.config.warning = self.warning 20116740c29SMauro Carvalho Chehab 202ee13b3f3SMauro Carvalho Chehab self.config.src_tree = os.environ.get("SRCTREE", None) 203ee13b3f3SMauro Carvalho Chehab 20416740c29SMauro Carvalho Chehab # Initialize variables that are internal to KernelFiles 20516740c29SMauro Carvalho Chehab 206ee13b3f3SMauro Carvalho Chehab self.out_style = out_style 207ee13b3f3SMauro Carvalho Chehab 20816740c29SMauro Carvalho Chehab self.errors = 0 209a566ba5aSMauro Carvalho Chehab self.results = {} 210ee13b3f3SMauro Carvalho Chehab 211ee13b3f3SMauro Carvalho Chehab self.files = set() 212799b0d2aSMauro Carvalho Chehab self.export_files = set() 21316740c29SMauro Carvalho Chehab self.export_table = {} 214ee13b3f3SMauro Carvalho Chehab 215799b0d2aSMauro Carvalho Chehab def parse(self, file_list, export_file=None): 216ee13b3f3SMauro Carvalho Chehab """ 217*8d08c7c6SMauro Carvalho Chehab Parse all files. 218ee13b3f3SMauro Carvalho Chehab """ 219ee13b3f3SMauro Carvalho Chehab 220ee13b3f3SMauro Carvalho Chehab glob = GlobSourceFiles(srctree=self.config.src_tree) 221ee13b3f3SMauro Carvalho Chehab 222799b0d2aSMauro Carvalho Chehab for fname in glob.parse_files(file_list, self.file_not_found_cb): 22316740c29SMauro Carvalho Chehab self.parse_file(fname) 224ee13b3f3SMauro Carvalho Chehab 225799b0d2aSMauro Carvalho Chehab for fname in glob.parse_files(export_file, self.file_not_found_cb): 226ee13b3f3SMauro Carvalho Chehab self.process_export_file(fname) 227ee13b3f3SMauro Carvalho Chehab 228ee13b3f3SMauro Carvalho Chehab def out_msg(self, fname, name, arg): 229ee13b3f3SMauro Carvalho Chehab """ 2304fa5e411SMauro Carvalho Chehab Return output messages from a file name using the output style 2314fa5e411SMauro Carvalho Chehab filtering. 232ee13b3f3SMauro Carvalho Chehab 2335f88f44dSRandy Dunlap If output type was not handled by the styler, return None. 234ee13b3f3SMauro Carvalho Chehab """ 235ee13b3f3SMauro Carvalho Chehab 236ee13b3f3SMauro Carvalho Chehab # NOTE: we can add rules here to filter out unwanted parts, 237ee13b3f3SMauro Carvalho Chehab # although OutputFormat.msg already does that. 238ee13b3f3SMauro Carvalho Chehab 239ee13b3f3SMauro Carvalho Chehab return self.out_style.msg(fname, name, arg) 240ee13b3f3SMauro Carvalho Chehab 241ee13b3f3SMauro Carvalho Chehab def msg(self, enable_lineno=False, export=False, internal=False, 242a566ba5aSMauro Carvalho Chehab symbol=None, nosymbol=None, no_doc_sections=False, 24316740c29SMauro Carvalho Chehab filenames=None, export_file=None): 244ee13b3f3SMauro Carvalho Chehab """ 2454fa5e411SMauro Carvalho Chehab Interacts over the kernel-doc results and output messages, 246*8d08c7c6SMauro Carvalho Chehab returning kernel-doc markups on each interaction. 247ee13b3f3SMauro Carvalho Chehab """ 248ee13b3f3SMauro Carvalho Chehab 249ee13b3f3SMauro Carvalho Chehab self.out_style.set_config(self.config) 250ee13b3f3SMauro Carvalho Chehab 251a566ba5aSMauro Carvalho Chehab if not filenames: 252a566ba5aSMauro Carvalho Chehab filenames = sorted(self.results.keys()) 253a566ba5aSMauro Carvalho Chehab 25447c2d416SMauro Carvalho Chehab glob = GlobSourceFiles(srctree=self.config.src_tree) 25547c2d416SMauro Carvalho Chehab 256a566ba5aSMauro Carvalho Chehab for fname in filenames: 25716740c29SMauro Carvalho Chehab function_table = set() 25816740c29SMauro Carvalho Chehab 25916740c29SMauro Carvalho Chehab if internal or export: 26016740c29SMauro Carvalho Chehab if not export_file: 26116740c29SMauro Carvalho Chehab export_file = [fname] 26216740c29SMauro Carvalho Chehab 26347c2d416SMauro Carvalho Chehab for f in glob.parse_files(export_file, self.file_not_found_cb): 26416740c29SMauro Carvalho Chehab function_table |= self.export_table[f] 26516740c29SMauro Carvalho Chehab 26616740c29SMauro Carvalho Chehab if symbol: 26716740c29SMauro Carvalho Chehab for s in symbol: 26816740c29SMauro Carvalho Chehab function_table.add(s) 26916740c29SMauro Carvalho Chehab 27016740c29SMauro Carvalho Chehab self.out_style.set_filter(export, internal, symbol, nosymbol, 27116740c29SMauro Carvalho Chehab function_table, enable_lineno, 27216740c29SMauro Carvalho Chehab no_doc_sections) 27316740c29SMauro Carvalho Chehab 2744fa5e411SMauro Carvalho Chehab msg = "" 27527565cfcSMauro Carvalho Chehab if fname not in self.results: 27627565cfcSMauro Carvalho Chehab self.config.log.warning("No kernel-doc for file %s", fname) 27727565cfcSMauro Carvalho Chehab continue 27827565cfcSMauro Carvalho Chehab 279104e0a68SMauro Carvalho Chehab symbols = self.results[fname] 280104e0a68SMauro Carvalho Chehab self.out_style.set_symbols(symbols) 281104e0a68SMauro Carvalho Chehab 282104e0a68SMauro Carvalho Chehab for arg in symbols: 283703f9074SJonathan Corbet m = self.out_msg(fname, arg.name, arg) 2844fa5e411SMauro Carvalho Chehab 285439111eeSMauro Carvalho Chehab if m is None: 286ee13b3f3SMauro Carvalho Chehab ln = arg.get("ln", 0) 287ee13b3f3SMauro Carvalho Chehab dtype = arg.get('type', "") 288ee13b3f3SMauro Carvalho Chehab 289ee13b3f3SMauro Carvalho Chehab self.config.log.warning("%s:%d Can't handle %s", 290ee13b3f3SMauro Carvalho Chehab fname, ln, dtype) 291439111eeSMauro Carvalho Chehab else: 292439111eeSMauro Carvalho Chehab msg += m 293439111eeSMauro Carvalho Chehab 2944fa5e411SMauro Carvalho Chehab if msg: 2954fa5e411SMauro Carvalho Chehab yield fname, msg 296