1#!/usr/bin/env python 2# Copyright 2019 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5r"""Automatically fetch, build, and run clang-tidy from source. 6 7This script seeks to automate the steps detailed in docs/clang_tidy.md. 8 9Example: the following command disables clang-tidy's default checks (-*) and 10enables the clang static analyzer checks. 11 12 tools/clang/scripts/clang_tidy_tool.py \\ 13 --checks='-*,clang-analyzer-*,-clang-analyzer-alpha*' \\ 14 --header-filter='.*' \\ 15 out/Release chrome 16 17The same, but checks the changes only. 18 19 git diff -U5 | tools/clang/scripts/clang_tidy_tool.py \\ 20 --diff \\ 21 --checks='-*,clang-analyzer-*,-clang-analyzer-alpha*' \\ 22 --header-filter='.*' \\ 23 out/Release chrome 24""" 25 26from __future__ import print_function 27 28import argparse 29import os 30import subprocess 31import sys 32import update 33 34import build_clang_tools_extra 35 36 37def GetBinaryPath(build_dir, binary): 38 if sys.platform == 'win32': 39 binary += '.exe' 40 return os.path.join(build_dir, 'bin', binary) 41 42 43def BuildNinjaTarget(out_dir, ninja_target): 44 args = ['autoninja', '-C', out_dir, ninja_target] 45 subprocess.check_call(args, shell=sys.platform == 'win32') 46 47 48def GenerateCompDb(out_dir): 49 gen_compdb_script = os.path.join( 50 os.path.dirname(__file__), 'generate_compdb.py') 51 comp_db_file_path = os.path.join(out_dir, 'compile_commands.json') 52 args = [ 53 sys.executable, 54 gen_compdb_script, 55 '-p', 56 out_dir, 57 '-o', 58 comp_db_file_path, 59 ] 60 subprocess.check_call(args) 61 62 # The resulting CompDb file includes /showIncludes which causes clang-tidy to 63 # output a lot of unnecessary text to the console. 64 with open(comp_db_file_path, 'r') as comp_db_file: 65 comp_db_data = comp_db_file.read(); 66 67 # The trailing space on /showIncludes helps keep single-spaced flags. 68 comp_db_data = comp_db_data.replace('/showIncludes ', '') 69 70 with open(comp_db_file_path, 'w') as comp_db_file: 71 comp_db_file.write(comp_db_data) 72 73 74def RunClangTidy(checks, header_filter, auto_fix, clang_src_dir, 75 clang_build_dir, out_dir, ninja_target): 76 """Invoke the |run-clang-tidy.py| script.""" 77 run_clang_tidy_script = os.path.join( 78 clang_src_dir, 'clang-tools-extra', 'clang-tidy', 'tool', 79 'run-clang-tidy.py') 80 81 clang_tidy_binary = GetBinaryPath(clang_build_dir, 'clang-tidy') 82 clang_apply_rep_binary = GetBinaryPath(clang_build_dir, 83 'clang-apply-replacements') 84 85 args = [ 86 sys.executable, 87 run_clang_tidy_script, 88 '-quiet', 89 '-p', 90 out_dir, 91 '-clang-tidy-binary', 92 clang_tidy_binary, 93 '-clang-apply-replacements-binary', 94 clang_apply_rep_binary, 95 ] 96 97 if checks: 98 args.append('-checks={}'.format(checks)) 99 100 if header_filter: 101 args.append('-header-filter={}'.format(header_filter)) 102 103 if auto_fix: 104 args.append('-fix') 105 106 args.append(ninja_target) 107 subprocess.check_call(args) 108 109 110def RunClangTidyDiff(checks, auto_fix, clang_src_dir, clang_build_dir, out_dir): 111 """Invoke the |clang-tidy-diff.py| script over the diff from stdin.""" 112 clang_tidy_diff_script = os.path.join( 113 clang_src_dir, 'clang-tools-extra', 'clang-tidy', 'tool', 114 'clang-tidy-diff.py') 115 116 clang_tidy_binary = GetBinaryPath(clang_build_dir, 'clang-tidy') 117 118 args = [ 119 clang_tidy_diff_script, 120 '-quiet', 121 '-p1', 122 '-path', 123 out_dir, 124 '-clang-tidy-binary', 125 clang_tidy_binary, 126 ] 127 128 if checks: 129 args.append('-checks={}'.format(checks)) 130 131 if auto_fix: 132 args.append('-fix') 133 134 subprocess.check_call(args) 135 136 137def main(): 138 script_name = sys.argv[0] 139 140 parser = argparse.ArgumentParser( 141 formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__) 142 parser.add_argument( 143 '--fetch', 144 nargs='?', 145 const=update.CLANG_REVISION, 146 help='Fetch and build clang sources') 147 parser.add_argument( 148 '--build', 149 action='store_true', 150 help='build clang sources to get clang-tidy') 151 parser.add_argument( 152 '--diff', 153 action='store_true', 154 default=False, 155 help ='read diff from the stdin and check it') 156 parser.add_argument('--clang-src-dir', type=str, 157 help='override llvm and clang checkout location') 158 parser.add_argument('--clang-build-dir', type=str, 159 help='override clang build dir location') 160 parser.add_argument('--checks', help='passed to clang-tidy') 161 parser.add_argument('--header-filter', help='passed to clang-tidy') 162 parser.add_argument( 163 '--auto-fix', 164 action='store_true', 165 help='tell clang-tidy to auto-fix errors') 166 parser.add_argument('OUT_DIR', help='where we are building Chrome') 167 parser.add_argument('NINJA_TARGET', help='ninja target') 168 args = parser.parse_args() 169 170 steps = [] 171 172 # If the user hasn't provided a clang checkout and build dir, checkout and 173 # build clang-tidy where update.py would. 174 if not args.clang_src_dir: 175 args.clang_src_dir = build_clang_tools_extra.GetCheckoutDir(args.OUT_DIR) 176 if not args.clang_build_dir: 177 args.clang_build_dir = build_clang_tools_extra.GetBuildDir(args.OUT_DIR) 178 elif (args.clang_build_dir and not 179 os.path.isfile(GetBinaryPath(args.clang_build_dir, 'clang-tidy'))): 180 sys.exit('clang-tidy binary doesn\'t exist at ' + 181 GetBinaryPath(args.clang_build_dir, 'clang-tidy')) 182 183 if args.fetch: 184 steps.append(('Fetching LLVM sources', lambda: 185 build_clang_tools_extra.FetchLLVM(args.clang_src_dir, 186 args.fetch))) 187 188 if args.build: 189 steps.append(('Building clang-tidy', 190 lambda: build_clang_tools_extra.BuildTargets( 191 args.clang_build_dir, 192 ['clang-tidy', 'clang-apply-replacements']))) 193 194 steps += [ 195 ('Building ninja target: %s' % args.NINJA_TARGET, 196 lambda: BuildNinjaTarget(args.OUT_DIR, args.NINJA_TARGET)), 197 ('Generating compilation DB', lambda: GenerateCompDb(args.OUT_DIR)) 198 ] 199 if args.diff: 200 steps += [ 201 ('Running clang-tidy on diff', lambda: RunClangTidyDiff( 202 args.checks, args.auto_fix, args.clang_src_dir, args. 203 clang_build_dir, args.OUT_DIR)), 204 ] 205 else: 206 steps += [ 207 ('Running clang-tidy', 208 lambda: RunClangTidy(args.checks, args.header_filter, 209 args.auto_fix, args.clang_src_dir, 210 args.clang_build_dir, args.OUT_DIR, 211 args.NINJA_TARGET)), 212 ] 213 214 # Run the steps in sequence. 215 for i, (msg, step_func) in enumerate(steps): 216 # Print progress message 217 print('-- %s %s' % (script_name, '-' * (80 - len(script_name) - 4))) 218 print('-- [%d/%d] %s' % (i + 1, len(steps), msg)) 219 print(80 * '-') 220 221 step_func() 222 223 return 0 224 225 226if __name__ == '__main__': 227 sys.exit(main()) 228