#!/usr/bin/env vpython3
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
r"""Automatically fetch, build, and run clang-tidy from source.
This script seeks to automate the steps detailed in docs/clang_tidy.md.
Example: the following command disables clang-tidy's default checks (-*) and
enables the clang static analyzer checks.
tools/clang/scripts/clang_tidy_tool.py \\
--checks='-*,clang-analyzer-*,-clang-analyzer-alpha*' \\
--header-filter='.*' \\
out/Release chrome
The same, but checks the changes only.
git diff -U5 | tools/clang/scripts/clang_tidy_tool.py \\
--diff \\
--checks='-*,clang-analyzer-*,-clang-analyzer-alpha*' \\
--header-filter='.*' \\
out/Release chrome
"""
from __future__ import print_function
import argparse
import os
import subprocess
import sys
import update
import build_clang_tools_extra
def GetBinaryPath(build_dir, binary):
if sys.platform == 'win32':
binary += '.exe'
return os.path.join(build_dir, 'bin', binary)
def BuildNinjaTarget(out_dir, ninja_target):
args = ['autoninja', '-C', out_dir, ninja_target]
subprocess.check_call(args, shell=sys.platform == 'win32')
def GenerateCompDb(out_dir):
gen_compdb_script = os.path.join(
os.path.dirname(__file__), 'generate_compdb.py')
comp_db_file_path = os.path.join(out_dir, 'compile_commands.json')
args = [
sys.executable,
gen_compdb_script,
'-p',
out_dir,
'-o',
comp_db_file_path,
]
subprocess.check_call(args)
# The resulting CompDb file includes /showIncludes which causes clang-tidy to
# output a lot of unnecessary text to the console.
with open(comp_db_file_path, 'r') as comp_db_file:
comp_db_data = comp_db_file.read();
# The trailing space on /showIncludes helps keep single-spaced flags.
comp_db_data = comp_db_data.replace('/showIncludes ', '')
with open(comp_db_file_path, 'w') as comp_db_file:
comp_db_file.write(comp_db_data)
def RunClangTidy(checks, header_filter, auto_fix, clang_src_dir,
clang_build_dir, out_dir, ninja_target):
"""Invoke the |run-clang-tidy.py| script."""
run_clang_tidy_script = os.path.join(
clang_src_dir, 'clang-tools-extra', 'clang-tidy', 'tool',
'run-clang-tidy.py')
clang_tidy_binary = GetBinaryPath(clang_build_dir, 'clang-tidy')
clang_apply_rep_binary = GetBinaryPath(clang_build_dir,
'clang-apply-replacements')
args = [
sys.executable,
run_clang_tidy_script,
'-quiet',
'-p',
out_dir,
'-clang-tidy-binary',
clang_tidy_binary,
'-clang-apply-replacements-binary',
clang_apply_rep_binary,
]
if checks:
args.append('-checks={}'.format(checks))
if header_filter:
args.append('-header-filter={}'.format(header_filter))
if auto_fix:
args.append('-fix')
args.append(ninja_target)
subprocess.check_call(args)
def RunClangTidyDiff(checks, auto_fix, clang_src_dir, clang_build_dir, out_dir):
"""Invoke the |clang-tidy-diff.py| script over the diff from stdin."""
clang_tidy_diff_script = os.path.join(
clang_src_dir, 'clang-tools-extra', 'clang-tidy', 'tool',
'clang-tidy-diff.py')
clang_tidy_binary = GetBinaryPath(clang_build_dir, 'clang-tidy')
args = [
clang_tidy_diff_script,
'-quiet',
'-p1',
'-path',
out_dir,
'-clang-tidy-binary',
clang_tidy_binary,
]
if checks:
args.append('-checks={}'.format(checks))
if auto_fix:
args.append('-fix')
subprocess.check_call(args)
def main():
script_name = sys.argv[0]
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__)
parser.add_argument(
'--fetch',
nargs='?',
const=update.CLANG_REVISION,
help='Fetch and build clang sources')
parser.add_argument(
'--build',
action='store_true',
help='build clang sources to get clang-tidy')
parser.add_argument(
'--diff',
action='store_true',
default=False,
help ='read diff from the stdin and check it')
parser.add_argument('--clang-src-dir', type=str,
help='override llvm and clang checkout location')
parser.add_argument('--clang-build-dir', type=str,
help='override clang build dir location')
parser.add_argument('--checks', help='passed to clang-tidy')
parser.add_argument('--header-filter', help='passed to clang-tidy')
parser.add_argument(
'--auto-fix',
action='store_true',
help='tell clang-tidy to auto-fix errors')
parser.add_argument('OUT_DIR', help='where we are building Chrome')
parser.add_argument('NINJA_TARGET', help='ninja target')
args = parser.parse_args()
steps = []
# If the user hasn't provided a clang checkout and build dir, checkout and
# build clang-tidy where update.py would.
if not args.clang_src_dir:
args.clang_src_dir = build_clang_tools_extra.GetCheckoutDir(args.OUT_DIR)
if not args.clang_build_dir:
args.clang_build_dir = build_clang_tools_extra.GetBuildDir(args.OUT_DIR)
elif (args.clang_build_dir and not
os.path.isfile(GetBinaryPath(args.clang_build_dir, 'clang-tidy'))):
sys.exit('clang-tidy binary doesn\'t exist at ' +
GetBinaryPath(args.clang_build_dir, 'clang-tidy'))
if args.fetch:
steps.append(('Fetching LLVM sources', lambda:
build_clang_tools_extra.FetchLLVM(args.clang_src_dir,
args.fetch)))
if args.build:
steps.append(('Building clang-tidy',
lambda: build_clang_tools_extra.BuildTargets(
args.clang_build_dir,
['clang-tidy', 'clang-apply-replacements'])))
steps += [
('Building ninja target: %s' % args.NINJA_TARGET,
lambda: BuildNinjaTarget(args.OUT_DIR, args.NINJA_TARGET)),
('Generating compilation DB', lambda: GenerateCompDb(args.OUT_DIR))
]
if args.diff:
steps += [
('Running clang-tidy on diff', lambda: RunClangTidyDiff(
args.checks, args.auto_fix, args.clang_src_dir, args.
clang_build_dir, args.OUT_DIR)),
]
else:
steps += [
('Running clang-tidy',
lambda: RunClangTidy(args.checks, args.header_filter,
args.auto_fix, args.clang_src_dir,
args.clang_build_dir, args.OUT_DIR,
args.NINJA_TARGET)),
]
# Run the steps in sequence.
for i, (msg, step_func) in enumerate(steps):
# Print progress message
print('-- %s %s' % (script_name, '-' * (80 - len(script_name) - 4)))
print('-- [%d/%d] %s' % (i + 1, len(steps), msg))
print(80 * '-')
step_func()
return 0
if __name__ == '__main__':
sys.exit(main())