18d541aeeSJoakim Bech#!/usr/bin/env python3 28d541aeeSJoakim Bech# SPDX-License-Identifier: GPL-2.0 38d541aeeSJoakim Bech# 48d541aeeSJoakim Bech# Copyright (C) Google LLC, 2018 58d541aeeSJoakim Bech# 68d541aeeSJoakim Bech# Author: Tom Roeder <tmroeder@google.com> 78d541aeeSJoakim Bech# Ported and modified for U-Boot by Joao Marcos Costa <jmcosta944@gmail.com> 88d541aeeSJoakim Bech# Briefly documented at doc/build/gen_compile_commands.rst 98d541aeeSJoakim Bech# 108d541aeeSJoakim Bech# Ported and modified for OP-TEE by Joakim Bech <joakim.bech@linaro.org> 118d541aeeSJoakim Bech"""A tool for generating compile_commands.json in OP-TEE.""" 128d541aeeSJoakim Bech 138d541aeeSJoakim Bechimport argparse 148d541aeeSJoakim Bechimport json 158d541aeeSJoakim Bechimport logging 168d541aeeSJoakim Bechimport os 178d541aeeSJoakim Bechimport re 188d541aeeSJoakim Bechimport subprocess 198d541aeeSJoakim Bechimport sys 208d541aeeSJoakim Bech 218d541aeeSJoakim Bech_DEFAULT_OUTPUT = 'compile_commands.json' 228d541aeeSJoakim Bech_DEFAULT_LOG_LEVEL = 'WARNING' 238d541aeeSJoakim Bech 248d541aeeSJoakim Bech_FILENAME_PATTERN = r'^\..*\.cmd$' 258d541aeeSJoakim Bech_LINE_PATTERN = r'^old-cmd[^ ]* := (?:/usr/bin/ccache )?(.*) -c (\S+)' 268d541aeeSJoakim Bech 278d541aeeSJoakim Bech_VALID_LOG_LEVELS = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] 288d541aeeSJoakim Bech# The tools/ directory adopts a different build system, and produces .cmd 298d541aeeSJoakim Bech# files in a different format. Do not support it. 308d541aeeSJoakim Bech_EXCLUDE_DIRS = ['.git', 'Documentation', 'include', 'tools'] 318d541aeeSJoakim Bech 328d541aeeSJoakim Bech 338d541aeeSJoakim Bechdef parse_arguments(): 348d541aeeSJoakim Bech """Sets up and parses command-line arguments. 358d541aeeSJoakim Bech 368d541aeeSJoakim Bech Returns: 378d541aeeSJoakim Bech log_level: A logging level to filter log output. 388d541aeeSJoakim Bech directory: The work directory where the objects were built. 398d541aeeSJoakim Bech ar: Command used for parsing .a archives. 408d541aeeSJoakim Bech output: Where to write the compile-commands JSON file. 418d541aeeSJoakim Bech paths: The list of files/directories to handle to find .cmd files. 428d541aeeSJoakim Bech """ 438d541aeeSJoakim Bech usage = 'Creates a compile_commands.json database from OP-TEE .cmd files' 448d541aeeSJoakim Bech parser = argparse.ArgumentParser(description=usage) 458d541aeeSJoakim Bech 468d541aeeSJoakim Bech directory_help = ('specify the output directory used for the OP-TEE build ' 478d541aeeSJoakim Bech '(defaults to the working directory)') 488d541aeeSJoakim Bech parser.add_argument('-d', '--directory', type=str, default='.', 498d541aeeSJoakim Bech help=directory_help) 508d541aeeSJoakim Bech 518d541aeeSJoakim Bech output_help = ('path to the output command database (defaults to ' + 528d541aeeSJoakim Bech _DEFAULT_OUTPUT + ')') 538d541aeeSJoakim Bech parser.add_argument('-o', '--output', type=str, default=_DEFAULT_OUTPUT, 548d541aeeSJoakim Bech help=output_help) 558d541aeeSJoakim Bech 568d541aeeSJoakim Bech log_level_help = ('the level of log messages to produce (defaults to ' + 578d541aeeSJoakim Bech _DEFAULT_LOG_LEVEL + ')') 588d541aeeSJoakim Bech parser.add_argument('--log_level', choices=_VALID_LOG_LEVELS, 598d541aeeSJoakim Bech default=_DEFAULT_LOG_LEVEL, help=log_level_help) 608d541aeeSJoakim Bech 618d541aeeSJoakim Bech ar_help = 'command used for parsing .a archives' 628d541aeeSJoakim Bech parser.add_argument('-a', '--ar', type=str, default='llvm-ar', 638d541aeeSJoakim Bech help=ar_help) 648d541aeeSJoakim Bech 658d541aeeSJoakim Bech paths_help = ('directories to search or files to parse ' 668d541aeeSJoakim Bech '(files should be *.o, *.a, or modules.order). ' 678d541aeeSJoakim Bech 'If nothing is specified, the current directory is searched') 688d541aeeSJoakim Bech parser.add_argument('paths', type=str, nargs='*', help=paths_help) 698d541aeeSJoakim Bech 708d541aeeSJoakim Bech args = parser.parse_args() 718d541aeeSJoakim Bech 728d541aeeSJoakim Bech return (args.log_level, 738d541aeeSJoakim Bech os.path.abspath(args.directory), 748d541aeeSJoakim Bech args.output, 758d541aeeSJoakim Bech args.ar, 768d541aeeSJoakim Bech args.paths if len(args.paths) > 0 else [args.directory]) 778d541aeeSJoakim Bech 788d541aeeSJoakim Bech 798d541aeeSJoakim Bechdef cmdfiles_in_dir(directory): 808d541aeeSJoakim Bech """Generate the iterator of .cmd files found under the directory. 818d541aeeSJoakim Bech 828d541aeeSJoakim Bech Walk under the given directory, and yield every .cmd file found. 838d541aeeSJoakim Bech 848d541aeeSJoakim Bech Args: 858d541aeeSJoakim Bech directory: The directory to search for .cmd files. 868d541aeeSJoakim Bech 878d541aeeSJoakim Bech Yields: 888d541aeeSJoakim Bech The path to a .cmd file. 898d541aeeSJoakim Bech """ 908d541aeeSJoakim Bech 918d541aeeSJoakim Bech filename_matcher = re.compile(_FILENAME_PATTERN) 928d541aeeSJoakim Bech exclude_dirs = [os.path.join(directory, d) for d in _EXCLUDE_DIRS] 938d541aeeSJoakim Bech 948d541aeeSJoakim Bech for dirpath, dirnames, filenames in os.walk(directory, topdown=True): 958d541aeeSJoakim Bech # Prune unwanted directories. 968d541aeeSJoakim Bech if dirpath in exclude_dirs: 978d541aeeSJoakim Bech dirnames[:] = [] 988d541aeeSJoakim Bech continue 998d541aeeSJoakim Bech 1008d541aeeSJoakim Bech for filename in filenames: 1018d541aeeSJoakim Bech if filename_matcher.match(filename): 1028d541aeeSJoakim Bech yield os.path.join(dirpath, filename) 1038d541aeeSJoakim Bech 1048d541aeeSJoakim Bech 1058d541aeeSJoakim Bechdef to_cmdfile(path): 1068d541aeeSJoakim Bech """Return the path of .cmd file used for the given build artifact 1078d541aeeSJoakim Bech 1088d541aeeSJoakim Bech Args: 1098d541aeeSJoakim Bech Path: file path 1108d541aeeSJoakim Bech 1118d541aeeSJoakim Bech Returns: 1128d541aeeSJoakim Bech The path to .cmd file 1138d541aeeSJoakim Bech """ 1148d541aeeSJoakim Bech dir, base = os.path.split(path) 1158d541aeeSJoakim Bech return os.path.join(dir, '.' + base + '.cmd') 1168d541aeeSJoakim Bech 1178d541aeeSJoakim Bech 1188d541aeeSJoakim Bechdef cmdfiles_for_a(archive, ar): 1198d541aeeSJoakim Bech """Generate the iterator of .cmd files associated with the archive. 1208d541aeeSJoakim Bech 1218d541aeeSJoakim Bech Parse the given archive, and yield every .cmd file used to build it. 1228d541aeeSJoakim Bech 1238d541aeeSJoakim Bech Args: 1248d541aeeSJoakim Bech archive: The archive to parse 1258d541aeeSJoakim Bech 1268d541aeeSJoakim Bech Yields: 1278d541aeeSJoakim Bech The path to every .cmd file found 1288d541aeeSJoakim Bech """ 1298d541aeeSJoakim Bech for obj in subprocess.check_output([ar, '-t', archive]).decode().split(): 1308d541aeeSJoakim Bech yield to_cmdfile(obj) 1318d541aeeSJoakim Bech 1328d541aeeSJoakim Bech 1338d541aeeSJoakim Bechdef cmdfiles_for_modorder(modorder): 1348d541aeeSJoakim Bech """Generate the iterator of .cmd files associated with the modules.order. 1358d541aeeSJoakim Bech 1368d541aeeSJoakim Bech Parse the given modules.order, and yield every .cmd file used to build the 1378d541aeeSJoakim Bech contained modules. 1388d541aeeSJoakim Bech 1398d541aeeSJoakim Bech Args: 1408d541aeeSJoakim Bech modorder: The modules.order file to parse 1418d541aeeSJoakim Bech 1428d541aeeSJoakim Bech Yields: 1438d541aeeSJoakim Bech The path to every .cmd file found 1448d541aeeSJoakim Bech """ 1458d541aeeSJoakim Bech with open(modorder) as f: 1468d541aeeSJoakim Bech for line in f: 1478d541aeeSJoakim Bech obj = line.rstrip() 1488d541aeeSJoakim Bech base, ext = os.path.splitext(obj) 1498d541aeeSJoakim Bech if ext != '.o': 1508d541aeeSJoakim Bech sys.exit('{}: module path must end with .o'.format(obj)) 1518d541aeeSJoakim Bech mod = base + '.mod' 1528d541aeeSJoakim Bech # Read from *.mod, to get a list of objects that compose the 1538d541aeeSJoakim Bech # module. 1548d541aeeSJoakim Bech with open(mod) as m: 1558d541aeeSJoakim Bech for mod_line in m: 1568d541aeeSJoakim Bech yield to_cmdfile(mod_line.rstrip()) 1578d541aeeSJoakim Bech 1588d541aeeSJoakim Bech 1598d541aeeSJoakim Bechdef process_line(root_directory, command_prefix, file_path): 1608d541aeeSJoakim Bech """Extracts information from a .cmd line and creates an entry from it. 1618d541aeeSJoakim Bech 1628d541aeeSJoakim Bech Args: 1638d541aeeSJoakim Bech root_directory: The directory that was searched for .cmd files. Usually 1648d541aeeSJoakim Bech used directly in the "directory" entry in compile_commands.json. 1658d541aeeSJoakim Bech command_prefix: The extracted command line, up to the last element. 1668d541aeeSJoakim Bech file_path: The .c file from the end of the extracted command. 1678d541aeeSJoakim Bech Usually relative to root_directory, but sometimes absolute. 1688d541aeeSJoakim Bech 1698d541aeeSJoakim Bech Returns: 1708d541aeeSJoakim Bech An entry to append to compile_commands. 1718d541aeeSJoakim Bech 1728d541aeeSJoakim Bech Raises: 1738d541aeeSJoakim Bech ValueError: Could not find the extracted file based on file_path and 1748d541aeeSJoakim Bech root_directory or file_directory. 1758d541aeeSJoakim Bech """ 1768d541aeeSJoakim Bech # The .cmd files are intended to be included directly by Make, so they 1778d541aeeSJoakim Bech # escape the pound sign '#', either as '\#' or '$(pound)' (depending on the 1788d541aeeSJoakim Bech # kernel version). The compile_commands.json file is not interpreted 1798d541aeeSJoakim Bech # by Make, so this code replaces the escaped version with '#'. 180*fe5af822SShen Jiamin prefix = command_prefix.replace(r'\#', '#').replace('$(pound)', '#') 1818d541aeeSJoakim Bech 1828d541aeeSJoakim Bech # Use os.path.abspath() to normalize the path resolving '.' and '..' . 1838d541aeeSJoakim Bech abs_path = os.path.abspath(os.path.join(root_directory, file_path)) 1848d541aeeSJoakim Bech if not os.path.exists(abs_path): 1858d541aeeSJoakim Bech raise ValueError('File %s not found' % abs_path) 1868d541aeeSJoakim Bech return { 1878d541aeeSJoakim Bech 'directory': root_directory, 1888d541aeeSJoakim Bech 'file': abs_path, 1898d541aeeSJoakim Bech 'command': prefix + file_path, 1908d541aeeSJoakim Bech } 1918d541aeeSJoakim Bech 1928d541aeeSJoakim Bech 1938d541aeeSJoakim Bechdef main(): 1948d541aeeSJoakim Bech """Walks through the directory and finds and parses .cmd files.""" 1958d541aeeSJoakim Bech log_level, directory, output, ar, paths = parse_arguments() 1968d541aeeSJoakim Bech 1978d541aeeSJoakim Bech level = getattr(logging, log_level) 1988d541aeeSJoakim Bech logging.basicConfig(format='%(levelname)s: %(message)s', level=level) 1998d541aeeSJoakim Bech 2008d541aeeSJoakim Bech line_matcher = re.compile(_LINE_PATTERN) 2018d541aeeSJoakim Bech 2028d541aeeSJoakim Bech compile_commands = [] 2038d541aeeSJoakim Bech 2048d541aeeSJoakim Bech for path in paths: 2058d541aeeSJoakim Bech # If 'path' is a directory, handle all .cmd files under it. 2068d541aeeSJoakim Bech # Otherwise, handle .cmd files associated with the file. 2078d541aeeSJoakim Bech # built-in objects are linked via vmlinux.a 2088d541aeeSJoakim Bech # Modules are listed in modules.order. 2098d541aeeSJoakim Bech if os.path.isdir(path): 2108d541aeeSJoakim Bech cmdfiles = cmdfiles_in_dir(path) 2118d541aeeSJoakim Bech elif path.endswith('.a'): 2128d541aeeSJoakim Bech cmdfiles = cmdfiles_for_a(path, ar) 2138d541aeeSJoakim Bech elif path.endswith('modules.order'): 2148d541aeeSJoakim Bech cmdfiles = cmdfiles_for_modorder(path) 2158d541aeeSJoakim Bech else: 2168d541aeeSJoakim Bech sys.exit('{}: unknown file type'.format(path)) 2178d541aeeSJoakim Bech 2188d541aeeSJoakim Bech for cmdfile in cmdfiles: 2198d541aeeSJoakim Bech with open(cmdfile, 'rt') as f: 2208d541aeeSJoakim Bech try: 2218d541aeeSJoakim Bech result = line_matcher.match(f.readline()) 2228d541aeeSJoakim Bech if result: 2238d541aeeSJoakim Bech entry = process_line(directory, result.group(1), 2248d541aeeSJoakim Bech result.group(2)) 2258d541aeeSJoakim Bech compile_commands.append(entry) 2268d541aeeSJoakim Bech except ValueError as err: 2278d541aeeSJoakim Bech logging.info('Could not add line from %s: %s', 2288d541aeeSJoakim Bech cmdfile, err) 2298d541aeeSJoakim Bech except AttributeError as err: 2308d541aeeSJoakim Bech continue 2318d541aeeSJoakim Bech 2328d541aeeSJoakim Bech with open(output, 'wt') as f: 2338d541aeeSJoakim Bech json.dump(compile_commands, f, indent=2, sort_keys=True) 2348d541aeeSJoakim Bech 2358d541aeeSJoakim Bech 2368d541aeeSJoakim Bechif __name__ == '__main__': 2378d541aeeSJoakim Bech main() 238