cmakepackage-managersconanconan-2

conan2: Library 'X' not found in package (in "Testing the package" stage)


I'm trying my luck with conan2, but I have a hard time getting it to build my library. Well to be precise, my library is packaged just fine, but the test_package invocation fails as it can't find said library. Here's the CMake error from the conan-generated cmakedeps_macros.cmake:

======== Testing the package: Building ========
cojson/0.1.0 (test package): Calling build()
cojson/0.1.0 (test package): Running CMake.configure()
cojson/0.1.0 (test package): RUN: cmake -G "Visual Studio 17 2022" -DCMAKE_TOOLCHAIN_FILE="C:/dev/cojson/test_package/build/msvc-193-x86_64-20-release/generators/conan_toolchain.cmake" -DCMAKE_INSTALL_PREFIX="C:/dev/cojson/test_package" -DCMAKE_POLICY_DEFAULT_CMP0091="NEW" "C:\dev\cojson\test_package"
...(cmake stuff)...
CMake Error at build/msvc-193-x86_64-20-release/generators/cmakedeps_macros.cmake:67 (message):
  Library 'cojson::cojson' not found in package.  If 'cojson::cojson' is a
  system library, declare it with 'cpp_info.system_libs' property

I'm not really adept enough to see behind the curtains. I have fairly good understanding of cmake, but I don't quite get how targets are imported into the test_package context. So I'm a bit lost with this error. Here is my project structure:

├── CMakeLists.txt
├── src
│   ├── CMakeLists.txt
│   └── main.cpp
├── include
│   └── cojson
│       └── myheader.hpp
├── tests
│   ├── CMakeLists.txt
│   └── tests.cpp
├── test_package
│   ├── conanfile.py
│   ├── CMakeLists.txt
│   └── src
│       └── example.cpp
└── LICENSE

I have gone through all tutorial examples (which of course work flawlessly) and poked around in the docs but I didn't find a specific hint on why the target might not be known. My guesses were

so that should work or not?

Here are the relevant parts:

conanfile.py (root):

import os
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout
from conan.tools.build import check_min_cppstd
from conan.tools.files import copy

class cojsonConan(ConanFile):
    name = "cojson"
    version = "0.1.0"

    # Optional metadata
    license = "https://github.com/MartyMcFlyInTheSky/cojson/blob/master/LICENSE"
    author = "Samuel Beer"
    url = "https://github.com/MartyMcFlyInTheSky/cojson"
    description = "A C++ JSON library using coroutines"
    topics = ("json", "coroutines", "cpp20")

    # Binary configuration
    settings = "os", "compiler", "build_type", "arch"
    options = {
        "shared": [True, False],
        "fPIC": [True, False],
    }
    default_options = {
        "shared": False,
        "fPIC": True,
    }

    # Sources located in same place
    exports = ["LICENSE"]
    exports_sources = "CMakeLists.txt", "src/*", "include/*", "tests/*"

    def validate(self):
        check_min_cppstd(self, "20")

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def configure(self):
        # delete fPIC option if shared
        if self.options.shared:
            # If os=Windows, fPIC will have been removed in config_options()
            # use rm_safe to avoid double delete errors
            self.options.rm_safe("fPIC")

    def requirements(self):
        self.test_requires("gtest/1.14.0")

    def _configure_cmake(self):
        cmake = CMake(self)
        cmake.configure()
        return cmake

    def layout(self):
        cmake_layout(self)

    def generate(self):
        tc = CMakeToolchain(self)
        tc.generate()

    def build(self):
        cmake = self._configure_cmake()
        cmake.build()
        # if not self.conf.get("tools.build:skip_test", default=False):
        cmake.test()

    def package(self):
        copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"))
        cmake = self._configure_cmake()
        cmake.install()

    # consumer information
    def package_info(self):
        self.cpp_info.libs = ["cojson"]

conanfile.py (test_package):

import os

from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout
from conan.tools.build import can_run

class cojsonConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    generators = "CMakeDeps", "CMakeToolchain"

    def requirements(self):
        self.requires(self.tested_reference_str)

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def layout(self):
        cmake_layout(self)

    def test(self):
        if can_run(self):
            cmd = os.path.join(self.cpp.build.bindir, "example")
            self.run(cmd, env="conanrun")

EDIT1:

@Tsyvarev in the comments had the notable idea to not export seperate target names. So what I did is I left my main target at cojson_core and aliased it to cojson::cojson_core for consistency, and adapted that in tests and test_package CMakeFiles:

test_package/CMakeLists.txt

...
target_link_libraries(example cojson::cojson_core)
...

In the conanfile I had to adapt the exported libraries as well:

conanfile.py

def package_info(self):
    # self.cpp_info.libs = ["cojson"]
    self.cpp_info.libs = ["cojson_core"]
    self.cpp_info.set_property("cmake_target_name", "cojson::cojson_core")

This got me at least one step further, as cojson::cojson_core seems to be found. HOWEVER: I get another error now, stating that it doesn't know where the lib header files are:

C:\dev\cojson\test_package\src\example.cpp(2,10): fatal  error C1083: Datei (Include) kann nicht geöffnet werden: "cojson/cojson.hpp": No such file or directory [C:\dev\cojson\test_package\build\msvc-193-x
86_64-20-release\example.vcxproj]

This is very confusing, given that the test_package obviously has found the cojson::cojson_core target which should declare the include directories. I went to test test_package with cmake alone and it works if I do it like this:

in cojson/test_package/ execute

cmake -S . -B build -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\dev\installcojson
cmake --build build

The target is found so are the include directories. Idk why it doesn't work when conan is doing it. Any ideas? Maybe someone can explain how conan defines targets: Are these "wrapper" targets that conan defines by itself for every actual cmake target?

EDIT2:

The issue with the missing header file is now resolved. I had excluded the line

set_target_properties(cojson_core PROPERTIES PUBLIC_HEADER
    ${CMAKE_CURRENT_SOURCE_DIR}/../include/cojson/cojson.hpp;
)

for some reason. Inserting it back finally made me build the package. Still, the question is not resolved, as I want my target to have a different export name: cojson. I tried to again adapt the properties in conanfile.py

def package_info(self):
    # self.cpp_info.libs = ["cojson"]
    self.cpp_info.libs = ["cojson"]
    self.cpp_info.set_property("cmake_target_name", "cojson::cojson")

...aand the notorious error from above is back. How is the target not recognised?


Solution

  • I solved it.

    The problem was that the conan toolchain will not only look for a config module, but also for the binary using find_library()

    test_package/build/msvc../generators/cmakedeps_macros.cmake:23:

    find_library(CONAN_FOUND_LIBRARY NAMES ${_LIBRARY_NAME} PATHS ${package_libdir}
                 NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    

    So it's important the binary name matches the library name of conanfile.py exactly.

    Solution

    Add

    set_target_properties(cojson_core PROPERTIES OUTPUT_NAME "cojson")
    

    to the target which corresponds to the library as defined in conanfile.py:

    def package_info(self):
        self.cpp_info.libs = ["cojson"]