Skip to content

Dependency Resolution

Unlike languages with a unified ecosystem (Rust/Cargo, Node/NPM, Python/Pip), C++ does not utilize a Centralized registry or a standard package manager. This fragmentation is not an oversight but a Consequence of the language”s compilation model: C++ compiles directly to machine code, heavily Dependent on the Application Binary Interface (ABI).

To manage dependencies effectively, one must select a Distribution Model that aligns with the Project’s requirements regarding reproducibility, build time, and ABI compatibility.

In bytecode or interpreted languages, dependencies are often distributed as generic artifacts Compatible with any runtime version. In C++, a library compiled with GCC 11 in Debug mode is Binary-incompatible with the same library compiled with MSVC in Release mode.

A single C++ library source (e.g., fmt) can result in dozens of incompatible binary artifacts Based on:

  1. Operating System: Windows, Linux, macOS.
  2. Architecture: x86_64, ARM64.
  3. Compiler: GCC, Clang, MSVC.
  4. Runtime Library: libstdc++, libc++, MSVCRT (Dynamic/Static).
  5. Standard Version: C++17, C++20, C++23.
  6. Build Configuration: Debug, Release, RelWithDebInfo.

The total number of valid ABI configurations for a single library on a single platform with two Compilers, two standard libraries, and three build types is 2×2×3=122 \times 2 \times 3 = 12. Across three Platforms and two architectures, this becomes 12×3×2=7212 \times 3 \times 2 = 72. This combinatorial Explosion is why binary package management for C++ is fundamentally harder than for languages with a Single runtime.

Successful dependency management requires a system capable of resolving this matrix or bypassing it Entirely via source compilation.

1. System Package Managers (The “Distro” Model)

Section titled “1. System Package Managers (The “Distro” Model)”
  • Tools: apt``pacman``brew``choco.
  • Mechanism: Libraries are installed globally into system paths (/usr/lib``/usr/include).
  • Architecture:
  • Pre-built Binaries: The package maintainer decides the compiler and flags.
  • Shared Linkage: provides shared objects (.so``.dll) linked dynamically.
  • Critical Drawbacks:
  • ABI Mismatch: The system library might use libstdc++ while you wish to use libc++.
  • Version Locking: You are restricted to the version provided by the OS repository (often outdated).
  • Non-Reproducible: A developer on Ubuntu 22.04 will have different library versions than a developer on Ubuntu 24.04.

Verdict: Unsuitable for most application development. Valid only for system-level dependencies (drivers, libc).

  • Tools: Git Submodules, Monorepos.
  • Mechanism: Source code of dependencies is committed directly into the project repository or checked out into a subdirectory.
  • Architecture:
  • Source Compilation: Dependencies are built as part of the main project build graph.
  • Total Control: Exact commits are locked.
  • Critical Drawbacks:
  • Repo Bloat: Cloning the project becomes slow.
  • Transitive Complexity: If Lib A and Lib B both vendor Lib C, managing version conflicts (Diamond Dependency) requires manual intervention.
  • Build Time: Every clean build recompiles the entire world.

Verdict: Valid for very small projects or massive monorepos (Google style), but scales poorly For mid-sized projects without tooling.

  • Tools: vcpkg, CPM.cmake.
  • Mechanism: The tool downloads dependency source code and compiles it locally using the exact same compiler and flags as the main project.
  • Architecture:
  • ABI Guarantee: Since the dependency is built contextually with the project, ABI compatibility is mathematically guaranteed.
  • Semantic Versioning: Allows specifying constraints (fmt >= 9.0).
  • Trade-off: Significantly increased build times on the first run, as all dependencies must be compiled from scratch.

Verdict: The recommended default for modern C++ development due to robustness and simplicity.

  • Tools: Conan.
  • Mechanism: Contributors specify profiles (compiler, flags, OS) and upload links of pre-built binaries to a central repository. Allowing binaries to be downloaded and linked directly into the project.
  • Architecture:
  • Profile Matching: The client calculates a hash of the current compiler settings and requests a binary matching that hash.
  • Fallback: If no binary matches, it falls back to building from source.
  • Trade-off: Requires complex infrastructure setup (Artifactory, remotes) and rigorous profile management.

Verdict: An alternative that can reduce build times locally and for CI, but adds significant Complexity.

