diff options
| author | Sasha Levin <sashal@kernel.org> | 2025-08-19 11:52:10 -0400 |
|---|---|---|
| committer | Sasha Levin <sashal@kernel.org> | 2026-10-07 18:26:44 -0400 |
| commit | 6d009bd85fa4e6dbad869e1cb707404968f3f20d (patch) | |
| tree | f764bac49d89c2553cda715c7f02dd58776478de /tools/lib/python/kdoc/kdoc_parser.py | |
| parent | ce4c52682d78fc08c9b1e2217aae6f00fa02bab3 (diff) | |
| download | linux-next-6d009bd85fa4e6dbad869e1cb707404968f3f20d.tar.gz linux-next-6d009bd85fa4e6dbad869e1cb707404968f3f20d.zip | |
kernel/api: enable kerneldoc-based API specifications
Add support for extracting API specifications from kernel-doc comments
and generating C macro invocations for the kernel API specification
framework.
Changes include:
- New kdoc_apispec.py module for generating API spec macros
- Updates to tools/docs/kernel-doc to support the -apispec output format
- Build system integration in Makefile.build
- Support for API-specific sections in kernel-doc comments
kernel-doc recognises the KAPI sections only in -apispec mode, so its
other output formats are unchanged. With CONFIG_KAPI_SPEC=y, Kbuild
generates <file>.apispec.h for each built source that has a contexts:
or context-flags: line plus another KAPI section, and force-includes it
when compiling that source.
Assisted-by: LLM
Signed-off-by: Sasha Levin <sashal@kernel.org>
Diffstat (limited to 'tools/lib/python/kdoc/kdoc_parser.py')
| -rw-r--r-- | tools/lib/python/kdoc/kdoc_parser.py | 101 |
1 files changed, 96 insertions, 5 deletions
diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py index d9ad1ddc87dd..a29fdccda8ae 100644 --- a/tools/lib/python/kdoc/kdoc_parser.py +++ b/tools/lib/python/kdoc/kdoc_parser.py @@ -30,6 +30,23 @@ from kdoc.kdoc_item import KdocItem # Allow whitespace at end of comment start. doc_start = KernRe(r'^/\*\*\s*$', cache=False) +# Sections that are allowed to be duplicated for API specifications +# These represent lists of items (multiple errors, signals, etc.) +ALLOWED_DUPLICATE_SECTIONS = { + 'param', '@param', + 'error', '@error', + 'signal', '@signal', + 'lock', '@lock', + 'side-effect', '@side-effect', + 'state-trans', '@state-trans', + 'capability', '@capability', + 'constraint', '@constraint', + 'validation-group', '@validation-group', + 'validation-rule', '@validation-rule', + 'validation-flag', '@validation-flag', + 'struct-field', '@struct-field', +} + doc_end = KernRe(r'\*/', cache=False) doc_com = KernRe(r'\s*\*\s*', cache=False) doc_com_body = KernRe(r'\s*\* ?', cache=False) @@ -48,6 +65,71 @@ doc_sect = doc_com + \ KernRe(r'\s*(@[.\w]+|@\.\.\.|' + known_section_names + r')\s*:([^:].*)?$', flags=re.I, cache=False) +# API specification section names (for KAPI spec framework), only +# recognized when generating -apispec output +# Format: (base_name, has_count_variant, has_other_variants) +# Sections with has_count_variant=True need negative lookahead in kapi_doc_sect +# to avoid matching 'error' when 'error-count' is intended +_kapi_base_sections = [ + # (name, needs_lookahead, additional_variants) + ('api-type', False, []), + ('api-version', False, []), + ('param', True, []), # has param-count + ('struct', True, ['struct-type', 'struct-field', 'struct-field-[a-z\\-]+']), + ('validation-group', False, []), + ('validation-policy', False, []), + ('validation-flag', False, []), + ('validation-rule', False, []), + ('error', True, ['error-code', 'error-condition']), + ('capability', True, []), + ('signal', True, []), + ('lock', True, []), + ('context-flags', False, []), + ('contexts', False, []), + ('return', True, ['return-type', 'return-check', 'return-check-type', + 'return-success', 'return-desc']), + ('long-desc', False, []), + ('constraint', True, []), + ('side-effect', True, []), + ('state-trans', True, []), +] + +def _build_kapi_patterns(): + """Build KAPI section patterns from the base definitions.""" + validation_parts = [] # For kapi_known_sections (simple validation) + parsing_parts = [] # For kapi_doc_sect (with negative lookaheads) + + for name, has_count, variants in _kapi_base_sections: + # Add base name (with optional @ prefix) + validation_parts.append(f'@?{name}') + if has_count: + # Need negative lookahead to not match 'name-count' or 'name-*' + parsing_parts.append(f'@?{name}(?!-)') + validation_parts.append(f'@?{name}-count') + parsing_parts.append(f'@?{name}-count') + else: + parsing_parts.append(f'@?{name}') + + # Add variants + for variant in variants: + validation_parts.append(f'@?{variant}') + parsing_parts.append(f'@?{variant}') + + # Add catch-all for kapi-* extensions + validation_parts.append(r'@?kapi-.*') + parsing_parts.append(r'@?kapi-.*') + + return '|'.join(validation_parts), '|'.join(parsing_parts) + +_kapi_validation_pattern, _kapi_parsing_pattern = _build_kapi_patterns() + +kapi_known_sections = KernRe(known_section_names + '|' + + _kapi_validation_pattern, flags=re.I) +kapi_doc_sect = doc_com + \ + KernRe(r'\s*(@[.\w\-]+|@\.\.\.|' + known_section_names + '|' + + _kapi_parsing_pattern + r')\s*:([^:].*)?$', + flags=re.I, cache=False) + doc_content = doc_com_body + KernRe(r'(.*)', cache=False) doc_inline_start = KernRe(r'^\s*/\*\*\s*$', cache=False) doc_inline_sect = KernRe(r'\s*\*\s*(@\s*[\w][\w\.]*\s*):(.*)', cache=False) @@ -214,7 +296,9 @@ class KernelEntry: else: if name in self.sections and self.sections[name] != "": # Only warn on user-specified duplicate section names - if name != SECTION_DEFAULT: + # Skip warning for sections that are expected to have duplicates + # (like error, param, signal, etc. for API specifications) + if name != SECTION_DEFAULT and name not in ALLOWED_DUPLICATE_SECTIONS: self.emit_msg(self.new_start_line, f"duplicate section name '{name}'") # Treat as a new paragraph - add a blank line @@ -255,6 +339,13 @@ class KernelDoc: self.xforms = xforms self.store_src = store_src + if self.config.apispec: + self.known_sections = kapi_known_sections + self.doc_sect = kapi_doc_sect + else: + self.known_sections = known_sections + self.doc_sect = doc_sect + tokenizer_set_log(self.config.log, f"{self.fname}: CMatch: ") # Initial state for the state machines @@ -611,7 +702,7 @@ class KernelDoc: """ for section in self.entry.sections: if section not in self.entry.parameterlist and \ - not known_sections.search(section): + not self.known_sections.search(section): hint = self.get_suggestions_hint(section, self.entry.parameterlist) if decl_type == 'function': dname = f"{decl_type} parameter" @@ -1314,12 +1405,12 @@ class KernelDoc: """ Helper function to determine if a new section is being started. """ - if doc_sect.search(line): + if self.doc_sect.search(line): self.state = state.BODY # # Pick out the name of our new section, tweaking it if need be. # - newsection = doc_sect.group(1) + newsection = self.doc_sect.group(1) if newsection.lower() == 'description': newsection = 'Description' elif newsection.lower() == 'context': @@ -1334,7 +1425,7 @@ class KernelDoc: # # Initialize the contents, and get the new section going. # - newcontents = doc_sect.group(2) + newcontents = self.doc_sect.group(2) if not newcontents: newcontents = "" self.dump_section() |
