Dependency Management
About 1108 wordsAbout 4 min
2025-12-22
Conan's dependency management system is like a smart dependency detective - it automatically figures out what libraries your project needs, downloads them with all their dependencies, and ensures everything works together perfectly.
Understanding Dependencies
What is a Dependency?
A dependency is a library or package that your project needs to compile and run. Think of it like ingredients in a recipe:
# Your project (the main dish)
class MyProjectConan(ConanFile):
# Dependencies (ingredients)
requires = (
"fmt/10.0.0", # For beautiful text formatting
"openssl/3.0.8", # For security/encryption
"spdlog/1.11.0", # For logging
)Types of Dependencies
1. Build Dependencies (requires)
class MyProjectConan(ConanFile):
# Libraries needed to USE your package (runtime dependencies)
requires = (
"fmt/10.0.0", # Formatting library
"openssl/3.0.8", # Encryption library
)2. Tool Dependencies (tool_requires)
class MyProjectConan(ConanFile):
# Tools needed to BUILD your package (compile-time dependencies)
tool_requires = (
"cmake/3.27.0", # Build system generator
"ninja/1.11.1", # Fast build tool
"doxygen/1.9.6", # Documentation generator
)3. Test Dependencies (test_requires)
class MyProjectConan(ConanFile):
# Tools needed only for testing (not in final package)
test_requires = (
"gtest/1.13.0", # Testing framework
"benchmark/1.8.0", # Performance testing
)How Conan Resolves Dependencies
The Dependency Resolution Process
- Parse Requirements - Read your
conanfile.pyto see what you need - Search Remotes - Look for packages in configured remotes
- Resolve Dependencies - Find all transitive dependencies
- Check Compatibility - Ensure all packages work together
- Download/Build - Get or build the packages
- Generate Build Files - Create CMake/other build system files
Example Dependency Tree
MyApp (your project)
├── fmt/10.0.0
│ └── (no dependencies)
├── openssl/3.0.8
│ ├── zlib/1.2.13
│ └── (some platform-specific dependencies)
└── spdlog/1.11.0
└── fmt/10.0.0 (already included!)Conan is smart enough to:
- Share dependencies - Only download
fmtonce, even if multiple packages need it - Resolve conflicts - If different packages need different versions, Conan helps resolve it
- Optimize builds - Build only what's needed
Version Specification
Exact Versions
class MyProjectConan(ConanFile):
# Use specific version (most common)
requires = (
"fmt/10.0.0", # Exactly version 10.0.0
"openssl/3.0.8", # Exactly version 3.0.8
)Version Ranges
class MyProjectConan(ConanFile):
# Flexible versioning
requires = (
"fmt/[>=10.0.0 <11.0.0]", # Any 10.x version
"openssl/3.*", # Any 3.x version
"spdlog/1.11.0", # Exact version (fallback)
)Version Range Operators
| Operator | Meaning | Example |
|---|---|---|
> | Greater than | openssl/>3.0.0 |
< | Less than | fmt/<11.0.0 |
>= | Greater than or equal | boost/>=1.70.0 |
<= | Less than or equal | zlib/<=1.3.0 |
== | Equal to | cmake/==3.27.0 |
* | Wildcard | boost/1.8* (1.80, 1.81, etc.) |
&& | AND condition | boost/[>=1.70.0 && <2.0.0] |
Advanced Dependency Features
Conditional Dependencies
class MyProjectConan(ConanFile):
options = {
"use_openssl": [True, False],
"enable_logging": [True, False],
}
default_options = {
"use_openssl": True,
"enable_logging": True,
}
def configure(self):
"""Configure dependencies based on options"""
requires = ["fmt/10.0.0"]
if self.options.use_openssl:
requires.append("openssl/3.0.8")
if self.options.enable_logging:
requires.append("spdlog/1.11.0")
self.requires = tuple(requires)Optional Dependencies
class MyProjectConan(ConanFile):
# Required dependencies
requires = ("fmt/10.0.0",)
# Optional dependencies
def requirements(self):
if self.options.enable_benchmarking:
self.requires("benchmark/1.8.0")
if self.options.enable_profiling:
self.requires("google-perftools/2.10.0")Development Dependencies
class MyProjectConan(ConanFile):
# Regular dependencies
requires = ("fmt/10.0.0", "openssl/3.0.8")
# Build-time dependencies
build_requires = (
"cmake/3.27.0",
"doxygen/1.9.6",
)
# Test dependencies (for package testing)
test_build_requires = (
"gtest/1.13.0",
)Dependency Conflict Resolution
When Dependencies Clash
Sometimes different packages need different versions of the same library:
# Package A needs OpenSSL 1.1
# Package B needs OpenSSL 3.0
# Your project needs both A and B
class MyProjectConan(ConanFile):
requires = (
"package-a/1.0.0", # Depends on openssl/1.1.1
"package-b/2.0.0", # Depends on openssl/3.0.8
)Resolution Strategies
1. Version Override (Force a Version)
class MyProjectConan(ConanFile):
requires = (
"package-a/1.0.0",
"package-b/2.0.0",
("openssl/3.0.8", "override"), # Force OpenSSL 3.0
)
def configure(self):
# Both packages will use OpenSSL 3.0
# This works if both packages are compatible with OpenSSL 3.02. Conflict Detection and Warnings
# Conan will warn about conflicts
# Example output:
# Warning: Package 'package-a/1.0.0' requires 'openssl/1.1.1' but your project
# is trying to use 'openssl/3.0.8'. This might cause ABI incompatibilities.3. Multiple Version Handling
class MyProjectConan(ConanFile):
# Allow multiple versions (advanced)
def configure(self):
# This requires careful handling in your code
passDependency Graph Analysis
Visualizing Dependencies
# Show dependency tree
conan install . --print=graph
# Save dependency graph to file
conan install . --print=graph=graph.txt
# Generate dependency graph in DOT format
conan install . --print=graph_dot=deps.dotAnalyzing Dependencies
# Show package information
conan info .
# Show only direct dependencies
conan info . --only=requires
# Show build-time dependencies
conan info . --only=build_requires
# Show detailed dependency information
conan info . --json=info.jsonTransitive Dependencies
Understanding Transitive Dependencies
Dependencies often depend on other dependencies:
MyApp
├── fmt/10.0.0
│ └── (no dependencies)
├── openssl/3.0.8
│ └── zlib/1.2.13 # Transitive dependency
└── boost/1.81.0
├── zlib/1.2.13 # Same transitive dependency (shared!)
├── bzip2/1.0.8 # Another transitive dependency
└── (many more...)Managing Transitive Dependencies
Option 1: Default (Automatic)
Conan automatically handles transitive dependencies:
class MyProjectConan(ConanFile):
requires = ("boost/1.81.0",)
# Conan automatically includes zlib, bzip2, etc.Option 2: Explicit Transitive Dependencies
class MyProjectConan(ConanFile):
requires = (
"boost/1.81.0",
"zlib/1.2.13", # Explicitly include
)
# This is useful when you need to control versionsOption 3: Disable Transitive Dependencies
class MyProjectConan(ConanFile):
def configure(self):
# Remove transitive dependencies
if self.dependencies.get("boost"):
self.dependencies["boost"].requires.clear()Package Information Access
Accessing Dependency Information
class MyProjectConan(ConanFile):
requires = ("fmt/10.0.0", "openssl/3.0.8")
def generate(self):
# Access dependency information
fmt_dep = self.dependencies["fmt"]
openssl_dep = self.dependencies["openssl"]
# Get version
fmt_version = fmt_dep.ref.version
openssl_version = openssl_dep.ref.version
# Get settings
fmt_build_type = fmt_dep.settings.get_safe("build_type")
openssl_os = openssl_dep.settings.get_safe("os")
# Get package paths
fmt_include = fmt_dep.cpp_info.includedirs
openssl_libs = openssl_dep.cpp_info.libs
print(f"Using fmt {fmt_version} with build_type={fmt_build_type}")
print(f"Using openssl {openssl_version} on {openssl_os}")Conditional Logic Based on Dependencies
class MyProjectConan(ConanFile):
requires = ("fmt/10.0.0",)
def generate(self):
# Check if specific dependency exists
if self.dependencies.get("fmt"):
fmt = self.dependencies["fmt"]
# Check dependency version
if fmt.ref >= "10.1.0":
# Use new feature
pass
# Check dependency options
if fmt.options.get_safe("header_only"):
# Handle header-only library
passBest Practices for Dependency Management
1. Minimize Dependencies
# Good: Minimal dependencies
class GoodProjectConan(ConanFile):
requires = ("fmt/10.0.0",) # Only what you really need
# Bad: Too many dependencies
class BadProjectConan(ConanFile):
requires = ( # You probably don't need all of these
"boost/1.81.0", # Heavy dependency
"eigen/3.4.0", # Math library
"opencv/4.8.0", # Computer vision
"qt/6.4.0", # GUI framework
# ... 50 more dependencies
)2. Use Version Ranges Carefully
# Good: Reasonably flexible ranges
requires = (
"fmt/[>=10.0.0 <11.0.0]", # Allow patch updates
"openssl/3.*", # Allow minor updates
)
# Bad: Too restrictive or too broad
requires = (
"fmt/10.0.0", # Too restrictive
"openssl/>0.0.0", # Too broad - could break
)3. Document Dependencies
class DocumentedProjectConan(ConanFile):
"""
MyProject - A C++ networking library
Dependencies:
- fmt: Text formatting (required)
- openssl: TLS/SSL support (required)
- spdlog: Logging (optional, see enable_logging option)
"""
requires = ("fmt/10.0.0", "openssl/3.0.8")4. Test Dependency Compatibility
class TestedProjectConan(ConanFile):
def build(self):
# Test that all dependencies work together
# Build a small test program that uses all dependencies
test_code = """
#include <fmt/core.h>
#include <openssl/ssl.h>
#include <spdlog/spdlog.h>
int main() {
fmt::print("All dependencies work!\\n");
return 0;
}
"""
# Compile and run test...Key Points: Dependency Management
- Three types of dependencies -
requires,tool_requires,test_requires - Version specification - Exact versions, ranges, wildcards
- Automatic resolution - Conan handles transitive dependencies
- Conflict resolution - Version overrides and warnings
- Conditional dependencies - Based on options and settings
- Graph analysis - Visualize and understand dependency relationships
- Best practices - Minimize dependencies, use reasonable version ranges