014-ADR: Dependency Manager for C/C++¶
Architectural Decision Records (ADR) on adopting a dependency manager for C and C++ projects to manage third-party and internal libraries, ensuring reproducible builds, consistent dependency resolution, and seamless integration with the CMake build system.
1. State¶
- Author(s): Sentenz
- Date: 2026-05-01
- Status: Proposed
2. Context¶
C and C++ projects lack a standardized package manager in the language ecosystem. Managing third-party and internal libraries across multiple platforms, compiler toolchains, and build configurations introduces significant complexity. Dependencies must be resolved consistently, build artifacts must be reproducible, and the toolchain must integrate cleanly with the CMake build system used within the project.
-
Decision Drivers
-
Dependencies and Dev Dependencies
The manager must allow separate declaration of runtime (production) and development-only dependencies (e.g., test frameworks, benchmarking libraries) to keep the production artifact lean.
-
Dependency Resolution
The manager must automatically resolve, download, and configure direct and transitive dependencies, including conflict detection and version negotiation.
-
Dependency Pinning
The manager must support explicit version pinning for all dependencies to eliminate non-deterministic resolution across environments.
-
Transitive Dependencies
The manager must automatically resolve and propagate transitive dependencies, making the full dependency graph visible and manageable.
-
Private and Public Dependencies
The manager must support public registries and private or self-hosted package repositories so that proprietary or internal packages can be managed alongside open-source dependencies.
-
Immutable Lockfiles
The manager must generate a machine-readable lockfile that captures the exact resolved dependency graph to guarantee identical builds across environments.
-
Prebuilt Binary and Source Build
The manager must support consuming prebuilt binaries (e.g., central registry) while allowing source builds when custom build options are required.
-
CMake Integration
The manager must generate CMake-compatible artifacts (e.g.,
find_package) to integrate seamlessly with the existing CMake build system without requiring manual path configuration. -
Cross-Platform Support
The manager must operate across the operating systems (Linux, macOS, Windows), compiler toolchains (GCC, Clang, MSVC), and target platforms used for C and C++ development.
-
CI/CD
The manager must fit automated bootstrap, configure, build, test, and documentation jobs with minimal environment-specific branching.
-
3. Decision¶
3.1. Conan¶
Conan is selected as the C/C++ dependency manager. It separates regular from development dependencies, generates native CMake integration files, produces an immutable lockfile, and supports prebuilt binaries with a fallback to source builds. Its support for private Conan servers (Artifactory, self-hosted) and multi-platform cross-compilation profiles makes it suitable for enterprise projects.
-
Rationale
-
Dependencies and Dev Dependencies
Conan distinguishes production dependencies (
[requires]) from tool/build-time dependencies ([tool_requires]), enabling a clean separation between runtime and development artifacts. -
Dependency Resolution
Conan's SAT-solver-based resolver handles complex version graphs, detects conflicts, and supports version ranges and revisions.
-
Dependency Pinning
Exact versions are declared in
conanfile.txtorconanfile.py, and the resolved package revisions are pinned inconan.lock, making dependency updates explicit and reviewable. -
Transitive Dependencies
Conan resolves and manages the full dependency graph, propagating include paths, link flags, and compiler settings of transitive dependencies.
-
Private and Public Dependencies
Conan supports multiple configurable remotes (ConanCenter for public packages, JFrog Artifactory or a self-hosted Conan server for private packages) with per-remote authentication.
-
Immutable Lockfiles
The committed
conan.lockcan be treated as an immutable CI input until a deliberate lockfile refresh is reviewed and merged. -
Prebuilt Binary and Source Build
Conan supports binary packages from remotes and can still build dependencies from source when the required binary is unavailable.
-
CMake Integration
The
CMakeDepsandCMakeToolchaingenerators produce CMake config files and a toolchain file, allowing standardfind_package()/target_link_libraries()usage with no manual path setup. -
Cross-Platform Support
Conan profile files abstract compiler, OS, architecture, and standard library settings, enabling the same
conanfile.txtto target Linux, macOS, Windows, and cross-compilation scenarios without modification. -
CI/CD
Conan operates fully headlessly, its binary cache is file-system-based and integrates with any CI cache layer (GitHub Actions, GitLab CI, Jenkins). The
--lockfileflag enforces the recorded graph in pipelines.
-
4. Considered¶
4.1. Conan¶
Conan is a decentralized, cross-platform C/C++ package manager focused on reproducible builds and binary distribution.
# conanfile.txt
[requires]
zlib/1.3.1
boost/1.84.0
[tool_requires]
gtest/1.14.0
[generators]
CMakeDeps
CMakeToolchain
-
Pros
-
Dependencies and Dev Dependencies
Supports
[requires]for runtime dependencies and[tool_requires]for build-time and dev-only tools, cleanly separating production from development artifacts. -
Dependency Resolution
Uses a SAT-solver-based graph resolver that handles version ranges, revisions, conflicts, and complex transitive graphs reliably.
-
Dependency Pinning
Version pinning is native;
conanfile.txt/conanfile.pydeclare exact versions, andconan.lockrecords every resolved revision. -
Transitive Dependencies
Propagates all settings, options, and link flags through the full dependency graph automatically.
-
Immutable Lockfiles
conan.lockcaptures the full resolved graph including package revisions, enabling deterministic and reproducible builds. -
Cross-Platform Support
Profile system abstracts OS, compiler, architecture, and standard library, enabling multi-platform and cross-compilation workflows from the same recipe.
-
Prebuilt Binary and Source Build
Transparently downloads prebuilt binaries from a remote or builds from source when no matching binary exists, without requiring recipe changes.
-
CMake Integration
CMakeDepsandCMakeToolchaingenerators produce config files and toolchain files consumed directly byfind_package()andcmake --toolchain. -
Private and Public Dependencies
Supports multiple remotes with per-remote authentication; works with ConanCenter (public), JFrog Artifactory, and self-hosted Conan servers.
-
-
Cons
-
Setup Complexity
Requires installing Python and configuring profiles, remotes, and settings before first use, adding initial onboarding friction compared to header-only solutions.
-
Ecosystem Size
ConanCenter is smaller than vcpkg's curated port catalogue. Packages not available on ConanCenter require custom recipe authoring in Python (
conanfile.py).
-
4.2. vcpkg¶
vcpkg is a Microsoft-maintained C/C++ package manager with Visual Studio and CMake integration.
-
Pros
-
Dependency Resolution
Resolves and installs transitive dependencies automatically from a large curated port catalogue (over 2,000 ports).
-
CMake Integration
Toolchain file (
vcpkg.cmake) enables transparentfind_package()integration; CMake Presets can reference vcpkg's toolchain directly. -
Cross-Platform Support
Supports Linux, macOS, and Windows through a triplet system to express target platform, architecture, and linkage type.
-
Source Build with Optional Binary Caching
vcpkg builds all ports from source by default. Binary caching can be configured to reuse previously compiled artifacts across builds, reducing rebuild overhead in CI environments.
-
Private and Public Dependencies
Supports private registries and overlay ports, allowing internal packages to be managed alongside public ones.
-
-
Cons
-
Dependencies and Dev Dependencies
No distinction between runtime and development-only dependencies within the manifest file.
-
Immutable Lockfiles
vcpkg generates a
vcpkg.jsonmanifest and optionalvcpkg-configuration.jsonbaseline, but does not produce a full lockfile capturing every resolved transitive dependency revision. -
Dependency Pinning
Version pinning requires
overridesentries invcpkg.jsonand a baseline. The mechanism is less ergonomic than Conan's exact pinning with revisions.
-
4.3. CPM.cmake¶
CPM.cmake is a CMake dependency manager built on the CMake FetchContent module.
# CMakeLists.txt
include(cmake/CPM.cmake)
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
CPMAddPackage("gh:google/googletest@1.14.0")
-
Pros
-
CMake Integration
Packages are declared directly in
CMakeLists.txtusingCPMAddPackage(), requiring no external tooling beyond CMake itself. -
Zero Installation
CPM.cmake is a single CMake script; no additional tools need to be installed in the development environment.
-
Simplicity
Minimal configuration overhead for small projects with few dependencies.
-
Cross-Platform Support
Inherits full CMake cross-platform support.
-
Immutable Lockfiles
CPM.cmake can generate a
package-lock.cmakefile that captures the exact resolved URLs and commit hashes of dependencies, enabling reproducible builds.
-
-
Cons
-
Prebuilt Binary and Source Build
CPM.cmake always builds dependencies from source; there is no prebuilt binary registry or binary caching mechanism, leading to significantly longer build times.
-
Dependencies and Dev Dependencies
No formal separation between runtime and development dependencies; all packages are treated uniformly.
-
Transitive Dependencies
Transitive dependency resolution is limited to
CMakeLists.txtpackage declarations; conflicts must be resolved manually.
-
4.4. Git Submodules¶
Git Submodules is a Git-native mechanism for embedding external repositories at a pinned commit SHA as a subdirectory of the parent repository.
# .gitmodules
[submodule "third_party/googletest"]
path = third_party/googletest
url = https://github.com/google/googletest.git
branch = main
-
Pros
-
Dependency Pinning
Each submodule is pinned to an exact commit SHA recorded in the parent repository index, guaranteeing deterministic checkout across all environments without external tooling.
-
Zero Installation
No additional package manager is required; Git's built-in submodule support is the only prerequisite.
-
Private and Public Dependencies
Works with any accessible Git remote (GitHub, GitLab, self-hosted), including private repositories authenticated via SSH or HTTPS tokens.
-
CMake Integration
Submodules expose the full source tree, allowing
add_subdirectory()to build the dependency in-tree with direct access to its CMake targets.
-
-
Cons
-
Dependency Resolution
No automatic resolution of transitive dependencies; each indirect dependency must be identified and added as a separate submodule manually.
-
Dependencies and Dev Dependencies
No mechanism to distinguish runtime from development-only dependencies; all submodules are checked out unconditionally.
-
Prebuilt Binary and Source Build
Submodules always require building from source; there is no binary caching or prebuilt binary distribution mechanism.
-
CI/CD
CI pipelines must run
git submodule update --init --recursivebefore building; sparse or shallow submodule checkouts require additional configuration to avoid cloning full upstream histories.
-
5. Consequences¶
-
Positive
-
Reproducibility
Conan provides fully reproducible builds via
conan.lock, enabling the exact same dependency graph to be reproduced from a singleconanfile.txtand lockfile across all developer machines and CI environments. -
Binary Caching
Conan's binary cache reduces CI build times by reusing prebuilt packages rather than rebuilding dependencies from source on every pipeline run.
-
CMake Integration
Native integration with the CMake build system through
CMakeDepsandCMakeToolchaineliminates manual path configuration and aligns with existing build conventions.
-
-
Negative
-
Setup Overhead
Developers must install Python and the Conan client and must create and maintain a Conan profile for each platform or toolchain target, increasing initial onboarding effort.
-
Custom Recipes
Any new dependency not present on ConanCenter requires authoring a custom Conan recipe in Python (
conanfile.py), which involves learning the domain-specific language (DSL).
-
-
Risks
-
Package Availability
If a required package is not available on ConanCenter and a custom recipe cannot be maintained, an alternative source strategy (vendoring, system package) must be adopted. Mitigation: audit all required dependencies against ConanCenter before adoption and prefer widely used libraries with existing recipes.
-
6. Implementation¶
-
Install
Install the Conan client (
pip install conan) and initialize a default profile (conan profile detect) on all developer machines and CI runners. -
Configure
Define a
conanfile.txtat the repository root declaring[requires]for runtime dependencies and[tool_requires]for development dependencies, withCMakeDepsandCMakeToolchainlisted under[generators]. -
Lockfile
Run
conan install . --lockfile-out=conan.lockto resolve the dependency graph and generate an immutable lockfile; commitconan.lockto version control. -
CMake Integration
Integrate the Conan-generated toolchain file into
CMakePresets.jsonvia theCMAKE_TOOLCHAIN_FILEcache variable so that all CMake presets automatically consume Conan-managed dependencies. -
Validate
Confirm that
cmake --preset <preset>resolves allfind_package()calls without errors and that CI builds reproduce identically using the committed lockfile.
7. References¶
- Conan Center Package Index site.
- Conan Official Documentation site.
- Conan conanfile.txt reference.
- Conan Lockfiles reference.
- Conan CMakeDeps Generator reference.
- Conan CMakeToolchain Generator reference.
- vcpkg Documentation site.
- vcpkg Manifest Mode page.
- GitHub CPM.cmake repository.
- Git Submodules reference.