Many real-world C++ organizations combine multiple models:

  • Source-based for proprietary libraries + binary caching for public dependencies (e.g., vcpkg with NuGet binary cache).
  • CPM.cmake for CI + Conan for release builds with binary artifacts.
  • System packages for tooling (cmake, ninja, clang) + vcpkg for project dependencies.

The choice of distribution model is not binary; it is a spectrum based on the project’s scale, CI Infrastructure maturity, and ABI requirements.

Package managers can be classified along several axes:

Source-Based vs. Binary vs. Registry-Based

Section titled “Source-Based vs. Binary vs. Registry-Based”
AxisSource-BasedBinary-BasedRegistry-Based
MechanismDownloads source, builds locallyDownloads pre-built artifactsResolves metadata from a registry, may build or download
ABI GuaranteeAlways correct (same compiler)Depends on profile matchingDepends on implementation
First Build TimeSlow (compiles all deps)Fast (downloads)Variable
InfrastructureNone (just Git)Requires artifact serverRequires registry server
ExamplesCPM.cmake, vcpkg (default)Conan (primary mode)Cargo, npm, vcpkg (with binary cache)
FeaturevcpkgConan 2.xCPM.cmakeBuck2xmake
ResolutionBaseline + constraintsSAT solver (libsolv)First-winsSAT solverVersion ranges
Binary CachingNuGet / HTTP / filesBuilt-in (remotes)NoneBuilt-inOptional
RegistryGit-based (ports)ConanCenter / customGitHub reposCustomCommunity repos
CMake IntegrationToolchain fileCMake generatorsadd_subdirectoryNativeNative
Cross-CompilationTripletsProfiles + settingsInherits hostProfilesPlatform configs
Lockfilevcpkg.json baselineconan.lockNone (Git SHA)LockfileLockfile
LanguageCMake (portfiles)Python (conanfile)CMakeStarlarkLua

A lockfile captures the exact resolved dependency graph at a point in time, ensuring that every Build from that lockfile produces the same result.

Consider a project that depends on LibA >= 1.0.0 and LibB >= 2.0.0. Both libraries have released New versions: LibA 1.5.0 and LibB 2.3.0.

Without a lockfile:

  • Developer A resolves the graph on Monday and gets LibA 1.4.0 and LibB 2.2.0.
  • Developer B resolves the graph on Wednesday and gets LibA 1.5.0 and LibB 2.3.0.
  • Both developers build “the same” project but with different dependency versions. If LibA 1.5.0 introduced a behavioral change, the two developers will observe different program behavior.
  • CI may resolve a third combination, producing a binary that neither developer tested.

With a lockfile:

  • The lockfile records LibA = 1.4.0 and LibB = 2.2.0. Every developer and CI agent uses these exact versions regardless of when they build.
  • Updating a dependency is an explicit act (modify the manifest, regenerate the lockfile, commit the lockfile). This makes dependency updates auditable via version control.

The lockfile is the only mechanism that guarantees build reproducibility in the presence of version Ranges and floating dependencies.

Vcpkg does not use a traditional lockfile. Instead, it pins the entire ecosystem to a Git commit SHA (the baseline). This is equivalent to locking every dependency’s version simultaneously.

{
"name": "my-project",
"version-string": "1.0.0",
"builtin-baseline": "a1b2c3d4e5f6789012345678abcdef12"
}

When builtin-baseline is set, the versions of all dependencies are resolved against the vcpkg Registry at that specific commit. Updating a single dependency requires changing the baseline or Using overrides.

Conan generates explicit lockfiles that capture the full resolved graph:

# conan.lock (simplified)
[graph_root]
my-project/1.0.0
requires: fmt/10.1.1, nlohmann-json/3.11.2
[packages]
fmt/10.1.1:abc123def456
nlohmann-json/3.11.2:789abc012def

The lockfile is consumed with conan install . --lockfile=conan.lock --lockfile-out=conan.lock Ensuring that the exact same versions and binary package IDs are used across all machines and CI Agents.

  • Source determinism: Same source code is fetched (Guaranteed by SHA/pinned tags).
  • Build determinism: Same compiler flags produce the same binary (Guaranteed by source-based models).
  • Binary determinism: Same pre-built artifact is downloaded (Guaranteed by content-addressable hashing in Conan/vcpkg binary caches).

Different ecosystems use different version constraint syntaxes. Understanding these is critical when Reading dependency specifications.

Per SemVer 2.0.0:

  • MAJOR: Breaking changes.
  • MINOR: Backward-compatible additions.
  • PATCH: Backward-compatible bug fixes.

