diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f935948..94d247c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: cmake: ${{ matrix.cmake }} ninja: true vcpkg: true - conan: true + conan: 2.1.0 cppcheck: true clangtidy: true task: true diff --git a/README.md b/README.md index f555dfa0..b705160f 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ CMake experience following the best practices. FetchContent, vcpkg, etc. - `run_vcpkg`: automatic installation of vcpkg and the project dependencies -- `ENABLE_CONAN` in `project_options`: automatic installation of Conan - and the project dependencies +- `run_conan`: automatic installation of conan and the project + dependencies - `dynamic_project_options`: a wrapper around `project_options` to change the options on the fly dynamically - `target_link_system_libraries` and @@ -79,6 +79,8 @@ run_vcpkg( VCPKG_URL "https://github.com/microsoft/vcpkg.git" VCPKG_REV "10e052511428d6b0c7fcc63a139e8024bb146032" ) +# Install conan dependencies: - should be called before defining project() +run_conan() # Set the project name and language project(myproject LANGUAGES CXX C) @@ -119,7 +121,6 @@ project_options( ${ENABLE_CPPCHECK} ${ENABLE_CLANG_TIDY} ENABLE_VS_ANALYSIS - # ENABLE_CONAN # ENABLE_INTERPROCEDURAL_OPTIMIZATION # ENABLE_NATIVE_OPTIMIZATION ${ENABLE_DOXYGEN} @@ -142,7 +143,6 @@ project_options( # ENABLE_BUILD_WITH_TIME_TRACE # ENABLE_UNITY # LINKER "lld" - # CONAN_PROFILE ${profile_path} ) ``` diff --git a/docs/src/Readme_top.md b/docs/src/Readme_top.md index 7c32b683..ab12a277 100644 --- a/docs/src/Readme_top.md +++ b/docs/src/Readme_top.md @@ -22,7 +22,7 @@ A general-purpose CMake library that provides functions that improve the CMake e - using custom linkers (e.g. lld) - `package_project`: automatic packaging/installation of the project for seamless usage via find_package/target_link through CMake's FetchContent, vcpkg, etc. - `run_vcpkg`: automatic installation of vcpkg and the project dependencies -- `ENABLE_CONAN` in `project_options`: automatic installation of Conan and the project dependencies +- `run_conan`: automatic installation of conan and the project dependencies - `dynamic_project_options`: a wrapper around `project_options` to change the options on the fly dynamically - `target_link_system_libraries` and `target_include_system_directories`: linking/including external dependencies/headers without warnings - `target_link_cuda`: linking Cuda to a target diff --git a/docs/src/project_options_example.md b/docs/src/project_options_example.md index d52ee2f6..504575d7 100644 --- a/docs/src/project_options_example.md +++ b/docs/src/project_options_example.md @@ -33,6 +33,8 @@ run_vcpkg( VCPKG_REV "10e052511428d6b0c7fcc63a139e8024bb146032" ENABLE_VCPKG_UPDATE ) +# Install conan dependencies: - should be called before defining project() +run_conan() # Set the project name and language project(myproject LANGUAGES CXX C) @@ -73,7 +75,6 @@ project_options( ${ENABLE_CPPCHECK} ${ENABLE_CLANG_TIDY} ENABLE_VS_ANALYSIS - # ENABLE_CONAN # ENABLE_INTERPROCEDURAL_OPTIMIZATION # ENABLE_NATIVE_OPTIMIZATION ${ENABLE_DOXYGEN} @@ -96,7 +97,6 @@ project_options( # ENABLE_BUILD_WITH_TIME_TRACE # ENABLE_UNITY # LINKER "lld" - # CONAN_PROFILE ${profile_path} ) ``` diff --git a/src/Conan.cmake b/src/Conan.cmake index ac41ac17..b9293a69 100644 --- a/src/Conan.cmake +++ b/src/Conan.cmake @@ -1,7 +1,45 @@ include_guard() -# Run Conan for dependency management -macro(run_conan) +function(conan_get_version conan_current_version) + find_program(conan_command "conan" REQUIRED) + execute_process( + COMMAND ${conan_command} --version + OUTPUT_VARIABLE conan_output + RESULT_VARIABLE conan_result + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(conan_result) + message(FATAL_ERROR "Error when trying to run Conan") + endif() + + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" conan_version ${conan_output}) + set(${conan_current_version} ${conan_version} PARENT_SCOPE) +endfunction() + +# Run Conan 1 for dependency management +macro(_run_conan1) + set(options + DEPRECATED_CALL # For backward compability + ) + set(one_value_args + DEPRECATED_PROFILE # For backward compability + ) + set(multi_value_args + HOST_PROFILE + BUILD_PROFILE + INSTALL_ARGS + DEPRECATED_OPTIONS # For backward compability + ) + cmake_parse_arguments(_args "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + conan_get_version(_conan_current_version) + if(_conan_current_version VERSION_GREATER_EQUAL "2.0.0") + message(FATAL_ERROR + "ENABLE_CONAN in `project_options(...)` only supports conan 1.\n" + " If you're using conan 2, disable ENABLE_CONAN and use `run_conan(...)` before `project(...)`.") + endif() + # Download automatically, you can also just copy the conan.cmake file if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") @@ -61,35 +99,59 @@ macro(run_conan) set(OUTPUT_QUIET) endif() + set(_should_detect FALSE) + if(((NOT _args_DEPRECATED_CALL) AND ((NOT _args_HOST_PROFILE) OR ("auto-cmake" IN_LIST _args_HOST_PROFILE))) + OR ((_args_DEPRECATED_CALL) AND (NOT _args_DEPRECATED_PROFILE))) + set(_should_detect TRUE) + list(REMOVE_ITEM _args_HOST_PROFILE "auto-cmake") + endif() + + if(NOT _args_DEPRECATED_PROFILE) + set(CONAN_DEFAULT_PROFILE "default") + else() + set(CONAN_DEFAULT_PROFILE ${_args_DEPRECATED_PROFILE}) + endif() + + if(NOT _args_HOST_PROFILE) + set(CONAN_HOST_PROFILE ${CONAN_DEFAULT_PROFILE}) + else() + set(CONAN_HOST_PROFILE ${_args_HOST_PROFILE}) + endif() + + if(NOT _args_BUILD_PROFILE) + set(CONAN_BUILD_PROFILE ${CONAN_DEFAULT_PROFILE}) + else() + set(CONAN_BUILD_PROFILE ${_args_BUILD_PROFILE}) + endif() + + foreach(_install_args IN LISTS _args_INSTALL_ARGS) + string(REGEX MATCH "--build=.*" _possible_build_arg "${_install_args}") + + if(_possible_build_arg) + string(SUBSTRING "${_possible_build_arg}" 8 -1 CONAN_BUILD_ARG) + endif() + endforeach() + if(NOT CONAN_BUILD_ARG) + set(CONAN_BUILD_ARG "missing") + set(CONAN_INSTALL_ARGS "") + else() + list(REMOVE_ITEM _args_INSTALL_ARGS "--build=${CONAN_BUILD_ARG}") + set(CONAN_INSTALL_ARGS ${_args_INSTALL_ARGS}) + endif() + foreach(TYPE ${LIST_OF_BUILD_TYPES}) message(STATUS "Running Conan for build type '${TYPE}'") - if("${ProjectOptions_CONAN_PROFILE}" STREQUAL "") + if(_should_detect) # Detects current build settings to pass into conan conan_cmake_autodetect(settings BUILD_TYPE ${TYPE}) set(CONAN_SETTINGS SETTINGS ${settings}) set(CONAN_ENV ENV "CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}") - else() + elseif(_args_DEPRECATED_CALL) # Derive all conan settings from a conan profile - set(CONAN_SETTINGS PROFILE ${ProjectOptions_CONAN_PROFILE} SETTINGS "build_type=${TYPE}") - # CONAN_ENV should be redundant, since the profile can set CC & CXX - endif() + set(CONAN_SETTINGS PROFILE ${CONAN_DEFAULT_PROFILE} SETTINGS "build_type=${TYPE}") - if("${ProjectOptions_CONAN_PROFILE}" STREQUAL "") - set(CONAN_DEFAULT_PROFILE "default") - else() - set(CONAN_DEFAULT_PROFILE ${ProjectOptions_CONAN_PROFILE}) - endif() - if("${ProjectOptions_CONAN_BUILD_PROFILE}" STREQUAL "") - set(CONAN_BUILD_PROFILE ${CONAN_DEFAULT_PROFILE}) - else() - set(CONAN_BUILD_PROFILE ${ProjectOptions_CONAN_BUILD_PROFILE}) - endif() - - if("${ProjectOptions_CONAN_HOST_PROFILE}" STREQUAL "") - set(CONAN_HOST_PROFILE ${CONAN_DEFAULT_PROFILE}) - else() - set(CONAN_HOST_PROFILE ${ProjectOptions_CONAN_HOST_PROFILE}) + # CONAN_ENV should be redundant, since the profile can set CC & CXX endif() # PATH_OR_REFERENCE ${CMAKE_SOURCE_DIR} is used to tell conan to process @@ -99,10 +161,10 @@ macro(run_conan) PATH_OR_REFERENCE ${CMAKE_SOURCE_DIR} BUILD - missing + ${CONAN_BUILD_ARG} # Pass compile-time configured options into conan OPTIONS - ${ProjectOptions_CONAN_OPTIONS} + ${CONAN_INSTALL_ARGS} # Pass CMake compilers to Conan ${CONAN_ENV} PROFILE_HOST @@ -117,3 +179,123 @@ macro(run_conan) endif() endmacro() + +# Run Conan 2 for dependency management +macro(_run_conan2) + set(options) + set(one_value_args) + set(multi_value_args + HOST_PROFILE + BUILD_PROFILE + INSTALL_ARGS + ) + cmake_parse_arguments(_args "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + if(CMAKE_VERSION VERSION_LESS "3.24.0") + message(FATAL_ERROR + "`run_conan(...)` with conan 2 only supports cmake 3.24+, please update your cmake.\n" + " Or you can downgrade your conan to use conan 1.") + endif() + + conan_get_version(_conan_current_version) + if(_conan_current_version VERSION_LESS "2.0.5") + message(FATAL_ERROR + "`run_conan(...)` with conan 2 only supports conan 2.0.5+, please update your conan.\n" + " Or You can downgrade your conan to use conan 1.") + endif() + + # Download automatically, you can also just copy the conan.cmake file + if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_provider.cmake") + message(STATUS "Downloading conan_provider.cmake from https://github.com/conan-io/cmake-conan") + file( + DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/f6464d1e13ef7a47c569f5061f9607ea63339d39/conan_provider.cmake" + "${CMAKE_BINARY_DIR}/conan_provider.cmake" + EXPECTED_HASH SHA256=0a5eb4afbdd94faf06dcbf82d3244331605ef2176de32c09ea9376e768cbb0fc + + # TLS_VERIFY ON # fails on some systems + ) + endif() + + if(NOT _args_HOST_PROFILE) + set(_args_HOST_PROFILE "default;auto-cmake") + endif() + + if(NOT _args_BUILD_PROFILE) + set(_args_BUILD_PROFILE "default") + endif() + + if(NOT _args_INSTALL_ARGS) + set(_args_INSTALL_ARGS "--build=missing") + endif() + + set(CONAN_HOST_PROFILE "${_args_HOST_PROFILE}" CACHE STRING "Conan host profile" FORCE) + set(CONAN_BUILD_PROFILE "${_args_BUILD_PROFILE}" CACHE STRING "Conan build profile" FORCE) + set(CONAN_INSTALL_ARGS "${_args_INSTALL_ARGS}" CACHE STRING "Command line arguments for conan install" FORCE) + + # A workaround from https://github.com/conan-io/cmake-conan/issues/595 + list(APPEND CMAKE_PROJECT_TOP_LEVEL_INCLUDES "${CMAKE_BINARY_DIR}/conan_provider.cmake") + + # Add this to invoke conan even when there's no find_package in CMakeLists.txt. + # This helps users get the third-party package names, which is used in later find_package. + cmake_language(DEFER DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" CALL find_package Git QUIET) +endmacro() + +#[[.rst: + +``run_conan`` +============= + +Install conan and conan dependencies: + +.. code:: cmake + + run_conan() + +.. code:: cmake + + run_conan( + HOST_PROFILE default auto-cmake + BUILD_PROFILE default + INSTALL_ARGS --build=missing + ) + +Note that it should be called before defining ``project()``. + +Named String: + +- Values are semicolon separated, e.g. ``"--build=never;--update;--lockfile-out=''"``. + However, you can make use of the cmake behaviour that automatically concatenates + multiple space separated string into a semicolon seperated list, e.g. + ``--build=never --update --lockfile-out=''``. + +- ``HOST_PROFILE``: (Defaults to ``"default;auto-cmake"``). This option + sets the host profile used by conan. When ``auto-cmake`` is specified, + cmake-conan will invoke conan's autodetection mechanism which tries to + guess the system defaults. If multiple profiles are specified, a + `compound profile `_ + will be used - compounded from left to right, where right has the highest priority. + +- ``BUILD_PROFILE``: (Defaults to ``"default"``). This option + sets the build profile used by conan. If multiple profiles are specified, + a `compound profile `_ + will be used - compounded from left to right, where right has the highest priority. + +- ``INSTALL_ARGS``: (Defaults to ``"--build=missing"``). This option + customizes ``conan install`` command invocation. Note that ``--build`` + must be specified, otherwise conan will revert to its default behaviour. + + - Two arguments are reserved to the dependency provider implementation + and must not be set: the path to a ``conanfile.txt|.py``, and the output + format (``--format``). + +]] +macro(run_conan) + conan_get_version(_conan_current_version) + + if(_conan_current_version VERSION_LESS "2.0.0") + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY PROJECT_OPTIONS_SHOULD_INVOKE_CONAN1 TRUE) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY PROJECT_OPTIONS_CONAN1_ARGS ${ARGN}) + else() + _run_conan2(${ARGN}) + endif() +endmacro() \ No newline at end of file diff --git a/src/DynamicProjectOptions.cmake b/src/DynamicProjectOptions.cmake index 3e158e53..569db142 100644 --- a/src/DynamicProjectOptions.cmake +++ b/src/DynamicProjectOptions.cmake @@ -64,6 +64,8 @@ Here is an example of how to use ``dynamic_project_options``: # install vcpkg dependencies: - should be called before defining project() # run_vcpkg() + # install conan dependencies: - should be called before defining project() + # run_conan() # Set the project name and language project(myproject LANGUAGES CXX C) @@ -71,9 +73,6 @@ Here is an example of how to use ``dynamic_project_options``: # Set PCH to be on by default for all non-Developer Mode Builds set(ENABLE_PCH_USER_DEFAULT ON) - # enable Conan - set(ENABLE_CONAN_DEFAULT ON) - # Initialize project_options variable related to this project # This overwrites `project_options` and sets `project_warnings` # This also accepts the same arguments as `project_options`. diff --git a/src/Index.cmake b/src/Index.cmake index 3e90cdc9..1a8b890a 100644 --- a/src/Index.cmake +++ b/src/Index.cmake @@ -59,7 +59,6 @@ include("${ProjectOptions_SRC_DIR}/Vcpkg.cmake") - ``ENABLE_CLANG_TIDY``: Enable static analysis with clang-tidy - ``ENABLE_VS_ANALYSIS``: Enable Visual Studio IDE code analysis if the generator is Visual Studio. -- ``ENABLE_CONAN``: Use Conan for dependency management - ``ENABLE_INTERPROCEDURAL_OPTIMIZATION``: Enable Interprocedural Optimization (Link Time Optimization, LTO) in the release build - ``ENABLE_NATIVE_OPTIMIZATION``: Enable the optimizations specific to @@ -114,8 +113,6 @@ front of them: cppcheck - ``VS_ANALYSIS_RULESET``: Override the defaults for the code analysis rule set in Visual Studio. -- ``CONAN_OPTIONS``: Extra Conan options -- ``CONAN_PROFILE``: Passes a profile to conan: see https://docs.conan.io/en/latest/reference/profiles.html ]] @@ -308,7 +305,19 @@ macro(project_options) endif() if(${ProjectOptions_ENABLE_CONAN}) - run_conan() + _run_conan1( + DEPRECATED_CALL + DEPRECATED_PROFILE ${ProjectOptions_CONAN_PROFILE} + HOST_PROFILE ${ProjectOptions_CONAN_HOST_PROFILE} + BUILD_PROFILE ${ProjectOptions_CONAN_BUILD_PROFILE} + DEPRECATED_OPTIONS ${ProjectOptions_CONAN_OPTIONS} + ) + endif() + + get_property(_should_invoke_conan1 DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY PROJECT_OPTIONS_SHOULD_INVOKE_CONAN1) + if(_should_invoke_conan1) + get_property(conan1_args DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY PROJECT_OPTIONS_CONAN1_ARGS) + _run_conan1(${conan1_args}) endif() if(${ProjectOptions_ENABLE_UNITY}) diff --git a/tests/install/CMakeLists.txt b/tests/install/CMakeLists.txt index a1d5667f..e4a78b9d 100644 --- a/tests/install/CMakeLists.txt +++ b/tests/install/CMakeLists.txt @@ -4,13 +4,13 @@ cmake_minimum_required(VERSION 3.16...3.21) include(../../src/Index.cmake) run_vcpkg() +run_conan() project(anotherproj VERSION 0.1.0 LANGUAGES CXX C) # Initialize project_options project_options( ENABLE_CACHE - ENABLE_CONAN # WARNINGS_AS_ERRORS ENABLE_CPPCHECK ENABLE_CLANG_TIDY diff --git a/tests/install/conanfile.txt b/tests/install/conanfile.txt index 40a24b53..1a3bf031 100644 --- a/tests/install/conanfile.txt +++ b/tests/install/conanfile.txt @@ -4,4 +4,4 @@ docopt.cpp/0.6.3 [generators] -cmake_find_package_multi +CMakeDeps diff --git a/tests/myproj/CMakeLists.txt b/tests/myproj/CMakeLists.txt index 91db79dc..f2509775 100644 --- a/tests/myproj/CMakeLists.txt +++ b/tests/myproj/CMakeLists.txt @@ -20,6 +20,7 @@ endif() run_vcpkg(VCPKG_URL "https://github.com/microsoft/vcpkg.git" VCPKG_REV "10e052511428d6b0c7fcc63a139e8024bb146032" ENABLE_VCPKG_UPDATE ) +run_conan() project(myproj VERSION 0.2.0 LANGUAGES CXX C) @@ -54,7 +55,6 @@ project_options( PREFIX "myproj" ENABLE_CACHE - ENABLE_CONAN # WARNINGS_AS_ERRORS ENABLE_CPPCHECK ENABLE_CLANG_TIDY diff --git a/tests/myproj/conanfile.txt b/tests/myproj/conanfile.txt index 40a24b53..1a3bf031 100644 --- a/tests/myproj/conanfile.txt +++ b/tests/myproj/conanfile.txt @@ -4,4 +4,4 @@ docopt.cpp/0.6.3 [generators] -cmake_find_package_multi +CMakeDeps