Skip to content

Instantly share code, notes, and snippets.

@DavidAce
Last active March 30, 2023 07:27
Show Gist options
  • Save DavidAce/cbb5d2709bacf47bc9777377210309dc to your computer and use it in GitHub Desktop.
Save DavidAce/cbb5d2709bacf47bc9777377210309dc to your computer and use it in GitHub Desktop.
cmake-conan dependency provider (for conan v1)

CMake-Conan integration for conan v1.

Inspired by the dependency-provider mechanism for conan v2

This uses the new CMake dependency provider feature to let conan resolve dependencies when you call find_packkage(<lib>) from CMakeLists.txt in your own project.

Requires

  • conan>=1.59
  • cmake>=3.24

Usage

Use the flag CMAKE_PROJECT_TOP_LEVEL_INCLUDES to point CMake to the provider file:

cmake -B build -S . -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=[path-to-cmake-conan]/conan_provider.cmake -DCMAKE_BUILD_TYPE=Release

CMakePresets.json

You can make a hidden preset to use conan in other presets.

    {
      "name": "conan",
      "hidden": true,
      "cacheVariables": {
        "CMAKE_PREFIX_PATH": "${sourceDir}/build/${presetName}/conan",
        "CMAKE_PROJECT_TOP_LEVEL_INCLUDES": "${sourceDir}/cmake/conan/conan_provider.cmake",
        "CMAKE_FIND_PACKAGE_PREFER_CONFIG": "TRUE"
     },
      "environment": {
        "PATH": "$env{HOME}/miniconda3/envs/dmrg/bin:$env{HOME}/miniconda3/bin:$penv{PATH}"
      }
    },

This assumes the provider file is found at ./cmake/conan/conan_provider.cmake relative to your CMakePresets.json.

The flag CMAKE_FIND_PACKAGE_PREFER_CONFIG does what it says in the name: it lets find_pacakge search for CONFIG files first (i.e. the conan dependencies) and then fall back to MODULE mode when nothing is found.