C++ libraries rarely follow SemVer strictly because the concept of “breaking change” is ABI-dependent. A library that changes a private member variable may break ABI compatibility without Changing the public API. This makes SemVer an imperfect fit for C++ binary dependencies.

OperatorExampleMatchesDoes Not Match
Exact=1.2.31.2.31.2.4, 1.3.0
Caret ^^1.2.3\geq 1.2.3, < 2.0.02.0.0
Tilde ~~1.2.3\geq 1.2.3, < 1.3.01.3.0
Range>=1.0.0 &lt; 2.0.01.5.02.0.0
Wildcard1.*1.0.0, 1.9.92.0.0

Caret allows changes that do not modify the left-most non-zero digit. This is the default in Cargo and Conan.

Tilde allows patch-level changes only. This provides tighter control, suitable for dependencies Where minor version bumps may introduce regressions.

Vcpkg uses its own constraint syntax:

{
"name": "fmt",
"version>=": "10.1.0"
}

Supported operators: version>=``version>``version<=``version<``version=. Combined with the Baseline mechanism, this allows upgrading specific packages while keeping the rest of the ecosystem Frozen.

A dependency graph is a directed acyclic graph (DAG) where nodes are packages and edges represent “depends on” relationships.

  • Direct dependencies: Packages you explicitly list in your manifest.
  • Transitive dependencies: Dependencies of your dependencies, pulled in automatically.
  • Graph depth: The longest path from the root to a leaf node.

For a typical C++ project using vcpkg:

my-app (root)
+-- fmt (depth 1)
+-- spdlog (depth 1)
+-- fmt (depth 2, already resolved)
Project TypeDirect DepsTotal (with transitive)
Small CLI tool2-55-15
Medium application10-2030-80
Large framework (Qt)5-10200+

Managing transitive dependencies is why SAT solving and lockfiles matter: a change at depth 3 can Cascade to affect the entire build.

Package names across different registries follow different conventions:

PackageConanvcpkgSystem (apt)
fmtfmt/10.1.1fmtlibfmt-dev
nlohmann_jsonnlohmann_json/3.11.2nlohmann-jsonnlohmann-json3-dev
OpenSSLopenssl/3.1.2openssllibssl-dev
zlibzlib/1.2.13zlibzlib1g-dev

The inconsistency in naming (nlohmann_json vs nlohmann-json vs nlohmann-json3-dev) is a Consequence of the fragmented ecosystem. When migrating between package managers, name mapping is Often the first manual step.

Modern package managers (Conan 2.x, vcpkg) reduce version resolution to a Boolean satisfiability Problem. Each possible version of each package becomes a Boolean variable. Constraints become Clauses in a CNF (Conjunctive Normal Form) formula.

Example transformation:

  • LibA requires fmt >= 9.0 becomes: (fmt_9_0 OR fmt_9_1 OR fmt_10_0 OR ...)
  • LibB requires fmt < 10.0 becomes: (fmt_9_0 OR fmt_9_1)

The solver finds an assignment that satisfies all clauses simultaneously. If no assignment exists, The solver reports a conflict.

Conan 2.x uses libsolv, the same SAT solver used by RPM and openSUSE’s Zypper, which can resolve Graphs with thousands of packages in milliseconds.

Some simpler resolvers (early vcpkg, CPM.cmake) use a greedy approach: they resolve dependencies Depth-first and accept the first version that satisfies the immediate constraints. This is faster But can produce suboptimal or conflicting results.

StrategyOptimalitySpeedConflict Reporting
SAT solverGuaranteed optimalModerate (ms for typical graphs)Precise conflict sets
Greedy/first-winsMay miss valid solutionsFastVague (“version conflict”)
BacktrackingOptimal (exhaustive)Slow (worst case exponential)Can report why each attempt failed

A critical architectural challenge in C++ dependency management is the Diamond Dependency Combined with the One Definition Rule (ODR).

Scenario:

  • App depends on LibA and LibB.
  • LibA depends on JsonLib v1.0.
  • LibB depends on JsonLib v2.0.

In languages like Node.js (npm), LibA and LibB can each receive their own copy of JsonLib (nested node_modules). In C++, this is impossible if the symbols are exported globally. Linking Two versions of JsonLib into the same executable violates the ODR [N4950 S6.3], resulting in Linker errors or runtime undefined behavior (segfaults).

