| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ Script for building a Chromium CodeQL database.""" |
| import argparse |
| import functools |
| import json |
| import multiprocessing |
| import subprocess |
| import logging |
| import time |
| import os |
| import traceback |
| import gn_sources_tools |
| import targets_to_index |
| |
| from collections import namedtuple |
| |
| CommandOutput = namedtuple("CommandOutput", "output traceback") |
| |
| |
| def log_subprocess_output(output, logger=None): |
| """ Reads from a subprocess's stdout and writes it to `logger`, |
| or, if none given, to stdout. """ |
| if not logger: |
| print(output) |
| else: |
| logger.info(output) |
| |
| |
| class CodeQLDatabase: |
| |
| def __init__(self, src_path, db_path, codeql_binary_path): |
| """ Construct a new `CodeQLDatabase` object. |
| :param src_path: The path to the chromium/src tree. |
| :param db_path: The path where the CodeQL database will be created. |
| :return: returns nothing |
| """ |
| self.db_path = db_path |
| try: |
| process_stdout = subprocess.check_output([ |
| codeql_binary_path, 'database', 'init', f'--source-root={src_path}', |
| '--language=cpp', db_path, '--overwrite' |
| ]) |
| log_subprocess_output(process_stdout) |
| except subprocess.CalledProcessError: |
| # Presumably failed due to an invalid value for db_path. |
| raise ValueError |
| |
| |
| def index_one_target(target_name, |
| src_path, |
| db_path, |
| codeql_binary_path, |
| out_path, |
| logger, |
| ninja_path='ninja', |
| gn_path='gn', |
| logfile=None, |
| reduce_cores_used=False): |
| try: |
| process_stdout = subprocess.check_output([gn_path, 'clean', out_path]) |
| log_subprocess_output(process_stdout) |
| except subprocess.CalledProcessError as e: |
| print("Failed to clean build directory between targets") |
| print("stdout: %s" % e.stdout) |
| print("stderr: %s" % e.stderr) |
| exit(1) |
| db_path = os.path.join(db_path, target_name) |
| os.mkdir(os.path.join(db_path)) |
| |
| start_time = time.time() |
| |
| print("Initializing codeql.") |
| codeql_db = "" |
| try: |
| codeql_db = CodeQLDatabase(src_path, db_path, codeql_binary_path) |
| except ValueError: |
| print("Could not initialize CodeQL database at %s" % db_path) |
| exit(1) |
| |
| print("Tracing compilation.") |
| trace_command = [ |
| codeql_binary_path, 'database', 'trace-command', db_path, |
| f'--working-dir={src_path}', '--', ninja_path, '-C', out_path, target_name |
| ] |
| if reduce_cores_used: |
| usable_cpu_count = int(multiprocessing.cpu_count() / 2) |
| trace_command.extend(['-j', str(usable_cpu_count)]) |
| try: |
| process_stdout = subprocess.check_output(trace_command) |
| log_subprocess_output(process_stdout) |
| except subprocess.CalledProcessError as e: |
| print("CodeQL trace-process failed with return code %s" % e.returncode) |
| print("stdout: %s" % e.stdout) |
| print("stderr: %s" % e.stderr) |
| exit(1) |
| |
| print("Finalizing codeql db.") |
| try: |
| process_stdout = subprocess.check_output( |
| [codeql_binary_path, 'database', 'finalize', '-j=-1', db_path]) |
| log_subprocess_output(process_stdout) |
| except subprocess.CalledProcessError as e: |
| print("CodeQL DB finalization failed with return code %s" % e.returncode) |
| print("stdout: %s" % e.stdout) |
| print("stderr: %s" % e.stderr) |
| print("Database creation complete.") |
| total_time = time.time() - start_time |
| print("Time elapsed:") |
| print(str(total_time)) |
| |
| |
| def main(): |
| logger = logging.getLogger('log') |
| logger.setLevel(logging.INFO) |
| actual_cwd = os.getcwd() |
| script_directory = os.path.dirname(os.path.realpath(__file__)) |
| src_path = os.path.join(script_directory, '..', '..') |
| if actual_cwd != os.path.normpath(src_path): |
| print("Failure: Script must be executed from `chromium/src`. Exiting.") |
| print(actual_cwd) |
| print(src_path) |
| exit(1) |
| |
| print("Parsing command line arguments.") |
| parser = argparse.ArgumentParser( |
| description='Build CodeQL database for Chromium browser process') |
| parser.add_argument( |
| '--out_path', |
| '-o', |
| type=str, |
| default='out/release', |
| help='Relative path inside chromium checkout to build directory') |
| parser.add_argument('--db_path', |
| '-d', |
| type=str, |
| required=True, |
| help='Path to output database') |
| parser.add_argument( |
| '--logfile', |
| '-l', |
| type=str, |
| help="absolute path to logfile for `trace` calls, if desired") |
| parser.add_argument( |
| '--gn_targets', |
| '-g', |
| action='append', |
| type=str, |
| help=( |
| 'name for the specific GN target you want a CodeQL database for ' |
| '(e.g. `//components:components_unittests`); if left blank, indexes ' |
| 'everything')) |
| parser.add_argument( |
| '--codeql_binary_path', |
| '-c', |
| type=str, |
| default='codeql', |
| help=('Path to the codeql binary. If this is not set, the script assumes ' |
| 'it is located at `codeql` somewhere in the user\'s PATH.')) |
| parser.add_argument( |
| '--gn_path', |
| type=str, |
| default='gn', |
| help=('Path to the gn executable. If this is not set, the script assumes ' |
| 'it is located at `gn` somehwere in the user\'s PATH.')) |
| parser.add_argument( |
| '--ninja_path', |
| type=str, |
| default='ninja', |
| help=('Path to the ninja executable. If this is not set, the script ' |
| 'assumes it is located at `ninja` somehwere in the user\'s PATH.')) |
| parser.add_argument( |
| '--reduce_cores_used', |
| default=False, |
| action='store_true', |
| help=('If set, reduces the number of cores used when building a target.')) |
| args = parser.parse_args() |
| |
| if (args.logfile): |
| ch = logging.FileHandler(args.logfile) |
| ch.setFormatter(logging.Formatter('%(message)s')) |
| logger.addHandler(ch) |
| src_path = os.path.abspath(os.path.expanduser(src_path)) |
| args.db_path = os.path.abspath(os.path.expanduser(args.db_path)) |
| |
| # If an args.gn_target is given, index those targets. |
| # Otherwise, index the targets in targets_to_index. |
| actual_targets_to_index = [] |
| if not args.gn_targets: |
| actual_targets_to_index = targets_to_index.full_targets |
| else: |
| actual_targets_to_index = args.gn_targets |
| for target in actual_targets_to_index: |
| index_one_target(target, src_path, args.db_path, args.codeql_binary_path, |
| args.out_path, logger, args.ninja_path, args.gn_path, |
| args.logfile, args.reduce_cores_used) |
| |
| if __name__ == '__main__': |
| main() |