Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 <patso@likewhatevs.io>
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
Tested-by: Justin Stitt <justinstitt@google.com>
Tested-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://patch.msgid.link/20251008004615.2690081-1-patso@likewhatevs.io
Signed-off-by: Nicolas Schier <nsc@kernel.org>

authored by

Pat Somaru and committed by
Nicolas Schier
9362d34a 2d7eda1d

+128 -7
+128 -7
scripts/clang-tools/gen_compile_commands.py
··· 21 21 _FILENAME_PATTERN = r'^\..*\.cmd$' 22 22 _LINE_PATTERN = r'^(saved)?cmd_[^ ]*\.o := (?P<command_prefix>.* )(?P<file_path>[^ ]*\.[cS]) *(;|$)' 23 23 _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 + 24 30 # The tools/ directory adopts a different build system, and produces .cmd 25 31 # files in a different format. Do not support it. 26 32 _EXCLUDE_DIRS = ['.git', 'Documentation', 'include', 'tools'] ··· 88 82 The path to a .cmd file. 89 83 """ 90 84 91 - filename_matcher = re.compile(_FILENAME_PATTERN) 92 85 exclude_dirs = [ os.path.join(directory, d) for d in _EXCLUDE_DIRS ] 93 86 94 87 for dirpath, dirnames, filenames in os.walk(directory, topdown=True): ··· 97 92 continue 98 93 99 94 for filename in filenames: 100 - if filename_matcher.match(filename): 95 + if _FILENAME_MATCHER.match(filename): 101 96 yield os.path.join(dirpath, filename) 102 97 103 98 ··· 154 149 yield to_cmdfile(mod_line.rstrip()) 155 150 156 151 152 + def extract_includes_from_file(source_file, root_directory): 153 + """Extract #include statements from a C file. 154 + 155 + Args: 156 + source_file: Path to the source .c file to analyze 157 + root_directory: Root directory for resolving relative paths 158 + 159 + Returns: 160 + List of header files that should be included (without quotes/brackets) 161 + """ 162 + includes = [] 163 + if not os.path.exists(source_file): 164 + return includes 165 + 166 + try: 167 + with open(source_file, 'r') as f: 168 + for line in f: 169 + line = line.strip() 170 + # Look for #include statements. 171 + # Match both #include "header.h" and #include <header.h>. 172 + match = _INCLUDE_PATTERN.match(line) 173 + if match: 174 + header = match.group(1) 175 + # Skip including other .c files to avoid circular includes. 176 + if not header.endswith('.c'): 177 + # For relative includes (quoted), resolve path relative to source file. 178 + if '"' in line: 179 + src_dir = os.path.dirname(source_file) 180 + header_path = os.path.join(src_dir, header) 181 + if os.path.exists(header_path): 182 + rel_header = os.path.relpath(header_path, root_directory) 183 + includes.append(rel_header) 184 + else: 185 + includes.append(header) 186 + else: 187 + # System include like <linux/sched.h>. 188 + includes.append(header) 189 + except IOError: 190 + pass 191 + 192 + return includes 193 + 194 + 195 + def find_included_c_files(source_file, root_directory): 196 + """Find .c files that are included by the given source file. 197 + 198 + Args: 199 + source_file: Path to the source .c file 200 + root_directory: Root directory for resolving relative paths 201 + 202 + Yields: 203 + Full paths to included .c files 204 + """ 205 + if not os.path.exists(source_file): 206 + return 207 + 208 + try: 209 + with open(source_file, 'r') as f: 210 + for line in f: 211 + line = line.strip() 212 + # Look for #include "*.c" patterns. 213 + match = _C_INCLUDE_PATTERN.match(line) 214 + if match: 215 + included_file = match.group(1) 216 + # Handle relative paths. 217 + if not os.path.isabs(included_file): 218 + src_dir = os.path.dirname(source_file) 219 + included_file = os.path.join(src_dir, included_file) 220 + 221 + # Normalize the path. 222 + included_file = os.path.normpath(included_file) 223 + 224 + # Check if the file exists. 225 + if os.path.exists(included_file): 226 + yield included_file 227 + except IOError: 228 + pass 229 + 230 + 157 231 def process_line(root_directory, command_prefix, file_path): 158 - """Extracts information from a .cmd line and creates an entry from it. 232 + """Extracts information from a .cmd line and creates entries from it. 159 233 160 234 Args: 161 235 root_directory: The directory that was searched for .cmd files. Usually ··· 244 160 Usually relative to root_directory, but sometimes absolute. 245 161 246 162 Returns: 247 - An entry to append to compile_commands. 163 + A list of entries to append to compile_commands (may include multiple 164 + entries if the source file includes other .c files). 248 165 249 166 Raises: 250 167 ValueError: Could not find the extracted file based on file_path and ··· 261 176 abs_path = os.path.realpath(os.path.join(root_directory, file_path)) 262 177 if not os.path.exists(abs_path): 263 178 raise ValueError('File %s not found' % abs_path) 264 - return { 179 + 180 + entries = [] 181 + 182 + # Create entry for the main source file. 183 + main_entry = { 265 184 'directory': root_directory, 266 185 'file': abs_path, 267 186 'command': prefix + file_path, 268 187 } 188 + entries.append(main_entry) 189 + 190 + # Find and create entries for included .c files. 191 + for included_c_file in find_included_c_files(abs_path, root_directory): 192 + # For included .c files, create a compilation command that: 193 + # 1. Uses the same compilation flags as the parent file 194 + # 2. But compiles the included file directly (not the parent) 195 + # 3. Includes necessary headers from the parent file for proper macro resolution 196 + 197 + # Convert absolute path to relative for the command. 198 + rel_path = os.path.relpath(included_c_file, root_directory) 199 + 200 + # Extract includes from the parent file to provide proper compilation context. 201 + extra_includes = '' 202 + try: 203 + parent_includes = extract_includes_from_file(abs_path, root_directory) 204 + if parent_includes: 205 + extra_includes = ' ' + ' '.join('-include ' + inc for inc in parent_includes) 206 + except IOError: 207 + pass 208 + 209 + included_entry = { 210 + 'directory': root_directory, 211 + 'file': included_c_file, 212 + # Use the same compilation prefix but target the included file directly. 213 + # Add extra headers for proper macro resolution. 214 + 'command': prefix + extra_includes + ' ' + rel_path, 215 + } 216 + entries.append(included_entry) 217 + logging.debug('Added entry for included file: %s', included_c_file) 218 + 219 + return entries 269 220 270 221 271 222 def main(): ··· 334 213 result = line_matcher.match(f.readline()) 335 214 if result: 336 215 try: 337 - entry = process_line(directory, result.group('command_prefix'), 216 + entries = process_line(directory, result.group('command_prefix'), 338 217 result.group('file_path')) 339 - compile_commands.append(entry) 218 + compile_commands.extend(entries) 340 219 except ValueError as err: 341 220 logging.info('Could not add line from %s: %s', 342 221 cmdfile, err)