C++ Package managers must perform SAT (Boolean Satisfiability) Solving to find a single version Of JsonLib that satisfies the constraints of both LibA and LibB.

  • If LibA requires JsonLib >= 1.0 and LibB requires JsonLib >= 2.0The solver selects 2.0.
  • If LibA requires JsonLib < 2.0 and LibB requires JsonLib >= 2.0The build must fail before compilation begins.

This is a fundamental advantage of SAT-based resolution over greedy approaches: the solver can prove That no valid solution exists and report a precise conflict, rather than silently producing a broken Build.

For the architecture described in this course, we adhere to the following hierarchy:

  1. Default: vcpkg (Manifest Mode). It offers the best balance of usability, ODR safety (via strict baseline versioning), and ABI compliance (source-based).
  2. Lightweight: CPM.cmake. For projects with very few dependencies where setting up vcpkg is overkill. This can be used with conan to enable building source-based packages when library updates are not propagated to vcpkg or conan.
  3. Scale: Conan. Only when build times become the primary bottleneck (~30+ minutes) and binary caching is mandatory.

C++ dependency management introduces supply chain risks that differ from interpreted languages. Because C++ compiles to native machine code, a compromised dependency can execute arbitrary code With the full privileges of the host process — there is no sandbox or bytecode verifier.

  1. Dependency Confusion (Typosquatting): An attacker publishes a package with a name similar to a popular library (e.g., nlohmann-json vs nlohmann_json). If the package manager resolves names incorrectly, the malicious package is fetched instead.
  2. Compromised maintainer account: The maintainer’s account is breached, and a backdoored version is pushed. This happened to event-stream (Node.js, 2018) and xz-utils (C, 2024).
  3. Compromised build infrastructure: The CI/CD pipeline that produces binary artifacts is compromised, injecting malicious code into cached binaries.
  4. Transitive dependency injection: A legitimate package adds a new transitive dependency that is malicious.
  • Pin dependencies to exact SHAs: Never use floating versions like >= 1.0.0 in production. Lock to a specific Git commit SHA in vcpkg.json or conan.lock.
  • Audit the lockfile diff: When updating dependencies, review the full resolved graph diff, not just the direct dependency changes. A transitive dependency update can introduce a vulnerability.
  • Reproduce from source: Use source-based package managers (vcpkg, CPM.cmake) to ensure you are building from auditable source, not downloading opaque binary artifacts.
  • Use export CMAKE_VERIFY_INTERFACE_HEADER_SETS ON (CMake 3.29+) to verify that exported headers form a self-consistent interface, catching tampering at configure time.

The following CMakeLists.txt demonstrates a robust vcpkg integration with property propagation:

cmake_minimum_required(VERSION 3.28)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(fmt CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE fmt::fmt nlohmann_json::nlohmann_json)
# Verify that linked targets actually exported what they claim
set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ON)

When CMake locates packages via vcpkg’s toolchain file (cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]/scripts/buildsystems/vcpkg.cmake), it resolves the Dependency graph, compiles from source, and links against the resulting libraries. The CMAKE_VERIFY_INTERFACE_HEADER_SETS check ensures that the installed package headers form a valid Interface, catching common packaging errors and potential tampering.

  • Mixing package managers: Using both vcpkg and Conan for the same project can cause ODR violations. Pick one and stick with it, or use Conan’s CMake integration to consume vcpkg packages through a unified interface.
  • Unpinned versions: Relying on master or HEAD branches breaks reproducibility. Always pin to a specific tag or commit SHA.
  • Ignoring transitive dependencies: A dependency’s dependency may introduce a vulnerable library. Audit the full resolved graph, not just direct dependencies.
  • Assuming ABI compatibility: Two packages compiled with different standard library implementations (libstdc++ vs libc++) are binary-incompatible even if they use the same compiler version.
  • Not committing the lockfile: A lockfile that is not in version control provides no reproducibility guarantee. Always commit vcpkg.json``conan.lockOr equivalent to the repository.
  • Overriding dependencies without understanding the cascade: Using vcpkg overrides or Conan requires to force a specific version of a transitive dependency may break the dependency that originally required it. Always verify that the override satisfies all constraints.

Transitive Dependency Resolution in Practice

Section titled “Transitive Dependency Resolution in Practice”

When a package manager resolves the dependency graph, it must handle transitive dependencies Correctly. Consider a project with the following dependency structure:

my-app
+-- spdlog/2.1.0
| +-- fmt/10.1.0 (required: >= 9.0.0)
+-- nlohmann_json/3.11.2
+-- (no dependencies)

The package manager must ensure that:

  1. fmt 10.1.0 is built with the same compiler flags as spdlog 2.1.0 and my-app.
  2. fmt 10.1.0 is built exactly once and shared by all consumers.
  3. If spdlog were updated to 2.2.0 which requires fmt >= 10.2.0The resolver would either upgrade fmt to 10.2.0 (satisfying both constraints) or report a conflict if 10.2.0 is not available.

Dependency Graph Depth and Build Time Impact

Section titled “Dependency Graph Depth and Build Time Impact”

The depth of the dependency graph directly affects build time. Each level of transitive dependency Adds to the serial portion of the build (if dependencies must be built in order) or to the parallel Portion (if they are independent).

For source-based package managers, the first build of a project with 30 transitive dependencies may Take 10-30 minutes, depending on the complexity of the dependencies. Binary caching reduces this to Seconds.

Version Conflicts in Transitive Dependencies

Section titled “Version Conflicts in Transitive Dependencies”

A version conflict occurs when two direct (or transitive) dependencies require incompatible versions Of the same transitive dependency. The package manager’s resolution algorithm determines how this is Handled:

Package ManagerConflict Behavior
vcpkgFails at configure time with a clear error message
Conan 2.xSAT solver finds a compatible version or reports a conflict with the unsatisfiable constraints
CPM.cmakeFirst-wins: the first resolved version is used, potentially violating later constraints

The “first-wins” behavior of CPM.cmake is a known limitation. It means that if LibA (resolved First) requires fmt >= 9.0 and LibB (resolved second) requires fmt >= 10.0CPM.cmake may use fmt 9.x if that is the first version it encounters, violating LibB’s constraint. This is why CPM.cmake is recommended only for projects with simple dependency graphs.

CMake find_package vs Package Manager Integration

Section titled “CMake find_package vs Package Manager Integration”

CMake provides two mechanisms for locating dependencies: find_package() and package Manager-specific integration. Understanding the difference is critical:

find_package(fmt CONFIG REQUIRED)
target_link_libraries(app PRIVATE fmt::fmt)

find_package() searches for installed packages using CMake’s search heuristics (platform-specific Paths, CMAKE_PREFIX_PATHRegistry). It does not download or build anything; it only locates Pre-installed packages.

Package Manager Integration (vcpkg, Conan)

Section titled “Package Manager Integration (vcpkg, Conan)”
# vcpkg: find_package works because vcpkg's toolchain file installs packages into a known location
find_package(fmt CONFIG REQUIRED)
# Conan: uses CMake generators to create find modules
find_package(fmt CONFIG REQUIRED)

Both vcpkg and Conan integrate with CMake by either:

  • Providing a toolchain file that adds their install prefix to CMake’s search paths (vcpkg), or
  • Generating CMake find modules that find_package() can discover (Conan).

The key point is that find_package() is the common interface. The package manager handles Downloading, building, and installing dependencies behind the scenes, and find_package() locates The result.

  • System dependencies (cmake, ninja, compilers): Use find_package() directly.
  • Project dependencies (fmt, nlohmann_json, Boost): Use a package manager (vcpkg or Conan) that integrates with find_package().
  • Simple projects with 1-3 dependencies: CPM.cmake’s FetchContent approach is viable.
  • Do not mix find_package() with manual add_subdirectory() for the same dependency across different targets in the same project.

Achieving reproducible builds across different developer machines and CI agents requires three Conditions:

  1. Same source code: Version controlled (Git SHA).
  2. Same toolchain: Same compiler version, flags, and standard library. Use a toolchain file (CMake) or profile (Conan) to pin these.
  3. Same dependencies: Lockfile-pinned versions (vcpkg baseline or Conan lockfile).

The build is reproducible if and only if all three conditions are met. A missing lockfile means Condition 3 is violated. A compiler upgrade on one machine means condition 2 is violated.

For source-based package managers, you can verify reproducibility by comparing the build artifacts:

Terminal window
# Build on machine A
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake
cmake --build build
# Build on machine B (same source, same lockfile, same compiler)
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg.cmake
cmake --build build
# Compare (should be bit-for-bit identical with -O0)
diff <(sha256sum build/app) <(ssh machine-b 'sha256sum build/app')

Deterministic builds require -O0 (no optimization, which may vary with compiler version) and Timestamps to be stripped from the binary. Use -frandom-seed=0 and -Wl,--build-id=none for Bit-exact reproducibility.

Binary caching is the primary mechanism for reducing build times in production CI environments. The Concept is simple: if a dependency has already been built with identical inputs, download the Pre-built artifact instead of rebuilding from source.

The cache key is a hash of:

  1. Source code content (Git SHA of the dependency).
  2. Compiler version and flags (-std=c++23``-O2``-fPIC).
  3. Platform triplet (os-arch-compiler-runtime).
  4. CMake toolchain file content.
  5. Dependency’s own dependencies (transitive cache key).

If any of these inputs change, the cache key changes and the binary must be rebuilt.

BackendTool SupportUse Case
Local filesystemvcpkg, ConanDeveloper machines (single-user)
HTTP servervcpkg (NuGet), Conan (remote)CI/CD shared cache
Amazon S3Conan (S3 remote)Cloud-based CI
Azure Blobvcpkg (NuGet), ConanAzure DevOps
NFS mountvcpkg (binary cache), ConanOn-premise CI farm

Vcpkg supports multiple binary cache backends via the X_VCPKG_ASSET_SOURCES environment variable Or CMake variable:

# Use a local binary cache directory
set(VCPKG_BINARY_SOURCES "clear;files,${CMAKE_BINARY_DIR}/vcpkg_cache,readwrite")
# Use an HTTP-based NuGet feed
set(VCPKG_BINARY_SOURCES "clear;nuget,https://my-company-nuget.example.com/v3/index.json,readwrite")

The clear prefix removes all previously configured sources, preventing unintended accumulation of Cache backends across nested CMake projects.

Conan uses a package ID (a hash of the profile settings and options) to identify binary artifacts. Packages are uploaded to a remote and downloaded by consumers:

Terminal window
# Create a package and upload to remote
conan create . -pr:b default -pr:h default
conan upload mylib/1.0.0 -r=my-remote
# Consumer downloads the pre-built package
conan install . --requires mylib/1.0.0 -r=my-remote

If the consumer’s profile hash does not match any uploaded package, Conan falls back to building From source and optionally uploading the result.

Cross-Compilation and Dependency Management

Section titled “Cross-Compilation and Dependency Management”

Cross-compilation introduces additional complexity because the host machine (running the build) and The target machine (running the binary) have different ABIs. Package managers must handle this Explicitly.

Vcpkg uses triplets to specify the target platform:

Terminal window
# Build for ARM64 Linux from x86_64 Linux
vcpkg install fmt --triplet arm64-linux
# Build for Windows from Linux
vcpkg install fmt --triplet x64-windows

The triplet file specifies the target architecture, OS, and CRT. Vcpkg ships with predefined Triplets for common platforms and supports custom triplets.

Conan uses profiles to specify both the host and build platforms:

profiles/arm64-linux
[settings]
os=Linux
arch=armv8
compiler=gcc
compiler.version=13
compiler.libcxx=libstdc++11
build_type=Release
Terminal window
# Build on x86_64, target ARM64
conan install . -pr:h arm64-linux -pr:b x86_64-linux

The -pr:h flag specifies the host profile (target), and -pr:b specifies the build profile (where The compiler runs). This is necessary when cross-compiling because the dependency may need to run a Build step (e.g., code generation) on the host machine.

In a monorepo, dependencies between projects within the same repository are managed differently from External dependencies:

# Internal dependency (same repo)
add_subdirectory(../libs/utils)
# External dependency (package manager)
find_package(fmt CONFIG REQUIRED)

The challenge is that internal dependencies change frequently (with every commit), while external Dependencies change rarely. Build caching for internal dependencies requires a different strategy:

  1. Content-addressable caching: Hash the source files of the internal dependency. If the hash changes, invalidate the cached binary.
  2. CMake package export: Internal libraries can export themselves via install(EXPORT ...) and be consumed via find_package() within the monorepo, providing the same interface as external packages.

This topic covers the essential concepts and techniques related to dependency resolution, including key principles and practical applications.

Key concepts include:

  • core concepts and definitions
  • key principles and frameworks
  • practical applications
  • common techniques and methods
  • evaluation and critical analysis

A thorough understanding of these concepts, combined with regular practice and review, is essential for mastery of this topic.

Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.