# Copyright (C) 2025-2026 Advanced Micro Devices, Inc. # SPDX-License-Identifier: MIT # # Shared helpers for finding and version-checking LLVM/Clang tools. # Generalized from dnn-providers/cmake/CheckToolVersion.cmake. if(NOT EXPECTED_CLANG_FORMAT_VERSION) set(EXPECTED_CLANG_FORMAT_VERSION "18") endif() if(NOT EXPECTED_CLANG_TIDY_VERSION) set(EXPECTED_CLANG_TIDY_VERSION "20") endif() if(NOT EXPECTED_LLVM_VERSION) set(EXPECTED_LLVM_VERSION "20") endif() # Allows using any tool when the found version doesn't match expected. # Individual tools can still be explicitly enabled via e.g. -DALLOW_CLANG_FORMAT_VERSION_MISMATCH=ON # when ALLOW_TOOL_VERSION_MISMATCH is OFF. option(ALLOW_TOOL_VERSION_MISMATCH "Allow any tool version different from its expected version (warn but continue)" OFF ) if(ALLOW_TOOL_VERSION_MISMATCH) set(ALLOW_CLANG_FORMAT_VERSION_MISMATCH ON) set(ALLOW_CLANG_TIDY_VERSION_MISMATCH ON) set(ALLOW_LLVM_VERSION_MISMATCH ON) endif() # Build a list of versioned tool search paths from a base directory. function(get_versioned_search_paths OUTPUT_VAR BASE_PATH VERSION) set(PATHS_LIST "${BASE_PATH}${VERSION}/bin" "${BASE_PATH}${VERSION}/lib/llvm/bin" "${BASE_PATH}/${VERSION}/bin" "${BASE_PATH}/${VERSION}/lib/llvm/bin" "${BASE_PATH}/bin" "${BASE_PATH}/lib/llvm/bin" ) set(${OUTPUT_VAR} ${PATHS_LIST} PARENT_SCOPE) endfunction() if(NOT WIN32) set(LLVM_TOOL_PATHS /usr/bin /usr/local/bin /opt/rocm/llvm/bin) if(DEFINED ROCM_PATH) list(APPEND LLVM_TOOL_PATHS ${ROCM_PATH}/llvm/bin) endif() endif() if(DEFINED LLVM_TOOLS_SEARCH_PREFIX) set(LLVM_TOOL_HINTS "${LLVM_TOOLS_SEARCH_PREFIX}") elseif(DEFINED ENV{LLVM_TOOLS_SEARCH_PREFIX}) set(LLVM_TOOL_HINTS "$ENV{LLVM_TOOLS_SEARCH_PREFIX}") endif() # Verify that a tool binary matches the expected major version. function(checkToolVersion TOOL_BINARY TOOL_NAME EXPECTED_VERSION VERSION_REGEX SUCCESS_MESSAGE_FORMAT ) execute_process( COMMAND ${TOOL_BINARY} --version OUTPUT_VARIABLE VERSION_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE ) if(VERSION_OUTPUT MATCHES "${VERSION_REGEX}") set(TOOL_MAJOR_VERSION "${CMAKE_MATCH_1}") if(NOT TOOL_MAJOR_VERSION STREQUAL EXPECTED_VERSION) message( WARNING "${TOOL_NAME} version mismatch! Expected: ${EXPECTED_VERSION}, " "Found: ${TOOL_MAJOR_VERSION}, " "Path: ${TOOL_BINARY}, " "Full version: ${VERSION_OUTPUT}" ) set(${TOOL_NAME}_VERSION_MATCHED FALSE PARENT_SCOPE) else() string(REPLACE "{VERSION}" "${TOOL_MAJOR_VERSION}" SUCCESS_MSG "${SUCCESS_MESSAGE_FORMAT}" ) string(REPLACE "{PATH}" "${TOOL_BINARY}" SUCCESS_MSG "${SUCCESS_MSG}") string(APPEND SUCCESS_MSG " -- ${VERSION_OUTPUT}") message(STATUS "${SUCCESS_MSG}") set(${TOOL_NAME}_VERSION_MATCHED TRUE PARENT_SCOPE) endif() set(${TOOL_NAME}_MAJOR_VERSION ${TOOL_MAJOR_VERSION} PARENT_SCOPE) else() message(WARNING "Could not determine ${TOOL_NAME} version from: ${VERSION_OUTPUT}") set(${TOOL_NAME}_MAJOR_VERSION "unknown" PARENT_SCOPE) set(${TOOL_NAME}_VERSION_MATCHED FALSE PARENT_SCOPE) endif() endfunction() # Find a tool by name and check its version. # ~~~ # Parameters: # OUTPUT_VAR - Variable name to store the found tool path # TOOL_NAME - The single name of the single tool to search for (e.g., "clang-format") # EXPECTED_VERSION - Expected version to search for (typically assumed to be the tool's major version number) # VERSION_REGEX - Regex to extract the relevant portion to match EXPECTED_VERSION from the tool's --version output # ERROR_LEVEL - FATAL_ERROR, WARNING, STATUS, or VERBOSE for the not found message # Optional keyword arguments: # ALLOW_MISMATCH - If present, a version mismatch issues a warning but the tool is still # used. By default, a version mismatch disables the tool. # ~~~ function(findAndCheckTool OUTPUT_VAR TOOL_NAME EXPECTED_VERSION VERSION_REGEX ERROR_LEVEL) cmake_parse_arguments(PARSE_ARGV 5 _CHECK_TOOL_VERSION "ALLOW_MISMATCH" "" "") set(SEARCH_HINTS) if(DEFINED LLVM_TOOL_HINTS) foreach(HINT ${LLVM_TOOL_HINTS}) get_versioned_search_paths(VERSIONED_HINTS "${HINT}" "${EXPECTED_VERSION}") list(APPEND SEARCH_HINTS ${VERSIONED_HINTS}) endforeach() endif() find_program( ${OUTPUT_VAR} NAMES ${TOOL_NAME}-${EXPECTED_VERSION} ${TOOL_NAME} HINTS ${SEARCH_HINTS} PATHS ${LLVM_TOOL_PATHS} ) if(NOT ${OUTPUT_VAR}) string(REPLACE ";" "\n " FORMATTED_HINTS "${SEARCH_HINTS}") if(ERROR_LEVEL STREQUAL "FATAL_ERROR") message( FATAL_ERROR "\n${TOOL_NAME} not found in searching PATH, CMake PROGRAM and PREFIX paths, and these LLVM_TOOLS_SEARCH_PREFIX derived paths:\n ${FORMATTED_HINTS}\n" ) else() message( ${ERROR_LEVEL} "${TOOL_NAME} not found in searching PATH, CMake PROGRAM and PREFIX paths, and LLVM_TOOLS_SEARCH_PREFIX derived paths" ) endif() return() endif() if(VERSION_REGEX STREQUAL "SKIP_VERSION_CHECK") message(STATUS "Found ${TOOL_NAME} at ${${OUTPUT_VAR}}") else() checktoolversion( ${${OUTPUT_VAR}} "${TOOL_NAME}" ${EXPECTED_VERSION} "${VERSION_REGEX}" "Found ${TOOL_NAME} version {VERSION} at {PATH}" ) # checkToolVersion() already warned on mismatch if(NOT ${TOOL_NAME}_VERSION_MATCHED AND NOT _CHECK_TOOL_VERSION_ALLOW_MISMATCH) message(WARNING "${TOOL_NAME} disabled due to version mismatch " "(expected ${EXPECTED_VERSION}). Update your environment and perform " "a fresh cmake configure to set ${OUTPUT_VAR} to the needed version." ) unset(${OUTPUT_VAR} CACHE) return() endif() endif() set(${OUTPUT_VAR} ${${OUTPUT_VAR}} PARENT_SCOPE) endfunction() # Find clang-format and verify its version. function(findAndCheckClangFormat) set(_allow_mismatch_arg "") if(ALLOW_CLANG_FORMAT_VERSION_MISMATCH) set(_allow_mismatch_arg ALLOW_MISMATCH) endif() findandchecktool( CLANG_FORMAT_BINARY "clang-format" ${EXPECTED_CLANG_FORMAT_VERSION} "clang-format version ([0-9]+)\\." FATAL_ERROR ${_allow_mismatch_arg} ) set(CLANG_FORMAT_BINARY ${CLANG_FORMAT_BINARY} PARENT_SCOPE) endfunction() # Find clang-tidy and run-clang-tidy, verify versions. function(findAndCheckClangTidy) if(ENABLE_CLANG_TIDY) set(_not_found_log_level FATAL_ERROR) else() set(_not_found_log_level STATUS) endif() set(_allow_mismatch_arg "") if(ALLOW_CLANG_TIDY_VERSION_MISMATCH) set(_allow_mismatch_arg ALLOW_MISMATCH) endif() findandchecktool( CLANG_TIDY_EXE "clang-tidy" ${EXPECTED_CLANG_TIDY_VERSION} "version ([0-9]+)\\." ${_not_found_log_level} ${_allow_mismatch_arg} ) set(CLANG_TIDY_EXE ${CLANG_TIDY_EXE} PARENT_SCOPE) findandchecktool( RUN_CLANG_TIDY_EXE "run-clang-tidy" ${EXPECTED_CLANG_TIDY_VERSION} "SKIP_VERSION_CHECK" VERBOSE ) if(RUN_CLANG_TIDY_EXE) set(RUN_CLANG_TIDY_EXE ${RUN_CLANG_TIDY_EXE} PARENT_SCOPE) endif() endfunction()