conan-io / cmake-conan

CMake wrapper for conan C and C++ package manager
MIT License
831 stars 252 forks source link

conan_provider.cmake and conan options? #659

Open drmacdon opened 4 months ago

drmacdon commented 4 months ago

The CMake conan_provider.cmake approach as pointed out in #635 does not invoke conan until the first find_package(), but CMake processes it just after the project() line. I was not clear on why conan was deferred until the first find_package()?

Today, when conan is invoked it includes the option setting in the conan_toolchain.cmake but those option settings are unavailable to the conan_provider.cmake path and the point at which find_package() is actually invoked typically means CMake options have already been initialized.

To avoid replicating conan options when using conan_provider.cmake, I ended up doing something like the below which shells out to conan graph info to get the current values set prior the regular CMake option setting.

But if conan was resolvable at the point that conan_provider.cmake was evaluated it could do something similar with far less dependencies and be more seamless enabling the CMake to remain entirely agnostic of conan.

cmake_minimum_required(VERSION 3.24 FATAL_ERROR)

execute_process(
    COMMAND just cmake-options ${CMAKE_BINARY_DIR}/conan-options.cmake
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

project(testapp VERSION 1.0 LANGUAGES CXX C)

if (DEFINED CMAKE_PROJECT_TOP_LEVEL_INCLUDES AND EXISTS ${CMAKE_BINARY_DIR}/conan-options.cmake)
  include(${CMAKE_BINARY_DIR}/conan-options.cmake)
endif()

set(XYZ_OPTION None CACHE STRING "The XYZ OPTION")

Where the just cmake-options writes out the options via graph,

set unstable    # requires script from Just v1.32.0 to run nushell scripts

[script("nu"), private]
cmake-options OUTPUT_FILE="-":
    let options = (conan graph info "{{justfile_directory()}}" --format=json -vquiet | from json | get graph | get nodes | get "0" | get options | sort | transpose name value | 
        each { |it| 
            match $it.name {
                "fPIC" => $"set\(CMAKE_POSITION_INDEPENDENT_CODE ($it.value)\)"
                "shared" => $"set\(BUILD_SHARED_LIBS ($it.value)\)"
                _ => $"set\(($it.name | str upcase) \"($it.value)\"\)"
            }
        })
    if ("{{OUTPUT_FILE}}" == '-') {
      $options
    } else {
      "message(WARNING \"Loading cmake-options\")\n" | save -f "{{OUTPUT_FILE}}"
      $options | save -a "{{OUTPUT_FILE}}"
    }
memsharded commented 3 months ago

Hi @drmacdon

Thanks for your feedback

The CMake conan_provider.cmake approach as pointed out in https://github.com/conan-io/cmake-conan/issues/635 does not invoke conan until the first find_package(), but CMake processes it just after the project() line. I was not clear on why conan was deferred until the first find_package()?

This is how the CMake dependency providers work: https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html#dependency-providers, CMake designed them as interceptors of find_package() calls. Conan just followed their design.

But if conan was resolvable at the point that conan_provider.cmake was evaluated it could do something similar with far less dependencies and be more seamless enabling the CMake to remain entirely agnostic of conan.

I partially understand the desire to be able to keep 1 single command like cmake ... and have everything integrated from there. But at some point, if the added complexity to do this is very high, it is simply not worth it. Other programming languages have had flows such as npm install to install dependencies, then gulp build to do the actual build with a different tools for long time.

Hiding the package and dependency resolution is not always a good thing, from our experience. It often just "delays" the issues, overloading the "build" engineers everytime something doesn't work because the "build is broken" without understanding if it is a dependency new version, if it is a compiler error somewhere, or what.

The aim of "remain agnostic of conan" recalls me from devs using some abstractions over git when migrating from subversion (to not have to learn the complexity of git). Our experience is that we have seen in general better results in organizations that accept Conan as another new tool in the developers flow and "toolbelt" (because it really is a new tool in the flow) than organizations trying to completely hide it as a build internal implementation detail.

I understand that this is not always possible, and this is why we provide this cmake-conan integration, with a special focus on being able to provide a transparent integration without modifying the CMakeLists.txt, and following the recommended practices of the CMake dependency providers. But this doesn't allow to provide a multi-stage bidirectional flow of information between Conan and CMake, so cases like what you are trying to implement will be quite challenging.