Skip to content

[question] How to package a C++ library with all dependencies for non-Conan consumers #19355

@sachatt

Description

@sachatt

What is your question?

Context

I am developing a C++ library (e.g. MyLib) that will be consumed by a React Native iOS application. The consumer cannot use Conan because React Native for iOS relies on CocoaPods/Swift Package Manager, which does not support Conan integration. Therefore, I need to create a fully self-contained package that includes all necessary headers and binaries.

The Problem

My library (MyLib) depends on third-party libraries (e.g., boost, nlohmann_json, etc.) and exposes their types in my public API headers in certain contexts. I need to distribute a pre-compiled binary package that includes:

  • My library, MyLib, headers and binaries
  • All necessary third-party headers and binaries used in MyLib's public API

In addition even if react supported conan I still rather package these third-party libs together to avoid any ABI mismatches that may not directly appear or even fail, and avoid doing something like adding a package_info for requirements.

Example of the concern:

// MyLib.h (Public Header)
#pragma once
#include <fmt/core.h>

namespace mylib {
    struct LogContext {
        fmt::memory_buffer buffer;  // Size/alignment depends on how fmt was compiled
    };

    void process(LogContext& ctx);
}

If the consumer were using Conan, they might rebuild fmt with different compiler flags, causing ABI mismatches with my pre-compiled binary. Since memory layout of fmt::memory_buffer could vary based on compilation options, this would lead to undefined behavior.

However, since my consumer cannot use Conan at all, I need to bundle everything together into a self-contained package.

Attempted Solutions

Option 1: Using conan deploy with custom deployer

  1. Created a separate conanfile.py with only MyLib as a requirement
from pathlib import Path
from conan import ConanFile


class DeployConan(ConanFile):
    settings = "os", "compiler", "build_type", "arch"

    def set_version(self):
        # Read version from parent directory's VERSION file
        version_file = Path(self.recipe_folder).parent / "VERSION"
        self.my_lib_version = version_file.read_text().strip()

    def requirements(self):
        self.requires(f"MyLib/{self.my_lib_version}")
  1. This allowed Conan to resolve and fetch my library within the final output as well as all transitive dependencies
  2. Implemented a custom deploy.py script to selectively copy only the dependencies exposed in public headers
from pathlib import Path
import shutil

PACKAGES_TO_DEPLOY = [
    "MyLib",
    "boost",
    "range-v3",
    "fmt",
    "nlohmann_json",
]

def deploy(graph, output_folder, **kwargs):
    """Custom deployer that only copies specified packages."""
    output_path = Path(output_folder)
    deployed = set()

    # Use graph.nodes to access ALL packages (including hidden/transitive)
    for node in graph.nodes:
        if node.ref is None:
            continue

        pkg_folder = node.conanfile.package_folder
        if pkg_folder is None:
            continue

        pkg_name = node.ref.name

        if pkg_name in PACKAGES_TO_DEPLOY:
            src = Path(pkg_folder)
            dest = output_path / pkg_name

            if dest.exists():
                shutil.rmtree(dest)

            shutil.copytree(src, dest)
            deployed.add(pkg_name)
            print(f"Deployed: {pkg_name} -> {dest}")

    # Verify all required packages were deployed
    missing = set(PACKAGES_TO_DEPLOY) - deployed
    if missing:
        raise RuntimeError(f"Failed to deploy packages (not found in graph): {', '.join(sorted(missing))}")
  1. Deploy specifying275 the custom deploy.py script shown above with a local folder

  2. Problem: This approach did not include header-only libraries, and we couldn't find a clean (non-hacky) way to handle them. It would however include properly all build libraries that were not header only.

Option 2: Modify MyLib's conanfile.py package method

  1. Instead of using conan deploy, modify MyLib's own conanfile.py
  2. In the package() method, copy third-party headers and libraries directly
    def package(self):
        cmake = CMake(self)
        cmake.install()
        self._copy_third_party_headers()
        self._copy_third_party_libraries()

  1. Advantage: In this context, we have direct access to dependency information
  2. Question: Is this the recommended approach?

Have you read the CONTRIBUTING guide?

  • I've read the CONTRIBUTING guide

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions