Include a subdirectory using Conan

Summary

The core issue was a missing Conan package definition for the mylib dependency. The myapp recipe attempted to use find_package(mylib) in CMake, but there was no Conan recipe to export or package the mylib headers. Consequently, the mylib::mylib target was undefined, causing the linker error. Additionally, the CMake configuration referenced a discovery_devices target, which did not match the defined myapp executable target.

Root Cause

The root cause was an incorrect dependency modeling within the Conan ecosystem.

  • Missing Recipe: There was no conanfile.py for mylib to generate the necessary mylibConfig.cmake file.
  • Undefined Target: Because mylib was not a Conan package, find_package(mylib) failed silently or produced an empty result, leaving mylib::mylib undefined.
  • Target Mismatch: The target_link_libraries command in CMakeLists.txt referenced a target named discovery_devices instead of the actual executable target myapp.

Why This Happens in Real Systems

In real-world systems, this pattern occurs when developers try to integrate legacy code or internal libraries into a Conan-based workflow without fully encapsulating them as packages.

  • Hybrid Build Systems: Teams often mix raw file copying with package management, assuming Conan can consume raw headers simply by listing them in exports_sources.
  • Incomplete Transitions: When migrating from raw CMake to Conan, developers sometimes stop halfway, adding Conan logic to the app but treating the library as a simple subdirectory rather than a first-class package.
  • Copy-Paste Errors: CMake boilerplate is frequently reused; referencing the wrong target name (discovery_devices instead of myapp) is a common oversight during rapid prototyping.

Real-World Impact

The immediate impact is a broken build pipeline.

  • Compilation Failure: The build fails with “undefined reference” or “target not found” errors because the linker cannot resolve symbols from mylib.
  • Wasted Debugging Time: Engineers spend time investigating include paths and compiler flags when the actual issue is the missing CMake configuration generation.
  • Inconsistent Environments: Without a proper package, the library is not versioned or cached correctly, leading to reproducibility issues across different developer machines or CI runners.

Example or Code

To fix this, mylib must be packaged. Below is the corrected conanfile.py for mylib and the corrected CMakeLists.txt for myapp.

mylib/conanfile.py

from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMake, CMakeToolchain, CMakeDeps
import os

class MylibConan(ConanFile):
    name = "mylib"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    exports_sources = "include/*", "src/*", "CMakeLists.txt"
    package_type = "library"

    def layout(self):
        cmake_layout(self)

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

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

    def package(self):
        cmake = CMake(self)
        cmake.install()
        # Copy headers explicitly if not handled by cmake.install()
        copy(self, "*.hpp", src=os.path.join(self.source_folder, "include"), 
             dst=os.path.join(self.package_folder, "include"))

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

myapp/CMakeLists.txt (Corrected)

cmake_minimum_required(VERSION 3.15)
project(myapp CXX)

add_executable(myapp src/myapp.cpp src/main.cpp)

find_package(mylib REQUIRED)

# Corrected target name from 'discovery_devices' to 'myapp'
target_link_libraries(myapp PRIVATE mylib::mylib)

How Senior Engineers Fix It

Senior engineers approach this by enforcing strict separation of concerns and automated validation.

  1. Define the Package: Create a standalone conanfile.py for mylib. This ensures mylib is a versioned entity in the Conan cache.
  2. Standardize CMake: Use find_package to locate the dependency and target_link_libraries to consume it. This leverages CMake’s modern target-based architecture.
  3. Validate Locally: Before integrating, use conan create mylib/1.0@ to build and package the library. This proves the package is valid.
  4. Correct CMake Targets: Ensure the target name in target_link_libraries matches the executable defined in add_executable.
  5. Automate: Set up a CI pipeline that builds the library as a package first, then consumes it in the application build.

Why Juniors Miss It

Juniors often miss this because they treat Conan as a magic file copier rather than a package manager.

  • Assumption of “Include” Magic: They assume that simply adding include/* to exports_sources makes the headers available to the consumer’s compiler include path automatically, without the necessary package_info() configuration.
  • CMake Confusion: They may not fully understand that find_package relies on config files generated by the package build, not just the presence of source files.
  • Copy-Paste Reliance: They copy CMake commands (like target_link_libraries) without verifying that the target names (myapp vs discovery_devices) match their specific project structure.