# 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()