feat(tools): add support for clang-tidy
This patch integrates clang-tidy into the project. Custom targets for
clang-tidy are added, which run clang-tidy on either the codebase or
pending commits.
A selection of checks has been enabled, some have their warnings
promoted to errors. The enabled checks, and the checks which have
warnings promoted to errors, are configured in the .clang-tidy file.
Signed-off-by: Chuyue Luo <Chuyue.Luo@arm.com>
Change-Id: Idba6d6b3e4438f524ac68307b1e463b9aec958e3
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..e0d8eae
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,36 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+#
+# Configuration file for clang-tidy.
+#
+# Checks are specified as a comma-separated list. A '-' before the name of the
+# check will disable that check.
+#
+Checks: '-*,
+ bugprone-*,
+ -bugprone-reserved-identifier,
+ -bugprone-easily-swappable-parameters,
+ -bugprone-branch-clone,
+ misc-redundant-expression,
+ misc-unused-parameters,
+ google-readability-casting'
+
+#
+# The WarningsAsErrors field specifies which checks will have their warnings
+# promoted to errors. These checks are specified in the same way as above.
+#
+WarningsAsErrors: '-*,
+ bugprone-narrowing-conversions,
+ bugprone-implicit-widening-of-multiplication-result,
+ bugprone-infinite-loop,
+ bugprone-too-small-loop-variable'
+
+#
+# The HeaderFilterRegex field specifies which header files clang-tidy will
+# output warnings from. Note that this does NOT affect which *.c files are
+# checked.
+#
+HeaderFilterRegex: 'drivers/|plat/|runtime/|lib/'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 40e887a..905db56 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -191,3 +191,28 @@
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMAND ${CMAKE_COMMAND} -DCHECKINCLUDES_PATCH=1 -P ${CMAKE_SOURCE_DIR}/tools/checkincludes/CheckIncludes.cmake
)
+
+#
+# Rules for running clang-tidy checks
+#
+# Pass through the value of RMM_TOOLCHAIN as this must be verified before
+# clang-tidy can be run.
+#
+# Also pass through the build directory as this cannot be accessed when the
+# clang-tidy target is built.
+#
+add_custom_target(clang-tidy-codebase
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ COMMAND ${CMAKE_COMMAND} -DCLANG-TIDY_CODEBASE=1
+ -DRMM_TOOLCHAIN=${RMM_TOOLCHAIN}
+ -DBUILD_DIR=${CMAKE_BINARY_DIR}
+ -P ${CMAKE_SOURCE_DIR}/tools/clang-tidy/clang-tidy.cmake
+ )
+
+add_custom_target(clang-tidy-patch
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ COMMAND ${CMAKE_COMMAND} -DCLANG-TIDY_PATCH=1
+ -DRMM_TOOLCHAIN=${RMM_TOOLCHAIN}
+ -DBUILD_DIR=${CMAKE_BINARY_DIR}
+ -P ${CMAKE_SOURCE_DIR}/tools/clang-tidy/clang-tidy.cmake
+ )
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index d4fe338..a14200c 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -162,7 +162,27 @@
cmake -DRMM_CONFIG=fvp_defcfg -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
cmake --build ${RMM_BUILD_DIR} -- checkincludes-codebase
-14. Perform unit tests on development host:
+14. Perform a clang-tidy analysis:
+
+Run clang-tidy on commits in the current branch against BASE_COMMIT (default
+origin/master):
+
+.. code-block:: bash
+
+ cmake -DRMM_CONFIG=fvp_defcfg -DRMM_TOOLCHAIN=llvm -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
+ cmake --build ${RMM_BUILD_DIR} -- clang-tidy-patch
+
+Run clang-tidy on entire codebase:
+
+.. code-block:: bash
+
+ cmake -DRMM_CONFIG=fvp_defcfg -DRMM_TOOLCHAIN=llvm -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
+ cmake --build ${RMM_BUILD_DIR} -- clang-tidy-codebase
+
+Note that clang-tidy will work with all configurations. It will only check the
+source files that are used for the specified configuration.
+
+15. Perform unit tests on development host:
Build and run unit tests on host platform. It is recommended to enable the
Debug build of RMM.
@@ -180,7 +200,7 @@
cmake --build ${RMM_BUILD_DIR} -- build -j
${RMM_BUILD_DIR}/Debug/rmm.elf -gxlat -v -r${NUMBER_OF_TEST_ITERATIONS}
-15. Generate Coverage Report.
+16. Generate Coverage Report.
It is possible to generate a coverage report for a last execution of the host
platform (whichever the variant) by using the `run-coverage` build target.
diff --git a/tools/clang-tidy/clang-tidy.cmake b/tools/clang-tidy/clang-tidy.cmake
new file mode 100644
index 0000000..63bd1a8
--- /dev/null
+++ b/tools/clang-tidy/clang-tidy.cmake
@@ -0,0 +1,260 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+#
+# This script is called from main CMakeLists.txt to run clang-tidy checks. The
+# checks are run on either the codebase (if variable CLANG-TIDY_CODEBASE is
+# defined) or just new commits (if variable CLANG-TIDY_PATCH is defined).
+#
+# Enabled checks can be configured in the .clang-tidy file.
+#
+find_package(Git REQUIRED)
+find_package(Python REQUIRED)
+find_package(Python3 REQUIRED)
+
+find_program(CLANGTIDY_EXECUTABLE "run-clang-tidy"
+ DOC "Path to run-clang-tidy executable."
+ )
+
+if(NOT CLANGTIDY_EXECUTABLE)
+ message(FATAL_ERROR "Could not find run-clang-tidy executable.")
+endif()
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/tools/common")
+include(GitUtils)
+
+#
+# List of directories and files to exclude from checking for target
+#
+list(APPEND glob_excludes "^.git")
+list(APPEND glob_excludes "^out")
+list(APPEND glob_excludes "^build")
+list(APPEND glob_excludes "^ext")
+list(APPEND glob_excludes "^tools")
+list(APPEND glob_excludes "^configs")
+list(APPEND glob_excludes "^cmake")
+list(APPEND glob_excludes "^docs")
+
+#
+# clang-tidy_get_stats: Parse output and return number of errors and warnings
+#
+function(clangtidy_get_stats stats_arg warnings_ret errors_ret)
+ set(output_lines)
+ string(REPLACE "\n" ";" output_lines "${stats_arg}")
+
+ set(warnings 0)
+ set(errors 0)
+
+ #
+ # Ideally we would match against the substring ": warning: ", and similarly
+ # for errors.
+ #
+ # Unfortunately, the run-clang-tidy included with Clang 14.0.0 enables
+ # colours in the output by default, and there is no way to change this in
+ # the configuration. The use of colours in the output means the exact
+ # substring ": warning: " is never present, due to the presence of escape
+ # characters.
+ #
+ # In addition, the presence of escape characters presents difficulties in
+ # splitting the output into lines. This is why REGEX MATCHALL is used
+ # instead of FIND.
+ #
+ foreach(output_line IN LISTS output_lines)
+ set(warnings_list)
+
+ string(REGEX MATCHALL "warning: " warnings_list "${output_line}")
+ list(LENGTH warnings_list warnings_count)
+ MATH(EXPR warnings "${warnings} + ${warnings_count}")
+
+ set(errors_list)
+
+ string(REGEX MATCHALL "error: " errors_list "${output_line}")
+ list(LENGTH errors_list errors_count)
+ MATH(EXPR errors "${errors} + ${errors_count}")
+ endforeach()
+
+ set(${warnings_ret} ${warnings} PARENT_SCOPE)
+ set(${errors_ret} ${errors} PARENT_SCOPE)
+endfunction()
+
+#
+# print_stats_and_exit: Print summary of all errors and warnings.
+# If there are errors call message(FATAL_ERROR)
+#
+function(print_stats_and_exit check_type total_warnings total_errors)
+ message(STATUS "${check_type}: total warnings: ${total_warnings}, total errors: ${total_errors}")
+
+ if(${total_errors} GREATER 0)
+ message(FATAL_ERROR "${check_type}: FAILED")
+ endif()
+
+ message(STATUS "${check_type}: PASSED")
+endfunction()
+
+#
+# filter_source_files: Filter all files except *.c/*.h files
+#
+function(filter_source_files all_files source_files_ret)
+ foreach(exclude IN LISTS glob_excludes)
+ list(FILTER all_files EXCLUDE REGEX "${exclude}")
+ endforeach()
+
+ foreach(source_file ${all_files})
+ if(NOT source_file MATCHES ".c$" AND
+ NOT source_file MATCHES ".h$")
+ list(REMOVE_ITEM all_files ${source_file})
+ endif()
+ endforeach()
+
+ set(${source_files_ret} ${all_files} PARENT_SCOPE)
+endfunction()
+
+#
+# run_clangtidy: Run clang-tidy on the list of files.
+#
+function(run_clangtidy source_files warnings_ret errors_ret)
+ set(total_warnings 0)
+ set(total_errors 0)
+
+ string(REPLACE ";" " " source_files "${source_files}")
+ separate_arguments(source_files NATIVE_COMMAND "${source_files}")
+
+ foreach(source_file ${source_files})
+ message("Checking file ${source_file}")
+
+ #
+ # Run clang-tidy on each file, one at a time. Note that although this loop
+ # iterates through all files in the codebase, only files used for the
+ # specified configuration will actually be checked.
+ #
+ # We pass the compile commands database (in the build folder), which
+ # records the compile options used in the build, to clang-tidy.
+ #
+ # -quiet flag is required to prevent clang-tidy from printing the enabled
+ # checks for every file!
+ #
+ # We discard the standard error output, which is simply a line stating "X
+ # warnings generated". This number X includes warnings from system headers,
+ # which are not displayed. Instead, we manually count the number of
+ # warnings and errors generated from the relevant files.
+ #
+ execute_process(
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMAND ${CLANGTIDY_EXECUTABLE}
+ -p ${BUILD_DIR}
+ -quiet
+ ${source_file}
+ OUTPUT_VARIABLE clang-tidy_output
+ ERROR_QUIET
+ )
+
+ if(clang-tidy_output)
+ message(${clang-tidy_output})
+ endif()
+
+ # Update total counts for warnings and errors
+ clangtidy_get_stats("${clang-tidy_output}" warnings errors)
+ MATH(EXPR total_warnings "${total_warnings} + ${warnings}")
+ MATH(EXPR total_errors "${total_errors} + ${errors}")
+ endforeach()
+
+ set(${warnings_ret} ${total_warnings} PARENT_SCOPE)
+ set(${errors_ret} ${total_errors} PARENT_SCOPE)
+endfunction()
+
+#
+# Run clang-tidy on entire codebase. This verifies all files in this
+# repository in "GLOB_INCLUDES".
+#
+# Exits with FATAL_ERROR upon errors.
+#
+if(CLANG-TIDY_CODEBASE)
+ set(compile_commands
+ "${BUILD_DIR}/compile_commands.json")
+
+ if(NOT EXISTS "${compile_commands}")
+ message(FATAL_ERROR
+ "clang-tidy requires a compile command database. Use flag "
+ "`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` during configuration.")
+ endif()
+
+ if(NOT "${RMM_TOOLCHAIN}" STREQUAL "llvm")
+ message(FATAL_ERROR
+ "clang-tidy can be used only if the project is built using the LLVM "
+ "toolchain. Use flag `DRMM_TOOLCHAIN=llvm` during configuration.")
+ endif()
+
+ set(source_files "")
+
+ if (GIT_FOUND AND IS_DIRECTORY .git)
+ Git_Get_All_Files(all_files)
+ else()
+ file(GLOB_RECURSE all_files RELATIVE ${CMAKE_SOURCE_DIR} "*")
+ endif()
+
+ filter_source_files("${all_files}" source_files)
+
+ if(NOT source_files)
+ message(STATUS "clang-tidy-codebase: No files to check")
+ return()
+ endif()
+
+ run_clangtidy("${source_files}" total_warnings total_errors)
+ print_stats_and_exit("clang-tidy-codebase" ${total_warnings} ${total_errors})
+endif()
+
+#
+# Run clang-tidy on pending commits.
+#
+# Exits with FATAL_ERROR upon errors.
+#
+if(CLANG-TIDY_PATCH)
+ set(compile_commands
+ "${BUILD_DIR}/compile_commands.json")
+
+ if(NOT EXISTS "${compile_commands}")
+ message(FATAL_ERROR
+ "clang-tidy requires a compile command database. Use flag "
+ "`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` during configuration.")
+ endif()
+
+ if(NOT "${RMM_TOOLCHAIN}" STREQUAL "llvm")
+ message(FATAL_ERROR
+ "clang-tidy can be used only if the project is built using the LLVM "
+ "toolchain. Use flag `DRMM_TOOLCHAIN=llvm` during configuration.")
+ endif()
+
+ if(GIT_NOT_FOUND OR NOT IS_DIRECTORY .git)
+ message(FATAL_ERROR "Required dependencies Git not found")
+ endif()
+
+ #
+ # Get list of commits to check
+ #
+ Git_Get_Pending_Commits(pending_commits)
+
+ #
+ # Iterate through list of commit ids
+ #
+ set(total_warnings 0)
+ set(total_errors 0)
+
+ foreach(commit IN LISTS pending_commits)
+ message(STATUS "Checking commit: ${commit}")
+
+ Git_Get_Files_In_Commit("${commit}" files_in_commit)
+
+ set(source_files "")
+ filter_source_files("${files_in_commit}" source_files)
+
+ if(source_files)
+ run_clangtidy("${source_files}" warnings errors)
+ MATH(EXPR total_warnings "${total_warnings} + ${warnings}")
+ MATH(EXPR total_errors "${total_errors} + ${errors}")
+ endif()
+ endforeach()
+
+ print_stats_and_exit("clang-tidy-patch" ${total_warnings} ${total_errors})
+endif()