Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] CMakeDeps-generated recipe-*-data.cmake files potentially extinguish transitive dependency names, causing issues to find libraries #15851

Closed
shoeffner opened this issue Mar 11, 2024 · 3 comments · Fixed by #15853
Assignees
Milestone

Comments

@shoeffner
Copy link
Contributor

shoeffner commented Mar 11, 2024

Environment details

If you have multiple build_types and the generated pkg-*-data.cmake have different dependencies, it is possible that a Debug build cannot find its dependencies.

This issue is often not too important, as most of the time you will have, e.g., a cmake_layout and thus separate generator directories for each build_type. In our case, we have a slightly special setup where we copy all generated files into a common cmake folder which is then shipped to our developers in a full installer of the build environment.
Thanks to the generator expressions and careful selection of list appends inside the generated files, this is a great way to get them up to speed with all conan recipes, but we realized an issue for conditional dependencies:

If a conanfile has no dependencies in the release config, but a dependency in the debug config, and the *data.cmake files are inside the same ${CMAKE_CURRENT_LISTS_DIR}, the *-release-*-data.cmake file will reset the _FIND_DEPENDENCY_NAMES causing CMake to fail to find the proper targets.

  • Operating System+version: Linux (Ubuntu 22.04), MacOS (Sonoma 14.3.1) (but should also apply for other OSs, it's a template issue)
  • Compiler+version: Any / irrelevant (CMake issue)
  • Conan version: 2.1.0
  • Python version: 3.10, 3.11 (any, should be irrelevant)

Steps to reproduce

Create three conanfiles:

  • pkg_a (for illustrative purposes a debug only library, say a debugger)
  • pkg_b depends on pkg_a if build_type=Debug, but not for build_type=Release (this is the crucial bit)
  • pkg_c which depends on pkg_b

pkg_a/conanfile.py:

from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.cmake import cmake_layout
from conan.tools.files import collect_libs


class Recipe(ConanFile):
    name = "pkg_a"
    version = "1.0"
    generators = "CMakeDeps", "CMakeToolchain"
    settings = "os", "build_type", "arch", "compiler"

    def layout(self):
        cmake_layout(self, src_folder="src")

    def validate_build(self):
        if self.settings.build_type != "Debug":
            raise ConanInvalidConfiguration("Debug Only")

    def package_info(self):
        self.cpp_info.set_property("cmake_target_name", "A::A")
        self.cpp_info.libs = collect_libs(self)

pkg_b/conanfile.py:

from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.cmake import cmake_layout
from conan.tools.files import collect_libs


class Recipe(ConanFile):
    name = "pkg_b"
    version = "1.0"
    generators = "CMakeDeps", "CMakeToolchain"
    settings = "os", "build_type", "arch", "compiler"

    def layout(self):
        cmake_layout(self)

    def requirements(self):
        if self.settings.build_type == "Debug":
            self.requires("pkg_a/1.0")

    def package_info(self):
        self.cpp_info.set_property("cmake_target_name", "B::B")
        self.cpp_info.libs = collect_libs(self)

pkg_c/conanfile.py:

from conan import ConanFile


class Recipe(ConanFile):
    name = "pkg_c"
    version = "1.0"
    generators = "CMakeDeps", "CMakeToolchain"
    settings = "os", "build_type", "arch", "compiler"

    def requirements(self):
        self.requires("pkg_b/1.0")

We can run:

conan create -s build_type=Debug pkg_a
conan create -s build_type=Debug pkg_b
conan create -s build_type=Release pkg_b
cd pkg_c
conan install -s build_type=Debug .
conan install -s build_type=Release .

and will get all the relevant CMake files.
These CMakeFiles could now be used without conan, but they come with a slight twist.

Using find_package(pkg_b) will actually fail in Debug mode with this error (well, there is another error about missing include dirs in pkg_b, but that's irrelevant for this issue):

CMake Error at pkg_b-Target-debug.cmake:15 (set_property):
  The link interface of target "pkg_b_DEPS_TARGET" contains:

    pkg_a::pkg_a

  but the target was not found.  Possible reasons include:

    * There is a typo in the target name.
    * A find_package call is missing for an IMPORTED target.
    * An ALIAS target is missing.

Call Stack (most recent call first):
  pkg_bTargets.cmake:24 (include)
  pkg_b-config.cmake:16 (include)
  CMakeLists.txt:4 (find_package)

You can reproduce this with this pkg_c/CMakeLists.txt inside pkg_c's directory:

cmake_minimum_required(VERSION 3.15)
project(pkg_c CXX)

find_package(pkg_b)
message(STATUS "${pkg_b_FIND_DEPENDENCY_NAMES}")

add_executable(pkg_c hello.cpp)
target_link_libraries(pkg_c pkg_b::pkg_b)

Oh, and the pkg_c/hello.cpp:

#include <iostream>

int main() {
    std::cout << "Hello" << std::endl;
}

Now a call to cmake will fail with above error, if the build type is Debug:

mkdir build; cd build; cmake -DCMAKE_PREFIX_PATH=$(pwd)/.. -DCMAKE_BUILD_TYPE=Debug ..

but "succeed" (minus the include errors) with build type Release:

mkdir build; cd build; cmake -DCMAKE_PREFIX_PATH=$(pwd)/.. -DCMAKE_BUILD_TYPE=Release ..

Cause

The problem is that pkg_bTargets.cmake globs for the files:

file(GLOB DATA_FILES "${CMAKE_CURRENT_LIST_DIR}/pkg_b-*-data.cmake")

foreach(f ${DATA_FILES})
    include(${f})
endforeach()

which includes the files in alphabetical order:

include("${CMAKE_CURRENT_LIST_DIR}/pkg_b-debug-armv8-data.cmake"
include("${CMAKE_CURRENT_LIST_DIR}/pkg_b-release-armv8-data.cmake"

which, in turn, eventually translates to:

# from debug-data
list(APPEND pkg_b_FIND_DEPENDENCY_NAMES pkg_a)
list(REMOVE_DUPLICATES pkg_b_FIND_DEPENDENCY_NAMES)
# from release-data
set(pkg_b_FIND_DEPENDENCY_NAMES "")

This in turn causes the find_dependency(...) loop in pkg_b-config.cmake to not find anything in either build_type and thus not declaring the target pkg_a::pkg_a properly; still, the debug configuration attempts to link against it in pkg_b-Target-debug.cmake:

set_property(TARGET pkg_b_DEPS_TARGET
             PROPERTY INTERFACE_LINK_LIBRARIES
             $<$<CONFIG:Debug>:${pkg_b_FRAMEWORKS_FOUND_DEBUG}>
             $<$<CONFIG:Debug>:${pkg_b_SYSTEM_LIBS_DEBUG}>
             $<$<CONFIG:Debug>:pkg_a::pkg_a>
             APPEND)

Fixes

A possible fix could be to not use set(pkg_b_FIND_DEPENDENCY_NAMES "") inside the corresponding templates, but instead rely on the append mechanism as well, simply appending an empty item:

list(APPEND pkg_b_FIND_DEPENDENCY_NAMES )

Manually patching the CMakeFiles like that inside the generator step fixes this for our use-case right now, but I haven't checked the CMake-list-append-behavior thoroughly enough to see if that's a good fix.

If this is indeed a bug and should be fixed, I will happily change the template with a PR, so let me know if you want to change that or if it works as intended.

Logs

CMake Error at pkg_b-Target-debug.cmake:15 (set_property):
  The link interface of target "pkg_b_DEPS_TARGET" contains:

    pkg_a::pkg_a

  but the target was not found.  Possible reasons include:

    * There is a typo in the target name.
    * A find_package call is missing for an IMPORTED target.
    * An ALIAS target is missing.

Call Stack (most recent call first):
  pkg_bTargets.cmake:24 (include)
  pkg_b-config.cmake:16 (include)
  CMakeLists.txt:4 (find_package)
@memsharded
Copy link
Member

Hi @shoeffner

Thanks very much for your report, and specially for the detailed instructions to reproduce. That helped a lot.

I think your research is correct, and that doing the APPEND was the originally intended way, and it could have been than it has worked for other cases just because the inclusion order was different and the APPEND happened after the set().

I am submitting it as a potential fix in #15853, hopefully for next release.

@memsharded memsharded added this to the 2.2.0 milestone Mar 11, 2024
@shoeffner
Copy link
Contributor Author

I especially like the condensed version of the issue in the conditional build type test, it demonstrates the issue really well. Thanks for looking into this and providing such a quick (potential) fix!

@memsharded
Copy link
Member

#15853 merged in develop2, it will be in next 2.2 release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants