Dependencies using conanfile.py

Summary

When integrating Boost, Boost.Asio, and a local library with custom include paths into a project using Conan, the core requirement is to correctly define local source paths in the conanfile.py alongside remote package dependencies. The mistake often lies in treating local libraries as Conan packages prematurely or failing to expose custom include directories to the build system via Conan’s environment and generation hooks.

Root Cause

The primary root cause in this scenario is the misconfiguration of the package_info() method or the absence of a CMakeToolchain/CMakeDeps generator setup for local headers. When a user adds a local library with .hpp files in a subdirectory (e.g., include/libs), they must explicitly tell Conan where these headers are located so consumers can find them.

Specifically:

  • The user attempts to install Boost (remote) and link a local library (source) simultaneously.
  • Without a custom conanfile.py for the local library or a proper cpp_info definition in the consumer conanfile.py, the build system (e.g., CMake) cannot locate the local .hpp files.

Why This Happens in Real Systems

In real-world development, teams often mix third-party packages (like Boost) with proprietary in-house libraries.

  • Conan’s Default Behavior: Conan primarily manages binary packages. It does not automatically include local source subdirectories in the global include path unless explicitly told to via cpp_info.includedirs.
  • Include Path Conflicts: Developers often assume that adding a folder to their IDE’s include path is sufficient. However, for a reproducible build managed by Conan, the Conan generated files (e.g., conan_toolchain.cmake) must propagate these paths to the build system.
  • Boost Dependencies: Boost is modular. boost-asio is often a header-only component, but it depends on system libraries and other Boost modules. If the local library requires boost-asio, the requires list in conanfile.py must be precise to avoid linker errors.

Real-World Impact

  • Build Failures: The immediate impact is build errors such as fatal error: boost/asio.hpp: No such file or directory or fatal error: mylib/header.hpp: No such file or directory.
  • Onboarding Friction: New developers waste hours configuring their local IDE include paths instead of coding, as the project dependencies are not self-contained in conanfile.py.
  • CI/CD Pipeline Breakage: Automated pipelines fail because the environment lacks the manual IDE configurations, leading to inconsistent builds between local machines and CI servers.
  • Version Conflicts: If Boost versions are not pinned, transitive dependencies might resolve to different versions, causing ABI incompatibilities at runtime.

Example or Code

Below is an example conanfile.py that defines a Conan package for a local library (named mylib) and a consumer conanfile.py that uses it alongside boost and boost-asio.

1. Local Library conanfile.py (in include/libs/mylib/conanfile.py)

This package exports its .hpp files so they can be consumed by other packages.

from conan import ConanFile
from conan.tools.files import copy
from os.path import join

class MyLibConan(ConanFile):
    name = "mylib"
    version = "1.0"
    exports_sources = "*.hpp"  # Exports header files in this directory

    def package(self):
        # Copy headers to the package include folder
        copy(self, "*.hpp", src=self.source_folder, dst=join(self.package_folder, "include"))

    def package_info(self):
        # Define the include directory so consumers can find the headers
        self.cpp_info.includedirs = ["include"]

2. Consumer conanfile.py (Main Application)

This file declares the dependencies for both Boost (remote) and the local library.

from conan import ConanFile
from conan.tools.cmake import cmake_layout

class MyAppConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"
    requires = "boost/1.81.0", "mylib/1.0"  # Requires remote Boost and local mylib
    generators = "CMakeDeps", "CMakeToolchain" # Essential for CMake integration

    def layout(self):
        cmake_layout(self)

How Senior Engineers Fix It

Senior engineers address this by ensuring complete dependency isolation and correct build system integration:

  1. Create a Conan Recipe for Local Code: Instead of treating local libraries as loose source folders, seniors wrap them in a minimal conanfile.py (as shown above). This makes the local library a first-class citizen in the dependency graph.
  2. Use Generators Correctly: They explicitly define generators = "CMakeDeps", "CMakeToolchain" (or VirtualBuildEnv). This ensures that the paths to Boost and the local library headers are automatically injected into the build environment.
  3. Leverage cpp_info: They use self.cpp_info.libs and self.cpp_info.includedirs in the package_info() method to ensure the consumer’s build system knows exactly which libraries to link and where to find headers.
  4. Contextualize Boost: They specifically verify if boost is used as a header-only library or requires linking. For boost-asio, they ensure system-level dependencies (like OpenSSL if applicable) are also included in the requires list.

Why Juniors Miss It

Juniors often miss this configuration because they conflate source code organization with dependency management:

  • Hardcoding Paths: Juniors often manually add relative paths (e.g., ../include/libs) to their CMakeLists.txt or compiler flags. This works on their machine but breaks when the project is cloned elsewhere or run in CI.
  • Overlooking package_info: When creating a local package, they might forget to implement package_info(). Without it, Conan installs the package but doesn’t expose the headers or libraries to the consumer, resulting in “file not found” errors.
  • Confusion on Header-Only vs. Binary: They may not realize that boost-asio is largely header-only but still requires boost as a base. They might attempt to install only specific Boost modules without realizing the transitive dependencies required for linking.
  • Missing Generators: They might forget to add generators = "CMakeDeps" in the consumer conanfile.py, leading to missing FindBoost.cmake or similar configuration files during the CMake configuration step.