blob: cc1989377b6d1a180f9ce3dd86438b18707bc97b [file] [log] [blame]
Mahesh Saripallib7cebe92023-08-08 20:35:22 +00001#!/usr/bin/env fuchsia-vendored-python
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +02002# Copyright 2017 The Fuchsia 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.
5"""Runs clang-tidy on modified files.
6
7The tool uses `git diff-index` against the newest parent commit in the upstream
Gabriel Kerneisd5c391c2018-07-02 15:20:50 +02008branch (or against HEAD if no such commit is found) in order to find the files
9to be formatted. In result, the tool lints files that are locally modified,
10staged or touched by any commits introduced on the local branch.
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020011"""
12
13import argparse
Przemyslaw Pietrzkiewicz49f5c9e2017-08-02 13:11:25 +020014import multiprocessing
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020015import os
16import platform
17import re
18import subprocess
19import sys
20
Ian McKellar91e0f9e2019-04-04 18:25:52 +000021sys.path.append(os.path.dirname(os.path.dirname(__file__)))
22
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020023import git_utils
Adam MacBetha225d5d2019-05-03 20:50:50 +000024
25FUCHSIA_ROOT = os.path.dirname( # $root
26 os.path.dirname( # scripts
27 os.path.dirname( # git
28 os.path.abspath(__file__))))
Adam MacBeth78289292019-08-08 22:01:42 +000029PREBUILT_ROOT = os.path.join(FUCHSIA_ROOT, "prebuilt/third_party")
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020030
Adam MacBeth78289292019-08-08 22:01:42 +000031local_os = "linux"
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020032if platform.platform().startswith("Darwin"):
Adam MacBeth78289292019-08-08 22:01:42 +000033 local_os = "mac"
34CLANG_TIDY_TOOL = os.path.join(PREBUILT_ROOT, "clang",
35 "%s-x64" % local_os, "bin",
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020036 "clang-tidy")
Adam MacBeth78289292019-08-08 22:01:42 +000037NINJA_TOOL = os.path.join(PREBUILT_ROOT, "ninja",
38 "%s-x64" % local_os, "ninja")
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020039
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +020040def find_ancestor_with(filepath, relpath):
41 """Returns the lowest ancestor of |filepath| that contains |relpath|."""
42 cur_dir_path = os.path.abspath(os.path.dirname(filepath))
43 while True:
44 if os.path.exists(os.path.join(cur_dir_path, relpath)):
45 return cur_dir_path
46
47 next_dir_path = os.path.dirname(cur_dir_path)
48 if next_dir_path != cur_dir_path:
49 cur_dir_path = next_dir_path
50 else:
51 return None
52
53
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020054def get_out_dir(args):
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020055 if args.out_dir:
56 out_dir = args.out_dir
57
58 if not os.path.isabs(out_dir):
Adam MacBetha225d5d2019-05-03 20:50:50 +000059 out_dir = os.path.join(FUCHSIA_ROOT, out_dir)
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020060
61 if not os.path.isdir(out_dir):
62 print out_dir + " is not a directory"
63 sys.exit(-1)
64 return out_dir
65
Adam MacBetha225d5d2019-05-03 20:50:50 +000066 fuchsia_config_file = os.path.join(FUCHSIA_ROOT, '.fx-build-dir')
Ian McKellar311ff972018-07-02 13:30:37 -070067 if os.path.isfile(fuchsia_config_file):
68 fuchsia_config = open(fuchsia_config_file).read()
Adam MacBetha225d5d2019-05-03 20:50:50 +000069 return os.path.join(FUCHSIA_ROOT, fuchsia_config.strip())
Ian McKellar311ff972018-07-02 13:30:37 -070070
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020071 print("Couldn't find the output directory, pass --out-dir " +
Adam MacBetha225d5d2019-05-03 20:50:50 +000072 "(absolute or relative to Fuchsia root)")
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020073 sys.exit(-1)
74
75
76def generate_db(out_dir):
77 cmd = [NINJA_TOOL, "-C", out_dir, "-t", "compdb", "cc", "cxx"]
78 db = subprocess.check_output(
Adam MacBetha225d5d2019-05-03 20:50:50 +000079 cmd, cwd=FUCHSIA_ROOT, universal_newlines=True)
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020080
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020081 with open(os.path.join(out_dir, "compile_commands.json"), "w+") as db_file:
82 db_file.write(db)
83
84
85def go(args):
86 out_dir = get_out_dir(args)
87
88 # generate the compilation database
89 generate_db(out_dir)
90
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +020091 # Find the files to be checked.
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +020092 if args.all:
93 files = git_utils.get_all_files()
94 else:
95 files = git_utils.get_diff_files()
96
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +020097 filtered_files = []
98 for file_path in files:
99 # Skip deleted files.
100 if not os.path.isfile(file_path):
101 if args.verbose:
102 print "skipping " + file_path + " (deleted)"
103 continue
104
105 # Skip files with parent directories containing .nolint
106 if find_ancestor_with(file_path, ".nolint"):
107 if args.verbose:
108 print "skipping " + file_path + " (.nolint)"
109 continue
110 filtered_files.append(file_path)
111
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200112 if args.verbose:
113 print
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +0200114 print "Files to be checked:"
115 for file in filtered_files:
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200116 print " - " + file
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +0200117 if not filtered_files:
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200118 print " (no files)"
119 print
120
121 # change the working directory to Fuchsia root.
Adam MacBetha225d5d2019-05-03 20:50:50 +0000122 os.chdir(FUCHSIA_ROOT)
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200123
Przemyslaw Pietrzkiewicz5637c8c2017-08-03 15:57:52 +0200124 # It's not safe to run in parallel with "--fix", as clang-tidy traverses and
125 # fixes header files, and we might end up with concurrent writes to the same
126 # header file.
127 if args.no_parallel or args.fix:
Przemyslaw Pietrzkiewicz49f5c9e2017-08-02 13:11:25 +0200128 parallel_jobs = 1
129 else:
130 parallel_jobs = multiprocessing.cpu_count()
131 print("Running " + str(parallel_jobs) +
132 " jobs in parallel, pass --no-parallel to disable")
133
134 jobs = set()
135
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +0200136 for file_path in filtered_files:
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200137 _, extension = os.path.splitext(file_path)
138 if extension == ".cc":
139 relpath = os.path.relpath(file_path)
140 cmd = [CLANG_TIDY_TOOL, "-p", out_dir, relpath]
141 if args.checks:
142 cmd.append("-checks=" + args.checks)
143 if args.fix:
144 cmd.append("-fix")
Przemyslaw Pietrzkiewicz74a450e2017-08-08 11:36:10 +0200145 if not args.verbose:
146 cmd.append("-quiet")
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200147
148 if args.verbose:
Przemyslaw Pietrzkiewicz49f5c9e2017-08-02 13:11:25 +0200149 print "checking " + file_path + ": " + str(cmd)
150 jobs.add(subprocess.Popen(cmd))
151 if len(jobs) >= parallel_jobs:
152 os.wait()
153 jobs.difference_update(
154 [job for job in jobs if job.poll() is not None])
155 for job in jobs:
156 if job.poll() is None:
157 job.wait()
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200158
159
160def main():
161 parser = argparse.ArgumentParser(description="Lint modified files.")
162 parser.add_argument(
163 "--all",
164 dest="all",
Mahesh Saripalli8e446e702023-08-02 19:40:42 +0000165 action=argparse.BooleanOptionalAction,
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200166 default=False,
167 help="process all files in the repo under current working directory")
168 parser.add_argument(
169 "--fix",
170 dest="fix",
Mahesh Saripalli8e446e702023-08-02 19:40:42 +0000171 action=argparse.BooleanOptionalAction,
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200172 default=False,
173 help="automatically generate fixes when possible")
174 parser.add_argument("--checks", help="overrides the list of checks to use")
175 parser.add_argument(
176 "--out-dir",
177 help="Output directory, needed to generate compilation db for clang.")
178 parser.add_argument(
Przemyslaw Pietrzkiewicz49f5c9e2017-08-02 13:11:25 +0200179 "--no-parallel",
Mahesh Saripalli8e446e702023-08-02 19:40:42 +0000180 action=argparse.BooleanOptionalAction,
Przemyslaw Pietrzkiewicz49f5c9e2017-08-02 13:11:25 +0200181 default=False,
182 help="Process one file at a time")
183 parser.add_argument(
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200184 "--verbose",
185 dest="verbose",
Mahesh Saripalli8e446e702023-08-02 19:40:42 +0000186 action=argparse.BooleanOptionalAction,
Przemyslaw Pietrzkiewicz3fa398e2017-07-31 16:40:11 +0200187 default=False,
188 help="tell me what you're doing")
189 args = parser.parse_args()
190 go(args)
191
192 return 0
193
194
195if __name__ == "__main__":
196 sys.exit(main())