clang-tidy: Compile Command Database generation and clang-tidy
Adds support for clang-tidy by the use of a manually generated compile command database. This is likely not full functional yet, so more tests might have to be done on different platforms and compilers.
This commit is contained in:
+352
-27
@@ -1,9 +1,224 @@
|
||||
set(CLANG_PATH "" CACHE PATH "Path to Clang Toolset (if not in environment)")
|
||||
|
||||
function(string_escape)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
""
|
||||
"OUTPUT;INPUT"
|
||||
""
|
||||
)
|
||||
|
||||
set(_el "${_ARGS_INPUT}")
|
||||
string(REPLACE "\\" "\\\\" _el "${_el}")
|
||||
string(REPLACE "\"" "\\\"" _el "${_el}")
|
||||
set(${_ARGS_OUTPUT} "${_el}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(string_append_escaped)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
""
|
||||
"OUTPUT;INPUT"
|
||||
""
|
||||
)
|
||||
|
||||
set(_el "${_ARGS_INPUT}")
|
||||
if(_el)
|
||||
string_escape(OUTPUT _el INPUT "${_el}")
|
||||
set(${_ARGS_OUTPUT} "${${_ARGS_OUTPUT}}${_el}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(string_append_target_includes)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
"LINKED"
|
||||
"OUTPUT;PREFIX;TARGET"
|
||||
""
|
||||
)
|
||||
|
||||
set(_out "")
|
||||
if(TARGET ${_ARGS_TARGET})
|
||||
get_target_property(target_type ${_ARGS_TARGET} TYPE)
|
||||
if(
|
||||
(${target_type} STREQUAL "MODULE_LIBRARY") OR
|
||||
(${target_type} STREQUAL "STATIC_LIBRARY") OR
|
||||
(${target_type} STREQUAL "SHARED_LIBRARY") OR
|
||||
(${target_type} STREQUAL "EXECUTABLE")
|
||||
)
|
||||
set(prop "$<TARGET_PROPERTY:${_ARGS_TARGET},INCLUDE_DIRECTORIES>")
|
||||
set(test "$<$<BOOL:${prop}>:${_ARGS_PREFIX}$<JOIN:${prop}, ${_ARGS_PREFIX}>>")
|
||||
set(_out "${_out}${test} ")
|
||||
set(prop "$<TARGET_PROPERTY:${_ARGS_TARGET},INTERFACE_INCLUDE_DIRECTORIES>")
|
||||
set(test "$<$<BOOL:${prop}>:${_ARGS_PREFIX}$<JOIN:${prop}, ${_ARGS_PREFIX}>>")
|
||||
set(_out "${_out}${test} ")
|
||||
set(prop "$<TARGET_PROPERTY:${_ARGS_TARGET},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>")
|
||||
set(test "$<$<BOOL:${prop}>:${_ARGS_PREFIX}$<JOIN:${prop}, ${_ARGS_PREFIX}>>")
|
||||
set(_out "${_out}${test} ")
|
||||
if(NOT _ARGS_LINKED)
|
||||
# Scan linked libraries as well.
|
||||
get_target_property(_els ${_target} LINK_LIBRARIES)
|
||||
foreach(_lib ${_els})
|
||||
#string_append_target_includes(LINKED TARGET "${_lib}" OUTPUT "_out" PREFIX "${_ARGS_PREFIX}")
|
||||
endforeach()
|
||||
endif()
|
||||
elseif((${target_type} STREQUAL "INTERFACE_LIBRARY"))
|
||||
set(prop "$<TARGET_PROPERTY:${_ARGS_TARGET},INTERFACE_INCLUDE_DIRECTORIES>")
|
||||
set(test "$<$<BOOL:${prop}>:${_ARGS_PREFIX}$<JOIN:${prop}, ${_ARGS_PREFIX}>>")
|
||||
set(_out "${_out}${test} ")
|
||||
else()
|
||||
message("clang: Unsupported Target type '${target_type}', please open an issue for this.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(${_ARGS_OUTPUT} "${${_ARGS_OUTPUT}}${_out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(generate_compile_commands_json)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
""
|
||||
"REGEX"
|
||||
"TARGETS"
|
||||
)
|
||||
|
||||
set(COMPILER_TAG "")
|
||||
if((CMAKE_C_COMPILER_ID STREQUAL "MSVC") AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC"))
|
||||
set(COMPILER_TAG "MSVC")
|
||||
set(COMPILER_INCLUDE_PREFIX "/I")
|
||||
set(COMPILER_DEFINE_PREFIX "/D")
|
||||
elseif((CMAKE_C_COMPILER_ID STREQUAL "GNU") AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
|
||||
set(COMPILER_TAG "GNU")
|
||||
set(COMPILER_INCLUDE_PREFIX "-I")
|
||||
set(COMPILER_DEFINE_PREFIX "-D")
|
||||
elseif((CMAKE_C_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
|
||||
set(COMPILER_TAG "CLANG") # Compatible with GNU
|
||||
set(COMPILER_INCLUDE_PREFIX "-I")
|
||||
set(COMPILER_DEFINE_PREFIX "-D")
|
||||
else()
|
||||
message("clang-tidy: C ID '${CMAKE_C_COMPILER_ID}'")
|
||||
message("clang-tidy: C Version '${CMAKE_C_COMPILER_VERSION}'")
|
||||
message("clang-tidy: C++ ID '${CMAKE_CXX_COMPILER_ID}'")
|
||||
message("clang-tidy: C++ Version '${CMAKE_CXX_COMPILER_VERSION}'")
|
||||
message(FATAL_ERROR "clang-tidy: Current Compiler is not yet supported, please open an issue for it.")
|
||||
endif()
|
||||
|
||||
# Default Filter
|
||||
if(NOT _ARGS_REGEX)
|
||||
set(_ARGS_REGEX "\.(h|hpp|c|cpp)$")
|
||||
endif()
|
||||
|
||||
foreach(_target ${_ARGS_TARGETS})
|
||||
set(COMPILE_COMMAND_JSON "")
|
||||
|
||||
# Source Directory
|
||||
get_target_property(target_source_dir_rel ${_target} SOURCE_DIR)
|
||||
get_filename_component(target_source_dir ${target_source_dir_rel} ABSOLUTE)
|
||||
unset(target_source_dir_rel)
|
||||
|
||||
# Binary Directory
|
||||
get_target_property(target_binary_dir_rel ${_target} BINARY_DIR)
|
||||
get_filename_component(target_binary_dir ${target_binary_dir_rel} ABSOLUTE)
|
||||
unset(target_binary_dir_rel)
|
||||
|
||||
# Sources
|
||||
get_target_property(_els ${_target} SOURCES)
|
||||
set(target_sources "")
|
||||
foreach(_el ${_els})
|
||||
get_filename_component(_el "${_el}" ABSOLUTE)
|
||||
list(APPEND target_sources "${_el}")
|
||||
endforeach()
|
||||
list(FILTER target_sources INCLUDE REGEX "${_ARGS_REGEX}")
|
||||
|
||||
# Combine Compiler String
|
||||
set(COMPILER_OPTIONS "")
|
||||
## Compiler Options
|
||||
get_target_property(_els ${_target} COMPILE_OPTIONS)
|
||||
foreach(_el ${_els})
|
||||
string_append_escaped(OUTPUT COMPILER_OPTIONS INPUT "${_el} ")
|
||||
endforeach()
|
||||
string(APPEND COMPILER_OPTIONS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ")
|
||||
## C++ Standard
|
||||
get_target_property(_el ${_target} CXX_STANDARD)
|
||||
if(COMPILER_TAG STREQUAL "MSVC")
|
||||
if(${MSVC_VERSION} LESS "1920")
|
||||
message(FATAL_ERROR "clang-tidy: Current Compiler is not yet supported, please open an issue for it.")
|
||||
else()
|
||||
if((_el EQUAL 98) OR (_el EQUAL 11))
|
||||
# Nothing to do, this is the default.
|
||||
elseif(_el EQUAL 14)
|
||||
string(APPEND COMPILER_OPTIONS "/std:c++14 ")
|
||||
elseif(_el EQUAL 17)
|
||||
string(APPEND COMPILER_OPTIONS "/std:c++17 ")
|
||||
elseif(_el EQUAL 20)
|
||||
string(APPEND COMPILER_OPTIONS "/std:c++latest ")
|
||||
endif()
|
||||
endif()
|
||||
elseif((COMPILER_TAG STREQUAL "CLANG") OR (COMPILER_TAG STREQUAL "GNU"))
|
||||
if(_el EQUAL 98)
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++98 ")
|
||||
elseif(_el EQUAL 11)
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++11 ")
|
||||
elseif(_el EQUAL 14)
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++14 ")
|
||||
elseif(_el EQUAL 17)
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++17 ")
|
||||
elseif(_el EQUAL 20)
|
||||
if((COMPILER_TAG STREQUAL "CLANG") OR (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9))
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++20 ")
|
||||
else()
|
||||
string(APPEND COMPILER_OPTIONS "-std=c++2a ")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
## Definitions
|
||||
get_target_property(_els ${_target} COMPILE_DEFINITIONS)
|
||||
foreach(_el ${_els})
|
||||
string_append_escaped(OUTPUT COMPILER_OPTIONS INPUT "${COMPILER_DEFINE_PREFIX}${_el} ")
|
||||
endforeach()
|
||||
## Includes
|
||||
string_append_target_includes(TARGET ${_target} PREFIX ${COMPILER_INCLUDE_PREFIX} OUTPUT COMPILER_OPTIONS)
|
||||
|
||||
# Create Compilation Database
|
||||
set(target_compile_db "${target_binary_dir}/compile_commands.json")
|
||||
string(APPEND COMPILE_COMMAND_JSON "[\n")
|
||||
foreach(_el ${target_sources})
|
||||
file(TO_NATIVE_PATH "${_el}" _el)
|
||||
string(REPLACE "\\" "\\\\" _el "${_el}")
|
||||
string(REPLACE "\"" "\\\"" _el "${_el}")
|
||||
|
||||
string(APPEND COMPILE_COMMAND_JSON "\t{\n")
|
||||
string(APPEND COMPILE_COMMAND_JSON "\t\t\"directory\": \"${target_binary_dir}\",\n")
|
||||
string(APPEND COMPILE_COMMAND_JSON "\t\t\"file\": \"${_el}\",\n")
|
||||
if(MSVC)
|
||||
string(APPEND COMPILE_COMMAND_JSON "\t\t\"command\": \"cl ")
|
||||
else()
|
||||
string(APPEND COMPILE_COMMAND_JSON "\t\t\"command\": \"cc ")
|
||||
endif()
|
||||
|
||||
string(APPEND COMPILE_COMMAND_JSON "${COMPILER_OPTIONS}")
|
||||
|
||||
if(MSVC)
|
||||
string(APPEND COMPILE_COMMAND_JSON "${_el}")
|
||||
else()
|
||||
string(APPEND COMPILE_COMMAND_JSON " -c ${_el}")
|
||||
endif()
|
||||
string(APPEND COMPILE_COMMAND_JSON "\",\n\t},\n")
|
||||
endforeach()
|
||||
string(APPEND COMPILE_COMMAND_JSON "]")
|
||||
|
||||
file(GENERATE OUTPUT "$<TARGET_PROPERTY:${_target},BINARY_DIR>/$<CONFIG>/compile_commands.json" CONTENT "${COMPILE_COMMAND_JSON}")
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(clang_format)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_CLANG_FORMAT
|
||||
_ARGS
|
||||
"DEPENDENCY;GLOBAL"
|
||||
"REGEX;VERSION"
|
||||
"TARGETS"
|
||||
@@ -13,7 +228,7 @@ function(clang_format)
|
||||
"clang-format"
|
||||
DOC "Path (or name) of the clang-format binary"
|
||||
HINTS
|
||||
${CLANG_PATH}
|
||||
"${CLANG_PATH}"
|
||||
PATHS
|
||||
/bin
|
||||
/sbin
|
||||
@@ -30,44 +245,43 @@ function(clang_format)
|
||||
endif()
|
||||
|
||||
# Validate Version
|
||||
if (_CLANG_FORMAT_VERSION)
|
||||
if (_ARGS_VERSION)
|
||||
set(_VERSION_RESULT "")
|
||||
set(_VERSION_OUTPUT "")
|
||||
execute_process(
|
||||
COMMAND "${CLANG_FORMAT_BIN}" --version
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
||||
RESULT_VARIABLE _VERSION_RESULT
|
||||
OUTPUT_VARIABLE _VERSION_OUTPUT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ERROR_QUIET
|
||||
OUTPUT_QUIET ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT _VERSION_RESULT EQUAL 0)
|
||||
message(WARNING "Clang: Could not discover version, disabling clang-format...")
|
||||
return()
|
||||
endif()
|
||||
string(REGEX MATCH "([0-9]+\.[0-9]+\.[0-9]+)" _VERSION_MATCH ${_VERSION_OUTPUT})
|
||||
if(NOT ${_VERSION_MATCH} VERSION_GREATER_EQUAL ${_CLANG_FORMAT_VERSION})
|
||||
if(NOT ${_VERSION_MATCH} VERSION_GREATER_EQUAL ${_ARGS_VERSION})
|
||||
message(WARNING "Clang: Old version discovered, disabling clang-format...")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Default Filter
|
||||
if(NOT _CLANG_FORMAT_FILTER)
|
||||
set(_CLANG_FORMAT_FILTER "\.(h|hpp|c|cpp)$")
|
||||
if(NOT _ARGS_REGEX)
|
||||
set(_ARGS_REGEX "\.(h|hpp|c|cpp)$")
|
||||
endif()
|
||||
|
||||
# Go through each target
|
||||
foreach(_target ${_CLANG_FORMAT_TARGETS})
|
||||
# get_target_property(target_name ${_target} NAME)
|
||||
|
||||
foreach(_target ${_ARGS_TARGETS})
|
||||
get_target_property(target_sources_rel ${_target} SOURCES)
|
||||
set(target_sources "")
|
||||
foreach(source_relative ${target_sources_rel})
|
||||
get_filename_component(source_absolute ${source_relative} ABSOLUTE)
|
||||
list(APPEND target_sources ${source_absolute})
|
||||
foreach(_el ${target_sources_rel})
|
||||
get_filename_component(_el "${_el}" ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${_el}" _el)
|
||||
list(APPEND target_sources "${_el}")
|
||||
endforeach()
|
||||
list(FILTER target_sources INCLUDE REGEX "${_CLANG_FORMAT_FILTER}")
|
||||
list(FILTER target_sources INCLUDE REGEX "${_ARGS_REGEX}")
|
||||
unset(target_sources_rel)
|
||||
|
||||
get_target_property(target_source_dir_rel ${_target} SOURCE_DIR)
|
||||
@@ -75,22 +289,16 @@ function(clang_format)
|
||||
unset(target_source_dir_rel)
|
||||
|
||||
add_custom_target(${_target}_CLANG-FORMAT
|
||||
COMMAND
|
||||
${CLANG_FORMAT_BIN}
|
||||
-style=file
|
||||
-i
|
||||
${target_sources}
|
||||
COMMENT
|
||||
"clang-format: Formatting ${_target}..."
|
||||
WORKING_DIRECTORY
|
||||
${target_source_dir_rel}
|
||||
COMMAND "${CLANG_FORMAT_BIN}" -style=file -i ${target_sources}
|
||||
COMMENT "clang-format: Formatting ${_target}..."
|
||||
WORKING_DIRECTORY "${target_source_dir}"
|
||||
)
|
||||
|
||||
if(_CLANG_FORMAT_DEPENDENCY)
|
||||
if(_ARGS_DEPENDENCY)
|
||||
add_dependencies(${_target} ${_target}_CLANG-FORMAT)
|
||||
endif()
|
||||
|
||||
if(_CLANG_FORMAT_GLOBAL)
|
||||
if(_ARGS_GLOBAL)
|
||||
if(TARGET CLANG-FORMAT)
|
||||
add_dependencies(CLANG-FORMAT ${_target}_CLANG-FORMAT)
|
||||
else()
|
||||
@@ -104,3 +312,120 @@ function(clang_format)
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
function(clang_tidy)
|
||||
cmake_parse_arguments(
|
||||
PARSE_ARGV 0
|
||||
_ARGS
|
||||
"DEPENDENCY;GLOBAL"
|
||||
"REGEX;VERSION"
|
||||
"TARGETS"
|
||||
)
|
||||
|
||||
find_program(CLANG_TIDY_BIN
|
||||
"clang-tidy"
|
||||
DOC "Path (or name) of the clang-tidy binary"
|
||||
HINTS
|
||||
"${CLANG_PATH}"
|
||||
PATHS
|
||||
/bin
|
||||
/sbin
|
||||
/usr/bin
|
||||
/usr/local/bin
|
||||
PATH_SUFFIXES
|
||||
bin
|
||||
bin64
|
||||
bin32
|
||||
)
|
||||
if(NOT CLANG_TIDY_BIN)
|
||||
message(WARNING "Clang: Could not find clang-tidy at path '${CLANG_TIDY_BIN}', disabling clang-tidy...")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Validate Version
|
||||
if (_ARGS_VERSION)
|
||||
set(_VERSION_RESULT "")
|
||||
set(_VERSION_OUTPUT "")
|
||||
execute_process(
|
||||
COMMAND "${CLANG_TIDY_BIN}" --version
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
|
||||
RESULT_VARIABLE _VERSION_RESULT
|
||||
OUTPUT_VARIABLE _VERSION_OUTPUT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT _VERSION_RESULT EQUAL 0)
|
||||
message(WARNING "Clang: Could not discover version, disabling clang-tidy...")
|
||||
return()
|
||||
endif()
|
||||
string(REGEX MATCH "([0-9]+\.[0-9]+\.[0-9]+)" _VERSION_MATCH ${_VERSION_OUTPUT})
|
||||
if(NOT ${_VERSION_MATCH} VERSION_GREATER_EQUAL ${_ARGS_VERSION})
|
||||
message(WARNING "Clang: Old version discovered, disabling clang-tidy...")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Default Filter
|
||||
if(NOT _ARGS_REGEX)
|
||||
set(_ARGS_REGEX "\.(h|hpp|c|cpp)$")
|
||||
endif()
|
||||
|
||||
# Go through each target
|
||||
foreach(_target ${_ARGS_TARGETS})
|
||||
# Source Directory
|
||||
get_target_property(_els ${_target} SOURCE_DIR)
|
||||
get_filename_component(target_source_dir ${_els} ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${target_source_dir}" target_source_dir_nat)
|
||||
unset(_els)
|
||||
|
||||
# Binary Directory
|
||||
get_target_property(_els ${_target} BINARY_DIR)
|
||||
get_filename_component(target_binary_dir ${_els} ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${target_binary_dir}" target_binary_dir_nat)
|
||||
unset(_els)
|
||||
|
||||
# Sources
|
||||
get_target_property(_els ${_target} SOURCES)
|
||||
set(target_sources "")
|
||||
foreach(_el ${_els})
|
||||
get_filename_component(_el ${_el} ABSOLUTE)
|
||||
file(TO_NATIVE_PATH "${_el}" _el)
|
||||
list(APPEND target_sources "${_el}")
|
||||
endforeach()
|
||||
list(FILTER target_sources INCLUDE REGEX "${_ARGS_REGEX}")
|
||||
unset(_els)
|
||||
|
||||
add_custom_target(${_target}_CLANG-TIDY
|
||||
COMMENT "clang-tiy: Tidying ${_target}..."
|
||||
WORKING_DIRECTORY "${target_binary_dir}"
|
||||
VERBATIM
|
||||
)
|
||||
foreach(_el ${target_sources})
|
||||
add_custom_command(
|
||||
TARGET ${_target}_CLANG-TIDY
|
||||
POST_BUILD
|
||||
COMMAND "${CLANG_TIDY_BIN}"
|
||||
ARGS --quiet -p="$<TARGET_PROPERTY:${_target},BINARY_DIR>/$<CONFIG>" "${_el}"
|
||||
WORKING_DIRECTORY "${target_binary_dir}"
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
endforeach()
|
||||
|
||||
if(_ARGS_DEPENDENCY)
|
||||
add_dependencies(${_target} ${_target}_CLANG-TIDY)
|
||||
endif()
|
||||
|
||||
if(_ARGS_GLOBAL)
|
||||
if(TARGET CLANG-TIDY)
|
||||
add_dependencies(CLANG-TIDY ${_target}_CLANG-FORTIDYMAT)
|
||||
else()
|
||||
add_custom_target(CLANG-TIDY
|
||||
DEPENDS
|
||||
${_target}_CLANG-TIDY
|
||||
COMMENT
|
||||
"clang-tiy: Tidying..."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
Reference in New Issue
Block a user