conan-io / conan

Conan - The open-source C and C++ package manager
https://conan.io
MIT License
8.31k stars 986 forks source link

[bug] cmakedeps_macros.cmake does not create `SHARED_LIBRARY` import targets for libraries that only have a .dll but not a .lib file. #17395

Open Knitschi opened 5 days ago

Knitschi commented 5 days ago

Describe the bug

OS: Windows conan: 2.9.1

I am trying to consume the qt package with a project that uses the CMakeToolchain and CMakeDeps generators. I observer that find_package(Qt6 ... ) creates amongst others the import-target CONAN_LIB::qt_Qt6Cored_DEBUG of type SHARED_LIBRARY from which I can read the location of the Qt6Core.dll. BTW I need the location to deploy the dll to the build directory so I do not have to rely on PATH.

The problem is that the SHARED_LIBRARY target is not created for the Qt plugins like Qt6::QWindowsIntegrationPlugin. So the only way to find qwindows.dll would be to look in the path for it and guess its name. Our existing dll deployment code however takes import targets and then reads the IMPORTED_LOCATION_<config> property to get the .dll location.

After digging into the qt recipe and into cmakedeps_macros.cmake, it looks like there is no whay for cmakedeps_macros.cmake to create shared library import targets for libraries that have no .lib file. The function conan_package_library_targets() always looks for the .lib first and only when that is found it will look for the .dll. This fails if a library has no .lib which is the case for plugins.

My research also revealed that cmake 3.17 changed the default behavior of find_library() to no longer consider .dll files. Now users have to manuall activate .dll finding by adding set(CMAKE_FIND_LIBRARY_SUFFIXES .dll ${CMAKE_FIND_LIBRARY_SUFFIXES}) to their code.

With this we can change the recipe to fabricate a workaround:

# conanfile.py qt package simplified
...
self.cpp_info.components[componentname].libs = qwindows
self.cpp_info.components[componentname].libdirs = <path/to/plugin/dir> # contains only dlls.
self.cpp_info.components[componentname].bindirs = <path/to/plugin/dir>
# CMakeLists.txt test_package
...
set(CMAKE_FIND_LIBRARY_SUFFIXES .dll ${CMAKE_FIND_LIBRARY_SUFFIXES})
find_package(Qt6 COMPONENTS Core Network Sql Concurrent Xml REQUIRED CONFIG)

With this the test_package gets an import target with the name CONAN_LIB::qt_Qt6_QWindowsIntegrationPlugin_qwindowsd_DEBUG from which one could read the qwindows.dll location.

How to reproduce it

Can be reproduced with the qt package.

Add the following lines to test_package\CMakeLists.txt

# recipes\qt\6.x.x\test_package\CMakeLists.txt
...
get_property(importTargets DIRECTORY "${CMAKE_SOURCE_DIR}" PROPERTY IMPORTED_TARGETS)
foreach(target ${importTargets})
    get_property(type TARGET ${target} PROPERTY TYPE)
    message("----------------- ${target} - ${type}")
endforeach()

The output will contain SHARED_LIBRARY targets for Qt6::Core and other normal libraries but not for the plugin targets like Qt6::QWindowsIntegrationPlugin and others.

memsharded commented 5 days ago

Hi @Knitschi

This is a known limitation of the current CMakeDeps generator.

creates amongst others the import-target CONAN_LIB::qt_Qt6Cored_DEBUG of type SHARED_LIBRARY from which I can read the location of the Qt6Core.dll.

Please note that this is an internal implementation detail, that shouldn't be used as it can break in future releases. The recommended way by CMake to deploy shared library dependencies would be via the install(RUNTIME_DEPENDENCY_SET, which conan allows by providing the CONAN_RUNTIME_LIB_DIRS variable see https://docs.conan.io/2/reference/tools/cmake/cmaketoolchain.html#conan-runtime-lib-dirs

A different story is that plugins are really not linked libraries. They might not exist at all in CMake, because they might not be part of the build at all, just a pure runtime thing.

What happens if there are components that are only .dll and the consumer tries to link them? It will most likely fail the build as it will not find the import library. Conan components are designed to be at "build-time", so not sure this will not be a problematic approach.

We are trying to improve this in the new CMakeDeps generator, the one that is activated with -c tools.cmake.cmakedeps:new=will_break_next (only dev-testing). This generator do not create CONAN_LIB or any artificial targets, it creates the targets, with the correct SHARED/STATIC/INTERFACE time, doing the detection of the location if necessary (not explicitly defined in recipes, because recipes also learned the self.cpp_info.location and self.cpp_info.link_location and self.cpp_info.type info).

Following the CPS new specification, that we are also trying to contribute (see our CppCon23 and CppCon24 talks about it), this would require a new component type "MODULE" iirc, that would be able to model this. But this will still require some time to stabilize, the best to move it forward is to try it and give feedback. The current CMakeDeps generator will not be modified, the risk of breaking existing users is too high.

Knitschi commented 4 days ago

First off all thank you for your fast and detailed answer. I am glad to hear that you guys are working on a solution for plugins. Thank you for that as well.

I will check out your work when it is ready. Until then I will probably use my workaround that accesses the implementation targets.