conan-io / conan-center-index

Recipes for the ConanCenter repository
https://conan.io/center
MIT License
961 stars 1.77k forks source link

[question] Model Conan recipe using components #5003

Open klimkin opened 3 years ago

klimkin commented 3 years ago

Could you review this "model" example, please? Is there a cleaner option to achieve the same?

Package conanfile.py:

class MyLibraryConan(ConanFile):
    name = "my_library"
    ...

    requires = "boost/1.75.0", "gtest/1.10.0"
    generators = "cmake", "cmake_find_package"
    ...

    def package_id(self):
        self.info.shared_library_package_id()

    def package_info(self):
        self.cpp_info.name = "MyLibrary"
        self.cpp_info.components["_build"].requires = ["gtest::gtest"]
        self.cpp_info.components["libmy_library"].names["cmake_find_package"] = "my_library"
        self.cpp_info.components["libmy_library"].libs = ["my_library"]
        self.cpp_info.components["libmy_library"].requires = ["boost::filesystem"]

Package CMakeLists.txt:

cmake_minimum_required(VERSION 3.13)
project(my_library)
enable_testing()

include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(NO_OUTPUT_DIRS TARGETS)

list(PREPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})

find_package(Boost REQUIRED)
find_package(GTest REQUIRED)

add_library(my_library ${sources})
target_link_libraries(my_library PRIVATE Boost::filesystem)
set_target_properties(my_library PROPERTIES VERSION ${CONAN_PACKAGE_VERSION})

add_executable(my_library_test my_library_test.cpp)
target_link_libraries(my_library_test GTest::gmock GTest::gmock_main my_library)

test_package/CMakeLists.txt:

...
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(NO_OUTPUT_DIRS TARGETS)

find_package(MyLibrary REQUIRED COMPONENTS my_library)

add_executable(test_package test_package.cpp)
target_link_libraries(test_package PRIVATE MyLibrary::my_library)

Explanation of some decisions:

  1. Have to use components (Conan and CMake) to link only with Boost::filesystem. Linking with Boost::Boost breaks the build, since it picks all Boost components which are not necessary compatible with each other.

  2. Why to use cmake and cmake_find_package generators? a) conan_basic_setup() from cmake generator enables propagation of C++ standard and ABI flags, cmake_find_package does not. Also cmake propagates CONAN_PACKAGE_VERSION variable for use with shared libraries. b) cmake_find_package allows to use CMake's find_package() to import components in a way similar to standard CMake files.

  3. Why using NO_OUTPUT_DIRS? The option relocates target files that is not always desired. For example, when building SWIG modules, my preference is to keep shared libraries where generated Python wrappers are.

  4. Why using TARGETS? If not using TARGETS, conan_basic_setup() propagates includes to all upstream dependencies globally. In this example that means my_library target will have gtest in include paths. In most cases it's harmless, but still can hide missing dependencies in CMake files.

  5. Have to add an artificial _build Conan component. gtest is a "private" upstream dependency - shouldn't propagate to downstream dependencies. Unfortunately, using ("gtest/1.10.0", "private") doesn't work in this example, since it turns off generation of FindGTest.cmake file for cmake_find_package generator. If not using "private", conan create generates an error "Package require 'gtest' not used in components requires".

Pain points:

  1. Can cmake_find_package generator be self-sufficient? It should be setting up the C++ standard and ABI flags the same way cmake generator does. Same for CONAN_* variables.

  2. conan_basic_setup() seems to be doing too much. I would expect the empty list just setup a bare minimum (C++ standard, ABI, PIC flags) and additional arguments choose a build strategy (global variables - default, global properties - CMake's include_directories, TARGETS), and options (OUTPUT_DIRS, etc).

  3. Need a clean way to add "gtest" upstream dependencies without introducing an extra component. With extra component it's impossible to set downstream component requirement just to my_library::my_library, since it drags "gtest" with it. This wouldn't be a problem if ("gtest/1.10.0", "private") was generating FindGTest.cmake.

  4. For cmake* generators Conan passes some of CMake variables through cmake command-line. This makes conan build -c .. to be the only option to generate files for CMake. A better solution could be to make conan install generate separate files for each of the layers: shell, CMake toolchain, Conan CMake scripts. The separation would provide better tools for cases like running cmake without conan build -c or for IDE integration with basic CMake support. It would potentially simplify Conan generated files too.

    • conanenvironment.sh - environment variables (CXX, etc.) from the profile
    • conantoolchain.cmake - CMake variables that should be set before project(): C++ standard, ABI, libstdcpp, CMAKE_MODULE_PATH
    • conanbuildinfo.cmake - CONAN_* variables for use by conan_*_setup( and generated Find*.cmake files.
klimkin commented 3 years ago

@danimtb Could you comment on this, please?

danimtb commented 3 years ago

Hi @klimkin,

Thanks a lot for the clear explanation and the example provided above. IMO your example is correct and your decisions to cover your use case seem right to me.

Regarding the pain points, all I can say is that we are aware of those and already working to improve some parts.

So again, thanks for sharing this. I hope the above information helps to understand the current development in Conan. Some of the features above are still experimental but we are adding them to clear the path towards Conan 2.0 implementation. If you have any feedback about those please feel free to comment or share your experience if you have the chance to try them. Thanks a lot.

SpaceIm commented 3 years ago

Have to use components (Conan and CMake) to link only with Boost::filesystem. Linking with Boost::Boost breaks the build, since it picks all Boost components which are not necessary compatible with each other.

self.cpp_info.requires (no components) can be used in package_info() since conan 1.32.0. It allows to require specific components of dependencies.

Can cmake_findpackage generator be self-sufficient? It should be setting up the C++ standard and ABI flags the same way cmake generator does. Same for CONAN* variables.

cmake_find_package is nothing more than an emulation of CMake Find modules (and cmake_find_package_multi an emulation of CMake config file), it shouldn't add unexpected logic (maybe just the minimum C++ standard required like CMake config files can do, but not the one from profile). If you want to propagate others informations from your profile in a transparent integration, I believe that generate() is the good tool to use in combination with cmake_find_package / cmake_find_package_multi / pkg_config generators.