| #!/usr/bin/env python |
| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # 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 |
| """ |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| |
| 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, header_filter, 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 header_filter: |
| args.append('-header-filter={}'.format(header_filter)) |
| |
| 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', action='store_true', 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))) |
| |
| 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.header_filter, args.auto_fix, args.clang_src_dir, |
| args.clang_build_dir, args.OUT_DIR, args.NINJA_TARGET)), |
| ] |
| 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()) |