1#
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 determine if code complies
8# to coding standards as mentioned in docs/getting_started/coding-standard.rst
9#
10# Runs checkpatch.pl on entire codebase if variable CHECKCODEBASE_RUN is defined.
11#
12# Runs checkpatch.pl on new commits if variable CHECKCODEBASE_RUN is defined.
13#
14find_package(Git REQUIRED)
15find_package(Perl REQUIRED)
16find_program(CHECKPATCH_EXECUTABLE "checkpatch.pl"
17  PATHS ${CMAKE_SOURCE_DIR}
18  PATH_SUFFIXES tools/checkpatch
19  DOC "Path to checkpatch.pl"
20  )
21
22list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/tools/common")
23include(GitUtils)
24
25#
26# List of directories and files to exclude from checking for target checkcodebase
27#
28list(APPEND glob_excludes "^.git")
29list(APPEND glob_excludes "^out")
30list(APPEND glob_excludes "^build")
31list(APPEND glob_excludes "^ext")
32list(APPEND glob_excludes "^tools")
33list(APPEND glob_excludes ".patch$")
34list(APPEND glob_excludes ".md$")
35list(APPEND glob_excludes "~$")
36list(APPEND glob_excludes ".swp$")
37list(APPEND glob_excludes "^cscope.")
38
39set(total_errors "0")
40set(total_warnings "0")
41
42# Check if pre-reqs met
43if(PERL_NOT_FOUND OR NOT EXISTS ${CHECKPATCH_EXECUTABLE})
44  message(FATAL_ERROR "required dependencies not found")
45endif()
46
47#
48# checkpatch_get_stats: Parse and returns number of errors and warnings
49#
50function(checkpatch_get_stats stats_arg errors_ret warnings_ret)
51  string(FIND "${stats_arg}" "total:" idx REVERSE)
52  string(LENGTH "${stats_arg}" len)
53  string(SUBSTRING "${stats_arg}" ${idx} ${len} last_line)
54
55  string(REPLACE " " ";" last_line_list ${last_line})
56  list(GET last_line_list 1 errors)
57  list(GET last_line_list 3 warnings)
58
59  set(${errors_ret} ${errors} PARENT_SCOPE)
60  set(${warnings_ret} ${warnings} PARENT_SCOPE)
61endfunction()
62
63#
64# print_stats_and_exit: Print summary of all errors and warnings.
65# If there are errors call message(FATAL_ERROR)
66#
67function(print_stats_and_exit check_type total_errors total_warnings)
68  message(STATUS "${check_type}: total errors: ${total_errors} "
69    "warnings: ${total_warnings}")
70
71  if(${total_errors} GREATER 0)
72    message(FATAL_ERROR "${check_type}: FAILED")
73  endif()
74
75  message(STATUS "${check_type}: PASSED")
76endfunction()
77
78#
79# Run checkpatch on entire codebase. This verifies all files in this repository
80# except the files listed in "glob_excludes".
81#
82# Exits with FATAL_ERROR upon errors. Warnings are ignored (temporary)
83#
84if(CHECKCODEBASE_RUN)
85  set(source_files "")
86
87  if (GIT_FOUND AND IS_DIRECTORY .git)
88    Git_Get_All_Files(source_files)
89  else()
90    file(GLOB_RECURSE source_files RELATIVE ${CMAKE_SOURCE_DIR} "*")
91  endif()
92
93  # Filter out 'glob_excludes'
94  foreach(exclude IN LISTS glob_excludes)
95    list(FILTER source_files EXCLUDE REGEX "${exclude}")
96  endforeach()
97
98  if(NOT source_files)
99    message(STATUS "checkcodebase: No files to check")
100    return()
101  endif()
102
103  foreach(source_file ${source_files})
104    execute_process(
105      COMMAND ${CMAKE_COMMAND} -E echo "Checking file ${source_file}"
106      )
107
108    execute_process(
109      WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
110      COMMAND ${CHECKPATCH_EXECUTABLE} -f ${source_file}
111      OUTPUT_VARIABLE checkpatch_output
112      RESULT_VARIABLE checkpatch_rc
113      ECHO_OUTPUT_VARIABLE
114      OUTPUT_STRIP_TRAILING_WHITESPACE
115      )
116
117    # checkpatch.pl failed for this file. Collect no.of errors and warnings
118    if(${checkpatch_rc})
119      checkpatch_get_stats("${checkpatch_output}" errors warnings)
120      MATH(EXPR total_errors "${total_errors}+${errors}")
121      MATH(EXPR total_warnings "${total_warnings}+${warnings}")
122    endif()
123  endforeach()
124
125  print_stats_and_exit("checkcodebase" ${total_errors}, ${total_warnings})
126endif()
127
128#
129# Run checkpatch on pending commits.
130#
131# Exits with FATAL_ERROR upon errors.
132#
133if(CHECKPATCH_RUN)
134  if(GIT_NOT_FOUND OR NOT IS_DIRECTORY .git)
135    message(FATAL_ERROR "Required dependencies Git not found")
136  endif()
137
138  # Get list of commits to check
139  Git_Get_Pending_Commits(pending_commits)
140
141  foreach(commit IN LISTS pending_commits)
142    message(STATUS "Checking commit: ${commit}")
143
144    execute_process(
145      WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
146      COMMAND ${GIT_EXECUTABLE} diff --format=email "${commit}~..${commit}"
147      COMMAND ${CHECKPATCH_EXECUTABLE} -
148      OUTPUT_VARIABLE checkpatch_output
149      RESULT_VARIABLE checkpatch_rc
150      ECHO_OUTPUT_VARIABLE
151      )
152
153    # checkpatch.pl failed for this commit. Collect no.of errors and warnings
154    if(${checkpatch_rc})
155      checkpatch_get_stats("${checkpatch_output}" errors warnings)
156      MATH(EXPR total_errors "${total_errors}+${errors}")
157      MATH(EXPR total_warnings "${total_warnings}+${warnings}")
158    endif()
159  endforeach()
160
161  print_stats_and_exit("checkpatch" ${total_errors}, ${total_warnings})
162endif()
163