Skip to content

Commit 9362d34

Browse files
likewhatevsNicolas Schier
authored andcommitted
scripts/clang-tools: Handle included .c files in gen_compile_commands
The gen_compile_commands.py script currently only creates entries for the primary source files found in .cmd files, but some kernel source files text-include others (i.e. kernel/sched/build_policy.c). This prevents tools like clangd from working properly on text-included c files, such as kernel/sched/ext.c because the generated compile_commands.json does not have entries for them. Extend process_line() to detect when a source file includes .c files, and generate additional compile_commands.json entries for them. For included c files, use the same compile flags as their parent and add their parents headers. This enables lsp tools like clangd to work properly on files like kernel/sched/ext.c Signed-off-by: Pat Somaru <[email protected]> Reviewed-by: Nathan Chancellor <[email protected]> Tested-by: Justin Stitt <[email protected]> Tested-by: Eduard Zingerman <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Nicolas Schier <[email protected]>
1 parent 2d7eda1 commit 9362d34

File tree

1 file changed

+128
-7
lines changed

1 file changed

+128
-7
lines changed

scripts/clang-tools/gen_compile_commands.py

Lines changed: 128 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
_FILENAME_PATTERN = r'^\..*\.cmd$'
2222
_LINE_PATTERN = r'^(saved)?cmd_[^ ]*\.o := (?P<command_prefix>.* )(?P<file_path>[^ ]*\.[cS]) *(;|$)'
2323
_VALID_LOG_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
24+
25+
# Pre-compiled regexes for better performance
26+
_INCLUDE_PATTERN = re.compile(r'^\s*#\s*include\s*[<"]([^>"]*)[>"]')
27+
_C_INCLUDE_PATTERN = re.compile(r'^\s*#\s*include\s*"([^"]*\.c)"\s*$')
28+
_FILENAME_MATCHER = re.compile(_FILENAME_PATTERN)
29+
2430
# The tools/ directory adopts a different build system, and produces .cmd
2531
# files in a different format. Do not support it.
2632
_EXCLUDE_DIRS = ['.git', 'Documentation', 'include', 'tools']
@@ -82,7 +88,6 @@ def cmdfiles_in_dir(directory):
8288
The path to a .cmd file.
8389
"""
8490

85-
filename_matcher = re.compile(_FILENAME_PATTERN)
8691
exclude_dirs = [ os.path.join(directory, d) for d in _EXCLUDE_DIRS ]
8792

8893
for dirpath, dirnames, filenames in os.walk(directory, topdown=True):
@@ -92,7 +97,7 @@ def cmdfiles_in_dir(directory):
9297
continue
9398

9499
for filename in filenames:
95-
if filename_matcher.match(filename):
100+
if _FILENAME_MATCHER.match(filename):
96101
yield os.path.join(dirpath, filename)
97102

98103

@@ -149,8 +154,87 @@ def cmdfiles_for_modorder(modorder):
149154
yield to_cmdfile(mod_line.rstrip())
150155

151156

157+
def extract_includes_from_file(source_file, root_directory):
158+
"""Extract #include statements from a C file.
159+
160+
Args:
161+
source_file: Path to the source .c file to analyze
162+
root_directory: Root directory for resolving relative paths
163+
164+
Returns:
165+
List of header files that should be included (without quotes/brackets)
166+
"""
167+
includes = []
168+
if not os.path.exists(source_file):
169+
return includes
170+
171+
try:
172+
with open(source_file, 'r') as f:
173+
for line in f:
174+
line = line.strip()
175+
# Look for #include statements.
176+
# Match both #include "header.h" and #include <header.h>.
177+
match = _INCLUDE_PATTERN.match(line)
178+
if match:
179+
header = match.group(1)
180+
# Skip including other .c files to avoid circular includes.
181+
if not header.endswith('.c'):
182+
# For relative includes (quoted), resolve path relative to source file.
183+
if '"' in line:
184+
src_dir = os.path.dirname(source_file)
185+
header_path = os.path.join(src_dir, header)
186+
if os.path.exists(header_path):
187+
rel_header = os.path.relpath(header_path, root_directory)
188+
includes.append(rel_header)
189+
else:
190+
includes.append(header)
191+
else:
192+
# System include like <linux/sched.h>.
193+
includes.append(header)
194+
except IOError:
195+
pass
196+
197+
return includes
198+
199+
200+
def find_included_c_files(source_file, root_directory):
201+
"""Find .c files that are included by the given source file.
202+
203+
Args:
204+
source_file: Path to the source .c file
205+
root_directory: Root directory for resolving relative paths
206+
207+
Yields:
208+
Full paths to included .c files
209+
"""
210+
if not os.path.exists(source_file):
211+
return
212+
213+
try:
214+
with open(source_file, 'r') as f:
215+
for line in f:
216+
line = line.strip()
217+
# Look for #include "*.c" patterns.
218+
match = _C_INCLUDE_PATTERN.match(line)
219+
if match:
220+
included_file = match.group(1)
221+
# Handle relative paths.
222+
if not os.path.isabs(included_file):
223+
src_dir = os.path.dirname(source_file)
224+
included_file = os.path.join(src_dir, included_file)
225+
226+
# Normalize the path.
227+
included_file = os.path.normpath(included_file)
228+
229+
# Check if the file exists.
230+
if os.path.exists(included_file):
231+
yield included_file
232+
except IOError:
233+
pass
234+
235+
152236
def process_line(root_directory, command_prefix, file_path):
153-
"""Extracts information from a .cmd line and creates an entry from it.
237+
"""Extracts information from a .cmd line and creates entries from it.
154238
155239
Args:
156240
root_directory: The directory that was searched for .cmd files. Usually
@@ -160,7 +244,8 @@ def process_line(root_directory, command_prefix, file_path):
160244
Usually relative to root_directory, but sometimes absolute.
161245
162246
Returns:
163-
An entry to append to compile_commands.
247+
A list of entries to append to compile_commands (may include multiple
248+
entries if the source file includes other .c files).
164249
165250
Raises:
166251
ValueError: Could not find the extracted file based on file_path and
@@ -176,11 +261,47 @@ def process_line(root_directory, command_prefix, file_path):
176261
abs_path = os.path.realpath(os.path.join(root_directory, file_path))
177262
if not os.path.exists(abs_path):
178263
raise ValueError('File %s not found' % abs_path)
179-
return {
264+
265+
entries = []
266+
267+
# Create entry for the main source file.
268+
main_entry = {
180269
'directory': root_directory,
181270
'file': abs_path,
182271
'command': prefix + file_path,
183272
}
273+
entries.append(main_entry)
274+
275+
# Find and create entries for included .c files.
276+
for included_c_file in find_included_c_files(abs_path, root_directory):
277+
# For included .c files, create a compilation command that:
278+
# 1. Uses the same compilation flags as the parent file
279+
# 2. But compiles the included file directly (not the parent)
280+
# 3. Includes necessary headers from the parent file for proper macro resolution
281+
282+
# Convert absolute path to relative for the command.
283+
rel_path = os.path.relpath(included_c_file, root_directory)
284+
285+
# Extract includes from the parent file to provide proper compilation context.
286+
extra_includes = ''
287+
try:
288+
parent_includes = extract_includes_from_file(abs_path, root_directory)
289+
if parent_includes:
290+
extra_includes = ' ' + ' '.join('-include ' + inc for inc in parent_includes)
291+
except IOError:
292+
pass
293+
294+
included_entry = {
295+
'directory': root_directory,
296+
'file': included_c_file,
297+
# Use the same compilation prefix but target the included file directly.
298+
# Add extra headers for proper macro resolution.
299+
'command': prefix + extra_includes + ' ' + rel_path,
300+
}
301+
entries.append(included_entry)
302+
logging.debug('Added entry for included file: %s', included_c_file)
303+
304+
return entries
184305

185306

186307
def main():
@@ -213,9 +334,9 @@ def main():
213334
result = line_matcher.match(f.readline())
214335
if result:
215336
try:
216-
entry = process_line(directory, result.group('command_prefix'),
337+
entries = process_line(directory, result.group('command_prefix'),
217338
result.group('file_path'))
218-
compile_commands.append(entry)
339+
compile_commands.extend(entries)
219340
except ValueError as err:
220341
logging.info('Could not add line from %s: %s',
221342
cmdfile, err)

0 commit comments

Comments
 (0)