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

Revert "scripts/clang-tools: Handle included .c files in gen_compile_commands"

This reverts commit 9362d34acf91a706c543d919ade3e651b9bd2d6f.

Dmitry Vyukov reported that commit 9362d34acf91 ("scripts/clang-tools:
Handle included .c files in gen_compile_commands") generates false
entries in some cases for C files that are included in other C files but
not meant for standalone compilation.

For properly supporting clangd, including .c files is discouraged.

Reported-by: Dmitry Vyukov <dvyukov@google.com>
Closes: https://lore.kernel.org/r/CACT4Y+Z8aCz0XcoJx9XXPHZSZHxGF8Kx9iUbFarhpTSEPDhMfg@mail.gmail.com
Acked-by: Nathan Chancellor <nathan@kernel.org>
Acked-by: Dmitry Vyukov <dvyukov@google.com>
Fixes: 9362d34acf91 ("scripts/clang-tools: Handle included .c files in gen_compile_commands")
Link: https://patch.msgid.link/20251217-revert-scripts-clang-rools-handle-included-c-files-v1-1-def5651446da@kernel.org
Signed-off-by: Nicolas Schier <nsc@kernel.org>

+7 -128
+7 -128
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 - 30 24 # The tools/ directory adopts a different build system, and produces .cmd 31 25 # files in a different format. Do not support it. 32 26 _EXCLUDE_DIRS = ['.git', 'Documentation', 'include', 'tools'] ··· 82 88 The path to a .cmd file. 83 89 """ 84 90 91 + filename_matcher = re.compile(_FILENAME_PATTERN) 85 92 exclude_dirs = [ os.path.join(directory, d) for d in _EXCLUDE_DIRS ] 86 93 87 94 for dirpath, dirnames, filenames in os.walk(directory, topdown=True): ··· 92 97 continue 93 98 94 99 for filename in filenames: 95 - if _FILENAME_MATCHER.match(filename): 100 + if filename_matcher.match(filename): 96 101 yield os.path.join(dirpath, filename) 97 102 98 103 ··· 149 154 yield to_cmdfile(mod_line.rstrip()) 150 155 151 156 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 - 231 157 def process_line(root_directory, command_prefix, file_path): 232 - """Extracts information from a .cmd line and creates entries from it. 158 + """Extracts information from a .cmd line and creates an entry from it. 233 159 234 160 Args: 235 161 root_directory: The directory that was searched for .cmd files. Usually ··· 160 244 Usually relative to root_directory, but sometimes absolute. 161 245 162 246 Returns: 163 - A list of entries to append to compile_commands (may include multiple 164 - entries if the source file includes other .c files). 247 + An entry to append to compile_commands. 165 248 166 249 Raises: 167 250 ValueError: Could not find the extracted file based on file_path and ··· 176 261 abs_path = os.path.realpath(os.path.join(root_directory, file_path)) 177 262 if not os.path.exists(abs_path): 178 263 raise ValueError('File %s not found' % abs_path) 179 - 180 - entries = [] 181 - 182 - # Create entry for the main source file. 183 - main_entry = { 264 + return { 184 265 'directory': root_directory, 185 266 'file': abs_path, 186 267 'command': prefix + file_path, 187 268 } 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 220 269 221 270 222 271 def main(): ··· 213 334 result = line_matcher.match(f.readline()) 214 335 if result: 215 336 try: 216 - entries = process_line(directory, result.group('command_prefix'), 337 + entry = process_line(directory, result.group('command_prefix'), 217 338 result.group('file_path')) 218 - compile_commands.extend(entries) 339 + compile_commands.append(entry) 219 340 except ValueError as err: 220 341 logging.info('Could not add line from %s: %s', 221 342 cmdfile, err)