include("${CMAKE_CURRENT_LIST_DIR}/conan_support.cmake")
cmake_language(
SET_DEPENDENCY_PROVIDER conan_provide_dependency
SUPPORTED_METHODS FIND_PACKAGE
)
function(detect_os OS)
# it could be cross compilation
message(DEBUG "cmake-conan: cmake_system_name=${CMAKE_SYSTEM_NAME}")
if (CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic")
if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
set(${OS} Macos PARENT_SCOPE)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "QNX")
set(${OS} Neutrino PARENT_SCOPE)
else ()
set(${OS} ${CMAKE_SYSTEM_NAME} PARENT_SCOPE)
endif ()
endif ()
endfunction()
function(detect_cxx_standard CXX_STANDARD)
set(${CXX_STANDARD} ${CMAKE_CXX_STANDARD} PARENT_SCOPE)
if (CMAKE_CXX_EXTENSIONS)
set(${CXX_STANDARD} "gnu${CMAKE_CXX_STANDARD}" PARENT_SCOPE)
endif ()
endfunction()
function(detect_compiler COMPILER COMPILER_VERSION)
if (DEFINED CMAKE_CXX_COMPILER_ID)
set(_COMPILER ${CMAKE_CXX_COMPILER_ID})
set(_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
else ()
if (NOT DEFINED CMAKE_C_COMPILER_ID)
message(FATAL_ERROR "C or C++ compiler not defined")
endif ()
set(_COMPILER ${CMAKE_C_COMPILER_ID})
set(_COMPILER_VERSION ${CMAKE_C_COMPILER_VERSION})
endif ()
message(DEBUG "cmake-conan: CMake compiler=${_COMPILER}")
message(DEBUG "cmake-conan: CMake compiler version=${_COMPILER_VERSION}")
if (_COMPILER MATCHES MSVC)
set(_COMPILER "msvc")
string(SUBSTRING ${MSVC_VERSION} 0 3 _COMPILER_VERSION)
elseif (_COMPILER MATCHES AppleClang)
set(_COMPILER "apple-clang")
string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION})
list(GET VERSION_LIST 0 _COMPILER_VERSION)
elseif (_COMPILER MATCHES Clang)
set(_COMPILER "clang")
string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION})
list(GET VERSION_LIST 0 _COMPILER_VERSION)
elseif (_COMPILER MATCHES GNU)
set(_COMPILER "gcc")
string(REPLACE "." ";" VERSION_LIST ${CMAKE_CXX_COMPILER_VERSION})
list(GET VERSION_LIST 0 _COMPILER_VERSION)
endif ()
message(DEBUG "cmake-conan: [settings] compiler=${_COMPILER}")
message(DEBUG "cmake-conan: [settings] compiler.version=${_COMPILER_VERSION}")
set(${COMPILER} ${_COMPILER} PARENT_SCOPE)
set(${COMPILER_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE)
endfunction()
function(detect_build_type BUILD_TYPE)
if (NOT CMAKE_CONFIGURATION_TYPES)
# Only set when we know we are in a single-configuration generator
# Note: we may want to fail early if `CMAKE_BUILD_TYPE` is not defined
set(${BUILD_TYPE} ${CMAKE_BUILD_TYPE} PARENT_SCOPE)
endif ()
endfunction()
function(detect_host_profile output_file)
detect_os(MYOS)
detect_compiler(MYCOMPILER MYCOMPILER_VERSION)
detect_cxx_standard(MYCXX_STANDARD)
detect_build_type(MYBUILD_TYPE)
set(PROFILE "")
string(APPEND PROFILE "include(default)\n")
string(APPEND PROFILE "[settings]\n")
if (MYOS)
string(APPEND PROFILE os=${MYOS} "\n")
endif ()
if (MYCOMPILER)
string(APPEND PROFILE compiler=${MYCOMPILER} "\n")
endif ()
if (MYCOMPILER_VERSION)
string(APPEND PROFILE compiler.version=${MYCOMPILER_VERSION} "\n")
endif ()
if (MYCXX_STANDARD)
string(APPEND PROFILE compiler.cppstd=${MYCXX_STANDARD} "\n")
endif ()
if (MYBUILD_TYPE)
string(APPEND PROFILE "build_type=${MYBUILD_TYPE}\n")
endif ()
if (NOT DEFINED output_file)
set(_FN "${CMAKE_BINARY_DIR}/conan/profile")
else ()
set(_FN ${output_file})
endif ()
string(APPEND PROFILE "[conf]\n")
string(APPEND PROFILE "tools.cmake.cmaketoolchain:generator=${CMAKE_GENERATOR}\n")
string(TOUPPER "${CMAKE_BUILD_TYPE}" CONAN_BUILD_TYPE)
set(CONAN_CXXFLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CONAN_BUILD_TYPE}}")
set(CONAN_CFLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CONAN_BUILD_TYPE}}")
set(CONAN_LDFLAGS "${CMAKE_EXE_LINKER_FLAGS}")
string(APPEND PROFILE "[buildenv]\n")
if(CONAN_CFLAGS)
string(APPEND PROFILE "CFLAGS+=${CONAN_CFLAGS}\n")
endif()
if(CONAN_CXXFLAGS)
string(APPEND PROFILE "CXXFLAGS+=${CONAN_CXXFLAGS}\n")
endif()
if(CONAN_LDFLAGS)
string(APPEND PROFILE "LDFLAGS+=${CONAN_LDFLAGS}\n")
endif()
if(CMAKE_C_COMPILER)
string(APPEND PROFILE "CC=${CMAKE_C_COMPILER}\n")
endif()
if(CMAKE_CXX_COMPILER)
string(APPEND PROFILE "CXX=${CMAKE_CXX_COMPILER}\n")
endif()
if(CMAKE_Fortran_COMPILER)
string(APPEND PROFILE "FC=${CMAKE_Fortran_COMPILER}\n")
endif()
message(STATUS "cmake-conan: Creating profile ${_FN}")
file(WRITE ${_FN} ${PROFILE})
message(DEBUG "cmake-conan: Profile: \n${PROFILE}")
endfunction()
function(conan_install)
cmake_parse_arguments(ARGS CONAN_ARGS ${ARGN})
if(DEFINED CONANFILE AND NOT DEFINED CONAN_PATH_OR_REFERENCE)
set(CONAN_PATH_OR_REFERENCE "${CONANFILE}")
elseif(NOT DEFINED CONAN_PATH_OR_REFERENCE)
set(CONAN_PATH_OR_REFERENCE "${CMAKE_SOURCE_DIR}")
endif()
if(NOT DEFINED CONAN_OUTPUT_FOLDER)
set(CONAN_OUTPUT_FOLDER "${CMAKE_BINARY_DIR}/conan")
endif()
# Invoke "conan install" with the provided arguments
list(APPEND CONAN_ARGS -of=${CONAN_OUTPUT_FOLDER})
message(STATUS "cmake-conan: conan install ${CONAN_PATH_OR_REFERENCE} ${CONAN_ARGS} ${CONAN_OPTIONS} ${ARGN}")
execute_process(COMMAND conan install "${CONAN_PATH_OR_REFERENCE}" ${CONAN_ARGS} ${CONAN_OPTIONS} ${ARGN}
RESULT_VARIABLE return_code
OUTPUT_VARIABLE conan_stdout
ERROR_VARIABLE conan_stderr
ECHO_OUTPUT_VARIABLE
ECHO_ERROR_VARIABLE # show the text output regardless
WORKING_DIRECTORY ${CONAN_OUTPUT_FOLDER})
if (NOT "${return_code}" STREQUAL "0")
message(FATAL_ERROR "Conan install failed='${return_code}'")
else ()
message(STATUS "cmake-conan: CONAN_OUTPUT_FOLDER=${CONAN_OUTPUT_FOLDER}")
message(STATUS "cmake-conan:${conan_stdout}")
set(CONAN_OUTPUT_FOLDER "${CONAN_OUTPUT_FOLDER}" PARENT_SCOPE)
set(CONAN_INSTALL_SUCCESS TRUE CACHE BOOL "Conan install has been invoked and was successful")
endif ()
endfunction()
function(conan_provide_dependency package_name)
if(NOT CONAN_INSTALL_SUCCESS OR NOT IS_DIRECTORY "${CMAKE_BINARY_DIR}/conan")
message(STATUS "cmake-conan: first find_package(${ARGV1}) found. Installing dependencies with Conan")
detect_host_profile(${CMAKE_BINARY_DIR}/conan/conan_host_profile)
if(NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "cmake-conan: Installing single configuration ${CMAKE_BUILD_TYPE}")
conan_install(-pr ${CMAKE_BINARY_DIR}/conan/conan_host_profile --build=missing --build=outdated --build=cascade -g CMakeDeps)
else()
message(STATUS "cmake-conan: Installing both Debug and Release")
conan_install(-pr ${CMAKE_BINARY_DIR}/conan/conan_host_profile -s build_type=Release --build=missing --build=outdated --build=cascade -g CMakeDeps)
conan_install(-pr ${CMAKE_BINARY_DIR}/conan/conan_host_profile -s build_type=Debug --build=missing --build=outdated --build=cascade -g CMakeDeps)
endif()
if (CONAN_INSTALL_SUCCESS)
set(CONAN_OUTPUT_FOLDER "${CONAN_OUTPUT_FOLDER}" CACHE PATH "Conan generators folder")
set(CONAN_GENERATORS_FOLDER "${CONAN_OUTPUT_FOLDER}" CACHE PATH "Conan output folder")
endif()
else()
message(DEBUG "cmake-conan: find_package(${ARGV1}) found, 'conan install' already ran")
endif()
if (NOT CONAN_OUTPUT_FOLDER IN_LIST CMAKE_PREFIX_PATH)
message(STATUS "Prepending conan output folder to CMAKE_PREFIX_PATH: ${CONAN_OUTPUT_FOLDER}")
list(PREPEND CMAKE_PREFIX_PATH "${CONAN_OUTPUT_FOLDER}")
list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} CACHE INTERNAL "")
endif()
find_package(${ARGN} BYPASS_PROVIDER)
endfunction()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment