diff --git a/CMake/ITKClangFormatConfig.cmake.in b/CMake/ITKClangFormatConfig.cmake.in index a44b02adc33..fb58ddc0838 100644 --- a/CMake/ITKClangFormatConfig.cmake.in +++ b/CMake/ITKClangFormatConfig.cmake.in @@ -1,6 +1,5 @@ set(ITK_SOURCE_DIR "@ITK_SOURCE_DIR@") set(CMAKE_SOURCE_DIR "@CMAKE_SOURCE_DIR@") -set(CLANG_FORMAT_EXECUTABLE "@CLANG_FORMAT_EXECUTABLE@") set(WORKING_DIR "") if(ITK_SOURCE_DIR) @@ -9,9 +8,16 @@ else() set(WORKING_DIR "${CMAKE_SOURCE_DIR}") endif() -find_package(Git) -if(GIT_FOUND AND EXISTS "${WORKING_DIR}/.git/config") - execute_process(COMMAND ${GIT_EXECUTABLE} config clangFormat.binary - "${CLANG_FORMAT_EXECUTABLE}" - WORKING_DIRECTORY ${WORKING_DIR}) +if (EXISTS "@ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE@") + find_package(Git) + if(GIT_FOUND AND EXISTS "${WORKING_DIR}/.git/config") + execute_process( + COMMAND + ${GIT_EXECUTABLE} config clangFormat.binary "@ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE@" + WORKING_DIRECTORY + ${WORKING_DIR} + ) + endif() +else() + message(STATUS "Valid clang-format binary could not be found. Skipping 'git config clangFormat.binary'") endif() diff --git a/CMake/ITKClangFormatSetup.cmake b/CMake/ITKClangFormatSetup.cmake deleted file mode 100644 index 9ded94935e0..00000000000 --- a/CMake/ITKClangFormatSetup.cmake +++ /dev/null @@ -1,61 +0,0 @@ -# Provides itk_clangformat_setup to assist with maintaining consistent -# style across the ITK toolkit -# -# Globally the ${ITK_SOURCE_DIR}/.clang-format file is used to configure the enforced style. -# -# Clang version 8.0 is required -# -# The ITK style guidelines are represented by clang-format version 8.0.0 -# rules defined in ${ITK_SOURCE_DIR}/.clang-format -# -option(ITK_USE_CLANG_FORMAT "Enable the use of clang-format enforce ITK coding style." ${BUILD_TESTING}) -mark_as_advanced(ITK_USE_CLANG_FORMAT) - -find_program(CLANG_FORMAT_EXECUTABLE NAMES clang-format-8.0 clang-format8 clang-format) -mark_as_advanced(CLANG_FORMAT_EXECUTABLE) -if(ITK_USE_CLANG_FORMAT AND NOT CLANG_FORMAT_EXECUTABLE) - # Download pre-built binaries (about 2M) of clang-format extracted from - # https://releases.llvm.org/download.html and cached on data.kitware.com - # - # Darwin - macOS/OS X (Clang, GCC) https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz - # Windows - Windows (Visual Studio, MinGW GCC) https://releases.llvm.org/8.0.0/LLVM-8.0.0-win64.exe - # https://releases.llvm.org/8.0.0/LLVM-8.0.0-win32.exe - # Linux - Linux (GCC, Intel, PGI) https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-sles11.3.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-armv7a-linux-gnueabihf.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-aarch64-linux-gnu.tar.xz - # - # FreeBSD - FreeBSD https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-amd64-unknown-freebsd11.tar.xz - # https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-i386-unknown-freebsd11.tar.xz - # - # Android - Android NDK (GCC, Clang) - # CrayLinuxEnvironment - Cray supercomputers (Cray compiler) - # MSYS - Windows (MSYS2 shell native GCC) - if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") - set(CLANG_FORMAT_DOWNLOAD_URL "https://data.kitware.com/api/v1/file/5d274e88877dfcc902effc47/download") - elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") - set(CLANG_FORMAT_DOWNLOAD_URL "https://data.kitware.com/api/v1/file/5d2b8775877dfcc902fd8236/download") - elseif (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(CLANG_FORMAT_DOWNLOAD_URL "https://data.kitware.com/api/v1/file/5d2b8c87877dfcc902fda594/download") - else() - message(FATAL_ERROR "System binaries not available: set CLANG_FORMAT_EXECUTABLE to clang-format 8 or disable ITK_USE_CLANG_FORMAT.") - endif() - file(MAKE_DIRECTORY "${ITK_BINARY_DIR}/temp") - if(WIN32) - set(exe .exe) - endif() - set(CLANG_FORMAT_EXECUTABLE_NAME "clang-format-${CMAKE_SYSTEM_NAME}${exe}") - file(DOWNLOAD "${CLANG_FORMAT_DOWNLOAD_URL}" "${ITK_BINARY_DIR}/temp/${CLANG_FORMAT_EXECUTABLE_NAME}") - file( - COPY "${ITK_BINARY_DIR}/temp/${CLANG_FORMAT_EXECUTABLE_NAME}" - DESTINATION "${ITK_BINARY_DIR}" - FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ) - unset(CLANG_FORMAT_DOWNLOAD_URL) - set(CLANG_FORMAT_EXECUTABLE - "${ITK_BINARY_DIR}/${CLANG_FORMAT_EXECUTABLE_NAME}" - CACHE FILEPATH "The binary for clang-format" FORCE) -elseif(ITK_USE_CLANG_FORMAT AND NOT CLANG_FORMAT_EXECUTABLE) - message(FATAL_ERROR "Missing suitable clang-format executable, set CLANG_FORMAT_EXECUTABLE to clang-format 8 or disable ITK_USE_CLANG_FORMAT.") -endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index cc05e3deb5d..4164ba14fd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -578,8 +578,6 @@ add_subdirectory(Modules/Remote) # Enable modules according to user inputs and the module dependency DAG. include(ITKModuleEnablement) -# Setup clang-format for code style enforcement -include(ITKClangFormatSetup) # Setup clang-tidy for code best-practices enforcement for C++11 include(ITKClangTidySetup) #---------------------------------------------------------------------- diff --git a/Utilities/ClangFormat/DownloadClangFormat.cmake b/Utilities/ClangFormat/DownloadClangFormat.cmake index 6c8f392b0ca..3a523bfa879 100644 --- a/Utilities/ClangFormat/DownloadClangFormat.cmake +++ b/Utilities/ClangFormat/DownloadClangFormat.cmake @@ -1,67 +1,176 @@ -include(ExternalProject) +# ----------------------------------------------------------------------------------------- +# Provides clang-format identification to assist with maintaining consistent +# style across the ITK toolkit -if(WIN32) - set(exe .exe) -endif() -set(CLANG_FORMAT_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/ClangFormat-prefix/src/ClangFormat/clang-format${exe}) -configure_file("${ITK_CMAKE_DIR}/ITKClangFormatConfig.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/ITKClangFormatConfig.cmake" @ONLY) +# Globally the ${ITK_SOURCE_DIR}/.clang-format file is used to configure the enforced style. +# +# +# Step 1: Search for local binaries that have the correct clang-format version +# if acceptable version not found do step 2 +# Step 2a [OPTIONAL]: Configure to External project for future download of clang-format binaries +# setting variables to values that will eventually be the location of the +# binaries that are downloaded during the build phase of ITK as part of +# the ClangFormat build target +# Step 2b [OPTIONAL]: Build ITK, including the ClangFormat target that downloads clang-format binaries +# Step 2c [OPTIONAL]: Search again for binaries that meet the version requirements. +# Step 3: configure ITKClangFormatConfig.cmake.in to setup the git controlled hooks to clang-format +# with best guess of what the executable will be after building ITK. +# +# The ITK style guidelines are represented by clang-format version ${CLANG_FORMAT_MIN_VERSION} +# rules defined in ${ITK_SOURCE_DIR}/.clang-format +set(CLANG_FORMAT_MIN_VERSION 8.0) # First acceptable version +set(CLANG_FORMAT_MAX_VERSION 9.0) # First unacceptable version + +function(check_clang_format_version EXECUTABLE_FOUND_VARNAME CLANG_FORMAT_EXECUTABLE) + # This function has the required signature for the + # VALIDATOR feature of cmake >= 3.25 find_program function + # + # The check_clang_format_version function test that correct version of clang-format is available + # + if(CLANG_FORMAT_EXECUTABLE) + # Get the version of clang-format + execute_process( + COMMAND ${CLANG_FORMAT_EXECUTABLE} --version + OUTPUT_VARIABLE CLANG_FORMAT_VERSION_RESPONSE + ) + string(REGEX MATCH "([0-9]+\\.[0-9]+\\.[0-9]+)" VERSION_MATCH "${CLANG_FORMAT_VERSION_RESPONSE}") + + # Extract the matched version number + if(VERSION_MATCH) + set(CLANG_FORMAT_VERSION "${CMAKE_MATCH_1}") + endif() + unset(CLANG_FORMAT_VERSION_RESPONSE) + unset(VERSION_MATCH) + + # Print the version + if( CLANG_FORMAT_VERSION VERSION_GREATER_EQUAL "${CLANG_FORMAT_MIN_VERSION}" AND CLANG_FORMAT_VERSION VERSION_LESS "${CLANG_FORMAT_MAX_VERSION}" ) + message(STATUS "Found CLANG_FORMAT_EXECUTABLE=${CLANG_FORMAT_EXECUTABLE} with acceptable version condition ${CLANG_FORMAT_MIN_VERSION} <= ${CLANG_FORMAT_VERSION} < ${CLANG_FORMAT_MAX_VERSION}") + set(${EXECUTABLE_FOUND_VARNAME} TRUE PARENT_SCOPE) + else() + message(STATUS "unacceptable version found: CLANG_FORMAT_EXECUTABLE=${CLANG_FORMAT_EXECUTABLE}") + message(STATUS "unmet version condition: ${CLANG_FORMAT_MIN_VERSION} <= ${CLANG_FORMAT_VERSION} < ${CLANG_FORMAT_MAX_VERSION}" ) + set(${EXECUTABLE_FOUND_VARNAME} FALSE PARENT_SCOPE) + endif() + else() + set(${EXECUTABLE_FOUND_VARNAME} FALSE PARENT_SCOPE) + endif() + unset(CLANG_FORMAT_VERSION) + unset(CLANG_FORMAT_MIN_VERSION) + unset(CLANG_FORMAT_MAX_VERSION) +endfunction() -set(_clang_format_hash) -set(_clang_format_url) -if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") - set(_clang_format_hash - b14de32036c48f6c62998e2ebab509e71a0ae71464acb4616484e3a6eb941e1d9fac38559f5d27ea0cbbb512d590279ffb3015fae17779229e1090c2763ebcf3 - ) - set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") -elseif( - CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin" - AND ( CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64" ) - AND (NOT - CMAKE_HOST_SYSTEM_VERSION - VERSION_LESS - "13.0.0" - )) - set(_clang_format_hash - 97460f9eef556a27592ccd99d8fc894554e5b3196326df4e33bfcdecfcb7eda2b5c7488008abc4dd923d607f2cb47d61567b1da99df60f31f719195118c117a9 - ) - set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") -elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") - set(_clang_format_hash - e96dd15938fd9b1c57028a519189f138397774eb6b66971d114300d2a80248adda9f74b192985a3c91c7de52c4dbe21800bc6b3cc8201c4985fc39ecfc64fdbe - ) - set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") + +if(ITK_USE_CLANG_FORMAT) + get_filename_component(CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_DIR "${ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE}" DIRECTORY) + get_filename_component(CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_PROGRAM_NAME "${ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE}" NAME) + + if(CMAKE_VERSION LESS "3.25") + # If CLANG_FORMAT_EXECUTABLE is manually set to an incorrect version, unset the variable + check_clang_format_version( CORRECT_VERSION_CLANG_FORMAT_EXECUTABLE_FOUND "${CLANG_FORMAT_EXECUTABLE}") + if (NOT CORRECT_VERSION_CLANG_FORMAT_EXECUTABLE_FOUND) + unset(CLANG_FORMAT_EXECUTABLE CACHE) + endif() + unset(CORRECT_VERSION_CLANG_FORMAT_EXECUTABLE_FOUND) + + # Search for clang-format, if the first one found is not a valid version, then manually + # setting the correct version will be required. Use the downloaded binaries as the first + # check. + find_program(CLANG_FORMAT_EXECUTABLE + NAMES "${CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_PROGRAM_NAME}" clang-format + PATHS "${CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_DIR}" ) + check_clang_format_version( CLANG_FORMAT_EXECUTABLE_FOUND ${CLANG_FORMAT_EXECUTABLE}) + if (NOT CLANG_FORMAT_EXECUTABLE_FOUND) + message(FATAL_ERROR "Disable ITK_USE_CLANG_FORMAT or set CLANG_FORMAT_EXECUTABLE to one " + "in the acceptable version in range ${CLANG_FORMAT_MIN_VERSION} <= Version < ${CLANG_FORMAT_MAX_VERSION}" ) + else() + message(STATUS "clang-format with acceptable version found: ${CLANG_FORMAT_EXECUTABLE}" ) + endif() + else() + find_program(CLANG_FORMAT_EXECUTABLE + NAMES clang-format "${CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_PROGRAM_NAME}" + PATHS /Users/johnsonhj/Dashboard/src/ITK/venv/bin "${CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_DIR}" + VALIDATOR check_clang_format_version) + endif() + mark_as_advanced(CLANG_FORMAT_EXECUTABLE) + unset(CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_DIR) + unset(CLANG_FORMAT_EXECUTABLE_LOCAL_DOWNLOAD_PROGRAM_NAME) else() - # If desired, a compatible clang-format can be provided manually with - # `git config clangFormat.binary /path/to/clang-format`. + unset(CLANG_FORMAT_EXECUTABLE CACHE) endif() +# ====================== -# XXX Implementation of the itk_download_attempt_check macro copied from the -# ITK main CMakeLists.txt. This allows external modules to use the logic -# which is not defined when building against an ITK build tree. -# Equivalent to "itk_download_attempt_check(ClangFormat)". -if(ITK_FORBID_DOWNLOADS) - message(SEND_ERROR "Attempted to download ClangFormat when ITK_FORBID_DOWNLOADS is ON") -endif() -if(POLICY CMP0135) - cmake_policy(PUSH) - cmake_policy(SET CMP0135 NEW) -endif() -if(NOT TARGET ClangFormat AND _clang_format_hash) - ExternalProject_Add( - ClangFormat - URL ${_clang_format_url} - URL_HASH SHA512=${_clang_format_hash} - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - LOG_DOWNLOAD 1 - LOG_UPDATE 0 - LOG_CONFIGURE 0 - LOG_BUILD 0 - LOG_TEST 0 - LOG_INSTALL 0 - INSTALL_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/ITKClangFormatConfig.cmake) -endif() -if(POLICY CMP0135) - cmake_policy(POP) + +# setup external project do download local clang-format if acceptable version not found +if(NOT EXISTS CLANG_FORMAT_EXECUTABLE) + include(ExternalProject) + + if(WIN32) + set(exe .exe) + endif() + + # NOTE: Configuration of variables occurs during cmake config, download of binary occurs during build of ITK + set(ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/ClangFormat-prefix/src/ClangFormat/clang-format${exe}) + configure_file("${ITK_CMAKE_DIR}/ITKClangFormatConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/ITKClangFormatConfig.cmake" @ONLY) + + set(_clang_format_hash) + set(_clang_format_url) + if(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(_clang_format_hash + b14de32036c48f6c62998e2ebab509e71a0ae71464acb4616484e3a6eb941e1d9fac38559f5d27ea0cbbb512d590279ffb3015fae17779229e1090c2763ebcf3 + ) + set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") + elseif( + CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin" + AND ( CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64" ) + AND (NOT + CMAKE_HOST_SYSTEM_VERSION + VERSION_LESS + "13.0.0" + )) + set(_clang_format_hash + 97460f9eef556a27592ccd99d8fc894554e5b3196326df4e33bfcdecfcb7eda2b5c7488008abc4dd923d607f2cb47d61567b1da99df60f31f719195118c117a9 + ) + set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") + elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Windows" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") + set(_clang_format_hash + e96dd15938fd9b1c57028a519189f138397774eb6b66971d114300d2a80248adda9f74b192985a3c91c7de52c4dbe21800bc6b3cc8201c4985fc39ecfc64fdbe + ) + set(_clang_format_url "https://data.kitware.com/api/v1/file/hashsum/sha512/${_clang_format_hash}/download") + else() + # If desired, a compatible clang-format can be provided manually with + # `git config clangFormat.binary /path/to/clang-format`. + endif() + + # XXX Implementation of the itk_download_attempt_check macro copied from the + # ITK main CMakeLists.txt. This allows external modules to use the logic + # which is not defined when building against an ITK build tree. + # Equivalent to "itk_download_attempt_check(ClangFormat)". + if(ITK_FORBID_DOWNLOADS) + message(SEND_ERROR "Attempted to download ClangFormat when ITK_FORBID_DOWNLOADS is ON") + endif() + if(POLICY CMP0135) + cmake_policy(PUSH) + cmake_policy(SET CMP0135 NEW) + endif() + if(NOT TARGET ClangFormat AND _clang_format_hash) + ExternalProject_Add( + ClangFormat + URL ${_clang_format_url} + URL_HASH SHA512=${_clang_format_hash} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + LOG_DOWNLOAD 1 + LOG_UPDATE 0 + LOG_CONFIGURE 0 + LOG_BUILD 0 + LOG_TEST 0 + LOG_INSTALL 0 + INSTALL_COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/ITKClangFormatConfig.cmake) + endif() + if(POLICY CMP0135) + cmake_policy(POP) + endif() +else() + set(ITK_VERSIONED_CLANG_FORMAT_EXECUTABLE "${CLANG_FORMAT_EXECUTABLE}") endif()