8746aa57ae
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.
432 lines
12 KiB
CMake
432 lines
12 KiB
CMake
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
|
|
_ARGS
|
|
"DEPENDENCY;GLOBAL"
|
|
"REGEX;VERSION"
|
|
"TARGETS"
|
|
)
|
|
|
|
find_program(CLANG_FORMAT_BIN
|
|
"clang-format"
|
|
DOC "Path (or name) of the clang-format binary"
|
|
HINTS
|
|
"${CLANG_PATH}"
|
|
PATHS
|
|
/bin
|
|
/sbin
|
|
/usr/bin
|
|
/usr/local/bin
|
|
PATH_SUFFIXES
|
|
bin
|
|
bin64
|
|
bin32
|
|
)
|
|
if(NOT CLANG_FORMAT_BIN)
|
|
message(WARNING "Clang: Could not find clang-format at path '${CLANG_FORMAT_BIN}', disabling clang-format...")
|
|
return()
|
|
endif()
|
|
|
|
# Validate Version
|
|
if (_ARGS_VERSION)
|
|
set(_VERSION_RESULT "")
|
|
set(_VERSION_OUTPUT "")
|
|
execute_process(
|
|
COMMAND "${CLANG_FORMAT_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-format...")
|
|
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-format...")
|
|
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})
|
|
get_target_property(target_sources_rel ${_target} SOURCES)
|
|
set(target_sources "")
|
|
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 "${_ARGS_REGEX}")
|
|
unset(target_sources_rel)
|
|
|
|
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)
|
|
|
|
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}"
|
|
)
|
|
|
|
if(_ARGS_DEPENDENCY)
|
|
add_dependencies(${_target} ${_target}_CLANG-FORMAT)
|
|
endif()
|
|
|
|
if(_ARGS_GLOBAL)
|
|
if(TARGET CLANG-FORMAT)
|
|
add_dependencies(CLANG-FORMAT ${_target}_CLANG-FORMAT)
|
|
else()
|
|
add_custom_target(CLANG-FORMAT
|
|
DEPENDS
|
|
${_target}_CLANG-FORMAT
|
|
COMMENT
|
|
"clang-format: Formatting..."
|
|
)
|
|
endif()
|
|
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()
|