blob: 63bd1a8d8a491b591a5a59fd3f40718d13ac08be [file] [log] [blame]
Chuyue Luo88c07192023-09-25 16:11:36 +01001#
2# SPDX-License-Identifier: BSD-3-Clause
3# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
4#
5
6#
7# This script is called from main CMakeLists.txt to run clang-tidy checks. The
8# checks are run on either the codebase (if variable CLANG-TIDY_CODEBASE is
9# defined) or just new commits (if variable CLANG-TIDY_PATCH is defined).
10#
11# Enabled checks can be configured in the .clang-tidy file.
12#
13find_package(Git REQUIRED)
14find_package(Python REQUIRED)
15find_package(Python3 REQUIRED)
16
17find_program(CLANGTIDY_EXECUTABLE "run-clang-tidy"
18 DOC "Path to run-clang-tidy executable."
19 )
20
21if(NOT CLANGTIDY_EXECUTABLE)
22 message(FATAL_ERROR "Could not find run-clang-tidy executable.")
23endif()
24
25list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/tools/common")
26include(GitUtils)
27
28#
29# List of directories and files to exclude from checking for target
30#
31list(APPEND glob_excludes "^.git")
32list(APPEND glob_excludes "^out")
33list(APPEND glob_excludes "^build")
34list(APPEND glob_excludes "^ext")
35list(APPEND glob_excludes "^tools")
36list(APPEND glob_excludes "^configs")
37list(APPEND glob_excludes "^cmake")
38list(APPEND glob_excludes "^docs")
39
40#
41# clang-tidy_get_stats: Parse output and return number of errors and warnings
42#
43function(clangtidy_get_stats stats_arg warnings_ret errors_ret)
44 set(output_lines)
45 string(REPLACE "\n" ";" output_lines "${stats_arg}")
46
47 set(warnings 0)
48 set(errors 0)
49
50 #
51 # Ideally we would match against the substring ": warning: ", and similarly
52 # for errors.
53 #
54 # Unfortunately, the run-clang-tidy included with Clang 14.0.0 enables
55 # colours in the output by default, and there is no way to change this in
56 # the configuration. The use of colours in the output means the exact
57 # substring ": warning: " is never present, due to the presence of escape
58 # characters.
59 #
60 # In addition, the presence of escape characters presents difficulties in
61 # splitting the output into lines. This is why REGEX MATCHALL is used
62 # instead of FIND.
63 #
64 foreach(output_line IN LISTS output_lines)
65 set(warnings_list)
66
67 string(REGEX MATCHALL "warning: " warnings_list "${output_line}")
68 list(LENGTH warnings_list warnings_count)
69 MATH(EXPR warnings "${warnings} + ${warnings_count}")
70
71 set(errors_list)
72
73 string(REGEX MATCHALL "error: " errors_list "${output_line}")
74 list(LENGTH errors_list errors_count)
75 MATH(EXPR errors "${errors} + ${errors_count}")
76 endforeach()
77
78 set(${warnings_ret} ${warnings} PARENT_SCOPE)
79 set(${errors_ret} ${errors} PARENT_SCOPE)
80endfunction()
81
82#
83# print_stats_and_exit: Print summary of all errors and warnings.
84# If there are errors call message(FATAL_ERROR)
85#
86function(print_stats_and_exit check_type total_warnings total_errors)
87 message(STATUS "${check_type}: total warnings: ${total_warnings}, total errors: ${total_errors}")
88
89 if(${total_errors} GREATER 0)
90 message(FATAL_ERROR "${check_type}: FAILED")
91 endif()
92
93 message(STATUS "${check_type}: PASSED")
94endfunction()
95
96#
97# filter_source_files: Filter all files except *.c/*.h files
98#
99function(filter_source_files all_files source_files_ret)
100 foreach(exclude IN LISTS glob_excludes)
101 list(FILTER all_files EXCLUDE REGEX "${exclude}")
102 endforeach()
103
104 foreach(source_file ${all_files})
105 if(NOT source_file MATCHES ".c$" AND
106 NOT source_file MATCHES ".h$")
107 list(REMOVE_ITEM all_files ${source_file})
108 endif()
109 endforeach()
110
111 set(${source_files_ret} ${all_files} PARENT_SCOPE)
112endfunction()
113
114#
115# run_clangtidy: Run clang-tidy on the list of files.
116#
117function(run_clangtidy source_files warnings_ret errors_ret)
118 set(total_warnings 0)
119 set(total_errors 0)
120
121 string(REPLACE ";" " " source_files "${source_files}")
122 separate_arguments(source_files NATIVE_COMMAND "${source_files}")
123
124 foreach(source_file ${source_files})
125 message("Checking file ${source_file}")
126
127 #
128 # Run clang-tidy on each file, one at a time. Note that although this loop
129 # iterates through all files in the codebase, only files used for the
130 # specified configuration will actually be checked.
131 #
132 # We pass the compile commands database (in the build folder), which
133 # records the compile options used in the build, to clang-tidy.
134 #
135 # -quiet flag is required to prevent clang-tidy from printing the enabled
136 # checks for every file!
137 #
138 # We discard the standard error output, which is simply a line stating "X
139 # warnings generated". This number X includes warnings from system headers,
140 # which are not displayed. Instead, we manually count the number of
141 # warnings and errors generated from the relevant files.
142 #
143 execute_process(
144 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
145 COMMAND ${CLANGTIDY_EXECUTABLE}
146 -p ${BUILD_DIR}
147 -quiet
148 ${source_file}
149 OUTPUT_VARIABLE clang-tidy_output
150 ERROR_QUIET
151 )
152
153 if(clang-tidy_output)
154 message(${clang-tidy_output})
155 endif()
156
157 # Update total counts for warnings and errors
158 clangtidy_get_stats("${clang-tidy_output}" warnings errors)
159 MATH(EXPR total_warnings "${total_warnings} + ${warnings}")
160 MATH(EXPR total_errors "${total_errors} + ${errors}")
161 endforeach()
162
163 set(${warnings_ret} ${total_warnings} PARENT_SCOPE)
164 set(${errors_ret} ${total_errors} PARENT_SCOPE)
165endfunction()
166
167#
168# Run clang-tidy on entire codebase. This verifies all files in this
169# repository in "GLOB_INCLUDES".
170#
171# Exits with FATAL_ERROR upon errors.
172#
173if(CLANG-TIDY_CODEBASE)
174 set(compile_commands
175 "${BUILD_DIR}/compile_commands.json")
176
177 if(NOT EXISTS "${compile_commands}")
178 message(FATAL_ERROR
179 "clang-tidy requires a compile command database. Use flag "
180 "`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` during configuration.")
181 endif()
182
183 if(NOT "${RMM_TOOLCHAIN}" STREQUAL "llvm")
184 message(FATAL_ERROR
185 "clang-tidy can be used only if the project is built using the LLVM "
186 "toolchain. Use flag `DRMM_TOOLCHAIN=llvm` during configuration.")
187 endif()
188
189 set(source_files "")
190
191 if (GIT_FOUND AND IS_DIRECTORY .git)
192 Git_Get_All_Files(all_files)
193 else()
194 file(GLOB_RECURSE all_files RELATIVE ${CMAKE_SOURCE_DIR} "*")
195 endif()
196
197 filter_source_files("${all_files}" source_files)
198
199 if(NOT source_files)
200 message(STATUS "clang-tidy-codebase: No files to check")
201 return()
202 endif()
203
204 run_clangtidy("${source_files}" total_warnings total_errors)
205 print_stats_and_exit("clang-tidy-codebase" ${total_warnings} ${total_errors})
206endif()
207
208#
209# Run clang-tidy on pending commits.
210#
211# Exits with FATAL_ERROR upon errors.
212#
213if(CLANG-TIDY_PATCH)
214 set(compile_commands
215 "${BUILD_DIR}/compile_commands.json")
216
217 if(NOT EXISTS "${compile_commands}")
218 message(FATAL_ERROR
219 "clang-tidy requires a compile command database. Use flag "
220 "`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` during configuration.")
221 endif()
222
223 if(NOT "${RMM_TOOLCHAIN}" STREQUAL "llvm")
224 message(FATAL_ERROR
225 "clang-tidy can be used only if the project is built using the LLVM "
226 "toolchain. Use flag `DRMM_TOOLCHAIN=llvm` during configuration.")
227 endif()
228
229 if(GIT_NOT_FOUND OR NOT IS_DIRECTORY .git)
230 message(FATAL_ERROR "Required dependencies Git not found")
231 endif()
232
233 #
234 # Get list of commits to check
235 #
236 Git_Get_Pending_Commits(pending_commits)
237
238 #
239 # Iterate through list of commit ids
240 #
241 set(total_warnings 0)
242 set(total_errors 0)
243
244 foreach(commit IN LISTS pending_commits)
245 message(STATUS "Checking commit: ${commit}")
246
247 Git_Get_Files_In_Commit("${commit}" files_in_commit)
248
249 set(source_files "")
250 filter_source_files("${files_in_commit}" source_files)
251
252 if(source_files)
253 run_clangtidy("${source_files}" warnings errors)
254 MATH(EXPR total_warnings "${total_warnings} + ${warnings}")
255 MATH(EXPR total_errors "${total_errors} + ${errors}")
256 endif()
257 endforeach()
258
259 print_stats_and_exit("clang-tidy-patch" ${total_warnings} ${total_errors})
260endif()