diff --git a/cmake/conan_provider.cmake b/cmake/conan_provider.cmake index 0c8d6f7..d33faf5 100644 --- a/cmake/conan_provider.cmake +++ b/cmake/conan_provider.cmake @@ -1,10 +1,8 @@ -# Source: https://github.com/conan-io/cmake-conan/blob/b27d3eeb8cfec690e5efa30d526b3f48a1d522c1/conan_provider.cmake -# Modifications: -# 1. Added CONAN_EXTRA_INSTALL_ARGS variable. +# Source: https://github.com/valgur/cmake-conan/blob/7672df47f779f7be3e46ecc2aab62d993d42699d/conan_provider.cmake # The MIT License (MIT) # -# Copyright (c) 2019 JFrog +# Copyright (c) 2024 JFrog # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -24,8 +22,27 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -set(CONAN_MINIMUM_VERSION 2.0.5) - +# Configurable variables +set(CONAN_MINIMUM_VERSION "2.0.5" CACHE STRING "Minimum required Conan version") +set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") +set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") +set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install") +set(CONAN_DOWNLOAD "if-missing" CACHE STRING "Download the Conan client (always, if-missing or never)") +set(CONAN_DOWNLOAD_VERSION "latest" CACHE STRING "Download a specific Conan version") +set(CONAN_ISOLATE_HOME "if-downloaded" CACHE STRING "Set $CONAN_HOME to \${CMAKE_BINARY_DIR}/conan_home (always, if-downloaded or never)") + +# Create a new policy scope and set the minimum required cmake version so the +# features behind a policy setting like if(... IN_LIST ...) behaves as expected +# even if the parent project does not specify a minimum cmake version or a minimum +# version less than this module requires (e.g. 3.0) before the first project() call. +# (see: https://cmake.org/cmake/help/latest/variable/CMAKE_PROJECT_TOP_LEVEL_INCLUDES.html) +# +# The policy-affecting calls like cmake_policy(SET...) or `cmake_minimum_required` only +# affects the current policy scope, i.e. between the PUSH and POP in this case. +# +# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Policies.html#the-policy-stack +cmake_policy(PUSH) +cmake_minimum_required(VERSION 3.24) function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION) # it could be cross compilation @@ -96,7 +113,7 @@ function(detect_arch ARCH) message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.") endif() endif() - if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS") + if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") set(host_arch ${CMAKE_OSX_ARCHITECTURES}) elseif(MSVC) set(host_arch ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) @@ -300,10 +317,14 @@ macro(set_conan_compiler_if_appleclang lang command output_variable) if(CMAKE_${lang}_COMPILER_ID STREQUAL "AppleClang") execute_process(COMMAND xcrun --find ${command} OUTPUT_VARIABLE _xcrun_out OUTPUT_STRIP_TRAILING_WHITESPACE) - if (_xcrun_out STREQUAL "${CMAKE_${lang}_COMPILER}") + cmake_path(GET _xcrun_out PARENT_PATH _xcrun_toolchain_path) + cmake_path(GET CMAKE_${lang}_COMPILER PARENT_PATH _compiler_parent_path) + if ("${_xcrun_toolchain_path}" STREQUAL "${_compiler_parent_path}") set(${output_variable} "") endif() unset(_xcrun_out) + unset(_xcrun_toolchain_path) + unset(_compiler_parent_path) endif() endmacro() @@ -311,26 +332,37 @@ endmacro() macro(append_compiler_executables_configuration) set(_conan_c_compiler "") set(_conan_cpp_compiler "") + set(_conan_rc_compiler "") + set(_conan_compilers_list "") if(CMAKE_C_COMPILER) - set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\",") + set(_conan_c_compiler "\"c\":\"${CMAKE_C_COMPILER}\"") set_conan_compiler_if_appleclang(C cc _conan_c_compiler) + list(APPEND _conan_compilers_list ${_conan_c_compiler}) else() message(WARNING "CMake-Conan: The C compiler is not defined. " - "Please define CMAKE_C_COMPILER or enable the C language.") + "Please define CMAKE_C_COMPILER or enable the C language.") endif() if(CMAKE_CXX_COMPILER) set(_conan_cpp_compiler "\"cpp\":\"${CMAKE_CXX_COMPILER}\"") set_conan_compiler_if_appleclang(CXX c++ _conan_cpp_compiler) + list(APPEND _conan_compilers_list ${_conan_cpp_compiler}) else() message(WARNING "CMake-Conan: The C++ compiler is not defined. " - "Please define CMAKE_CXX_COMPILER or enable the C++ language.") + "Please define CMAKE_CXX_COMPILER or enable the C++ language.") endif() - - if(NOT "x${_conan_c_compiler}${_conan_cpp_compiler}" STREQUAL "x") - string(APPEND PROFILE "tools.build:compiler_executables={${_conan_c_compiler}${_conan_cpp_compiler}}\n") + if(CMAKE_RC_COMPILER) + set(_conan_rc_compiler "\"rc\":\"${CMAKE_RC_COMPILER}\"") + list(APPEND _conan_compilers_list ${_conan_rc_compiler}) + # Not necessary to warn if RC not defined + endif() + if(NOT "x${_conan_compilers_list}" STREQUAL "x") + string(REPLACE ";" "," _conan_compilers_list "${_conan_compilers_list}") + string(APPEND PROFILE "tools.build:compiler_executables={${_conan_compilers_list}}\n") endif() unset(_conan_c_compiler) unset(_conan_cpp_compiler) + unset(_conan_rc_compiler) + unset(_conan_compilers_list) endmacro() @@ -409,12 +441,12 @@ endfunction() function(conan_profile_detect_default) message(STATUS "CMake-Conan: Checking if a default profile exists") execute_process(COMMAND ${CONAN_COMMAND} profile path default - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - ECHO_OUTPUT_VARIABLE - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + ECHO_OUTPUT_VARIABLE + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) if(NOT ${return_code} EQUAL "0") message(STATUS "CMake-Conan: The default profile doesn't exist, detecting it.") execute_process(COMMAND ${CONAN_COMMAND} profile detect @@ -432,32 +464,47 @@ function(conan_install) cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN}) set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan) # Invoke "conan install" with the provided arguments - set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER} ${CONAN_EXTRA_INSTALL_ARGS}) - message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") + set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER}) + message(STATUS "CMake-Conan: ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}") + + + # In case there was not a valid cmake executable in the PATH, we inject the + # same we used to invoke the provider to the PATH + if(DEFINED PATH_TO_CMAKE_BIN) + set(_OLD_PATH $ENV{PATH}) + set(ENV{PATH} "$ENV{PATH}:${PATH_TO_CMAKE_BIN}") + endif() + execute_process(COMMAND ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN} --format=json - RESULT_VARIABLE return_code - OUTPUT_VARIABLE conan_stdout - ERROR_VARIABLE conan_stderr - ECHO_ERROR_VARIABLE # show the text output regardless - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_stdout + ERROR_VARIABLE conan_stderr + ECHO_ERROR_VARIABLE # show the text output regardless + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + if(DEFINED PATH_TO_CMAKE_BIN) + set(ENV{PATH} "${_OLD_PATH}") + endif() + if(NOT "${return_code}" STREQUAL "0") message(FATAL_ERROR "Conan install failed='${return_code}'") - else() - # the files are generated in a folder that depends on the layout used, if - # one is specified, but we don't know a priori where this is. - # TODO: this can be made more robust if Conan can provide this in the json output - string(JSON CONAN_GENERATORS_FOLDER GET ${conan_stdout} graph nodes 0 generators_folder) - cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) - # message("conan stdout: ${conan_stdout}") - message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") - set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") - # reconfigure on conanfile changes - string(JSON CONANFILE GET ${conan_stdout} graph nodes 0 label) - message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") - set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") - # success - set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) endif() + + # the files are generated in a folder that depends on the layout used, if + # one is specified, but we don't know a priori where this is. + # TODO: this can be made more robust if Conan can provide this in the json output + string(JSON CONAN_GENERATORS_FOLDER GET "${conan_stdout}" graph nodes 0 generators_folder) + cmake_path(CONVERT ${CONAN_GENERATORS_FOLDER} TO_CMAKE_PATH_LIST CONAN_GENERATORS_FOLDER) + # message("conan stdout: ${conan_stdout}") + message(STATUS "CMake-Conan: CONAN_GENERATORS_FOLDER=${CONAN_GENERATORS_FOLDER}") + set_property(GLOBAL PROPERTY CONAN_GENERATORS_FOLDER "${CONAN_GENERATORS_FOLDER}") + # reconfigure on conanfile changes + string(JSON CONANFILE GET "${conan_stdout}" graph nodes 0 label) + message(STATUS "CMake-Conan: CONANFILE=${CMAKE_SOURCE_DIR}/${CONANFILE}") + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/${CONANFILE}") + # success + set_property(GLOBAL PROPERTY CONAN_INSTALL_SUCCESS TRUE) + endfunction() @@ -469,7 +516,7 @@ function(conan_get_version conan_command conan_current_version) OUTPUT_STRIP_TRAILING_WHITESPACE ) if(conan_result) - message(FATAL_ERROR "CMake-Conan: Error when trying to run Conan") + message(FATAL_ERROR "CMake-Conan: Error when trying to run '${conan_command} --version'") endif() string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) @@ -479,7 +526,7 @@ endfunction() function(conan_version_check) set(options ) - set(oneValueArgs MINIMUM CURRENT) + set(oneValueArgs MINIMUM CURRENT RESULT) set(multiValueArgs ) cmake_parse_arguments(CONAN_VERSION_CHECK "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -492,8 +539,97 @@ function(conan_version_check) endif() if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM) - message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") + if(CONAN_VERSION_CHECK_RESULT) + set(${CONAN_VERSION_CHECK_RESULT} FALSE PARENT_SCOPE) + endif() + if(CONAN_DOWNLOAD STREQUAL "if-missing") + message(STATUS "CMake-Conan: Found Conan but its version (${CONAN_VERSION_CHECK_CURRENT}) is older than " + "required (${CONAN_VERSION_CHECK_MINIMUM}). Will download the latest version instead.") + else() + message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later") + endif() + else() + if(CONAN_VERSION_CHECK_RESULT) + set(${CONAN_VERSION_CHECK_RESULT} TRUE PARENT_SCOPE) + endif() + endif() +endfunction() + + +function(get_latest_conan_version VERSION_VARIABLE) + set(json_file "${CMAKE_BINARY_DIR}/conan_latest_release.json") + foreach(_retry_counter RANGE 3) + file(DOWNLOAD "https://api.github.com/repos/conan-io/conan/releases/latest" + "${json_file}" + INACTIVITY_TIMEOUT 15 + STATUS status) + list(GET status 0 status_code) + if(NOT status_code EQUAL 0) + list(GET status 1 message) + message(WARNING "CMake-Conan: Failed to get the latest Conan version info: ${message} (${status_code})") + continue() + endif() + break() + endforeach() + file(READ "${json_file}" json ENCODING UTF-8) + string(REGEX MATCH "\"tag_name\": \"([^\"]+)\"" _ "${json}") + if(NOT _) + message(FATAL_ERROR "CMake-Conan: Failed to parse the latest Conan version info from: '${json_file}'") + endif() + set(${VERSION_VARIABLE} "${CMAKE_MATCH_1}" PARENT_SCOPE) +endfunction() + + +function(download_conan) + set(options "") + set(oneValueArgs VERSION DESTINATION) + set(multiValueArgs "") + include(CMakeParseArguments) + cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${oneValueArgs}" "${multiValueArgs}") + + if(NOT ARG_VERSION) + message(FATAL_ERROR "CMake-Conan: Required parameter VERSION not set!") + endif() + if(NOT ARG_DESTINATION) + message(FATAL_ERROR "CMake-Conan: Required parameter DESTINATION not set!") + endif() + + if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "AMD64|amd64|x86_64|x64") + set(HOST_ARCH "x86_64") + elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "X86|i686|i386") + set(HOST_ARCH "i686") + elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") + set(HOST_ARCH "arm64") + else() + message(FATAL_ERROR "CMake-Conan: Pre-packaged Conan is not available for ${CMAKE_HOST_SYSTEM_PROCESSOR}") + endif() + + set(FILE_EXT "tgz") + if(WIN32 AND HOST_ARCH MATCHES "x86_64|i686") + set(HOST_OS "windows") + set(FILE_EXT "zip") + elseif(APPLE AND HOST_ARCH MATCHES "x86_64|arm64") + set(HOST_OS "macos") + elseif(LINUX AND HOST_ARCH STREQUAL "x86_64") + set(HOST_OS "linux") + else() + message(FATAL_ERROR "CMake-Conan: Pre-packaged Conan is not available for ${CMAKE_SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() + + set(CONAN_VERSION ${ARG_VERSION}) + set(CONAN_FILE "conan-${CONAN_VERSION}-${HOST_OS}-${HOST_ARCH}.${FILE_EXT}") + set(CONAN_URL "https://github.com/conan-io/conan/releases/download/${CONAN_VERSION}/${CONAN_FILE}") + + message(STATUS "CMake-Conan: Downloading Conan ${CONAN_VERSION} from ${CONAN_URL}") + include(FetchContent) + FetchContent_Declare( + Conan + URL "${CONAN_URL}" + DOWNLOAD_DIR ${CMAKE_BINARY_DIR} + SOURCE_DIR "${ARG_DESTINATION}" + DOWNLOAD_EXTRACT_TIMESTAMP 1 + ) + FetchContent_MakeAvailable(Conan) endfunction() @@ -519,9 +655,37 @@ macro(conan_provide_dependency method package_name) set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE) get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS) if(NOT _conan_install_success) - find_program(CONAN_COMMAND "conan" REQUIRED) - conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) - conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}) + if(NOT CONAN_DOWNLOAD STREQUAL "always") + find_program(CONAN_COMMAND "conan" QUIET) + if(CONAN_COMMAND) + conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION) + conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION} + RESULT _conan_version_check_result) + if(NOT _conan_version_check_result) + set(CONAN_COMMAND "-NOTFOUND") + endif() + endif() + endif() + if(NOT CONAN_COMMAND) + if(CONAN_DOWNLOAD STREQUAL "never") + message(FATAL_ERROR "CMake-Conan: Conan executable not found. " + "Please install Conan, set CONAN_COMMAND or enable CONAN_DOWNLOAD") + endif() + if(CONAN_DOWNLOAD_VERSION STREQUAL "latest") + get_latest_conan_version(_download_version) + else() + set(_download_version ${CONAN_DOWNLOAD_VERSION}) + endif() + download_conan(VERSION ${_download_version} DESTINATION "${CMAKE_BINARY_DIR}/conan_client") + set(CONAN_COMMAND "${CMAKE_BINARY_DIR}/conan_client/conan") + set(_conan_downloaded TRUE) + endif() + + if(CONAN_ISOLATE_HOME STREQUAL "always" OR (CONAN_ISOLATE_HOME STREQUAL "if-downloaded" AND _conan_downloaded)) + message(STATUS "CMake-Conan: Setting CONAN_HOME to '${CMAKE_BINARY_DIR}/conan_home'") + set(ENV{CONAN_HOME} "${CMAKE_BINARY_DIR}/conan_home") + endif() + message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan") if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE) conan_profile_detect_default() @@ -541,18 +705,18 @@ macro(conan_provide_dependency method package_name) file(READ "${CMAKE_SOURCE_DIR}/conanfile.txt" outfile) if(NOT "${outfile}" MATCHES ".*CMakeDeps.*") message(WARNING "Cmake-conan: CMakeDeps generator was not defined in the conanfile. " - "Please define the generator as it will be mandatory in the future") + "Please define the generator as it will be mandatory in the future") endif() set(generator "-g;CMakeDeps") endif() get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(NOT _multiconfig_generator) message(STATUS "CMake-Conan: Installing single configuration ${CMAKE_BUILD_TYPE}") - conan_install(${_host_profile_flags} ${_build_profile_flags} --build=missing ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} ${CONAN_INSTALL_ARGS} ${generator}) else() message(STATUS "CMake-Conan: Installing both Debug and Release") - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release --build=missing ${generator}) - conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug --build=missing ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Release ${CONAN_INSTALL_ARGS} ${generator}) + conan_install(${_host_profile_flags} ${_build_profile_flags} -s build_type=Debug ${CONAN_INSTALL_ARGS} ${generator}) endif() unset(_host_profile_flags) unset(_build_profile_flags) @@ -606,8 +770,8 @@ macro(conan_provide_dependency_check) get_property(_CONAN_PROVIDE_DEPENDENCY_INVOKED GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED) if(NOT _CONAN_PROVIDE_DEPENDENCY_INVOKED) message(WARNING "Conan is correctly configured as dependency provider, " - "but Conan has not been invoked. Please add at least one " - "call to `find_package()`.") + "but Conan has not been invoked. Please add at least one " + "call to `find_package()`.") if(DEFINED CONAN_COMMAND) # supress warning in case `CONAN_COMMAND` was specified but unused. set(_CONAN_COMMAND ${CONAN_COMMAND}) @@ -622,6 +786,10 @@ endmacro() # to check if the dependency provider was invoked at all. cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check) -# Configurable variables for Conan profiles -set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile") -set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile") +find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH) +if(NOT _cmake_program) + get_filename_component(PATH_TO_CMAKE_BIN "${CMAKE_COMMAND}" DIRECTORY) + set(PATH_TO_CMAKE_BIN "${PATH_TO_CMAKE_BIN}" CACHE INTERNAL "Path where the CMake executable is") +endif() + +cmake_policy(POP) diff --git a/cmake/third_party.cmake b/cmake/third_party.cmake index d0bc8fc..0bae768 100644 --- a/cmake/third_party.cmake +++ b/cmake/third_party.cmake @@ -26,9 +26,10 @@ cmake_dependent_option(INSTALL_THIRD_PARTY "Install third-party dependencies alo if(USE_CONAN) if(CMAKE_VERSION GREATER_EQUAL 3.24) - set(CONAN_EXTRA_INSTALL_ARGS + set(CONAN_INSTALL_ARGS + --build=missing # Deploy the installed dependencies in the build dir for easier installation - --deployer=full_deploy --deployer-folder=${CMAKE_BINARY_DIR} + --deployer=full_deploy "--deployer-folder=${CMAKE_BINARY_DIR}" ) list(APPEND CMAKE_PROJECT_TOP_LEVEL_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/conan_provider.cmake) else()