Cargo-based builds

As one aspect of delivering a good Rust–C++ interop experience, CXX turns Cargo into a quite usable build system for C++ projects published as a collection of crates.io packages, including a consistent and frictionless experience #include-ing C++ headers across dependencies.

Canonical setup

CXX's integration with Cargo is handled through the cxx-build crate.

# Cargo.toml
[package]
name = "..."
version = "..."
edition = "2021"

[dependencies]
cxx = "1.0"

[build-dependencies]
cxx-build = "1.0"

The canonical build script is as follows. The indicated line returns a cc::Build instance (from the usual widely used cc crate) on which you can set up any additional source files and compiler flags as normal.

// build.rs

fn main() {
    cxx_build::bridge("src/main.rs")  // returns a cc::Build
        .file("src/demo.cc")
        .std("c++11")
        .compile("cxxbridge-demo");

    println!("cargo:rerun-if-changed=src/main.rs");
    println!("cargo:rerun-if-changed=src/demo.cc");
    println!("cargo:rerun-if-changed=include/demo.h");
}

The rerun-if-changed lines are optional but make it so that Cargo does not spend time recompiling your C++ code when only non-C++ code has changed since the previous Cargo build. By default without any rerun-if-changed, Cargo will re-execute the build script after any file changed in the project.

If stuck, try comparing what you have against the demo/ directory of the CXX GitHub repo, which maintains a working Cargo-based setup for the blobstore tutorial (chapter 3).

Header include paths

With cxx-build, by default your include paths always start with the crate name. This applies to both #include within your C++ code, and include! in the extern "C++" section of your Rust cxx::bridge.

Your crate name is determined by the name entry in Cargo.toml.

For example if your crate is named yourcratename and contains a C++ header file path/to/header.h relative to Cargo.toml, that file will be includable as:

#include "yourcratename/path/to/header.h"

A crate can choose a prefix for its headers that is different from the crate name by modifying CFG.include_prefix from build.rs:

// build.rs

use cxx_build::CFG;

fn main() {
    CFG.include_prefix = "my/project";

    cxx_build::bridge(...)...
}

Subsequently the header located at path/to/header.h would now be includable as:

#include "my/project/path/to/header.h"

The empty string "" is a valid include prefix and will make it possible to have #include "path/to/header.h". However, if your crate is a library, be considerate of possible name collisions that may occur in downstream crates. If using an empty include prefix, you'll want to make sure your headers' local path within the crate is sufficiently namespaced or unique.

Including generated code

If your #[cxx::bridge] module contains an extern "Rust" block i.e. types or functions exposed from Rust to C++, or any shared data structures, the CXX-generated C++ header declaring those things is available using a .rs.h extension on the Rust source file's name.

// the header generated from path/to/lib.rs
#include "yourcratename/path/to/lib.rs.h"

For giggles, it's also available using just a plain .rs extension as if you were including the Rust file directly. Use whichever you find more palatable.

#include "yourcratename/path/to/lib.rs"

Including headers from dependencies

You get to include headers from your dependencies, both handwritten ones contained as .h files in their Cargo package, as well as CXX-generated ones.

It works the same as an include of a local header: use the crate name (or their include_prefix if their crate changed it) followed by the relative path of the header within the crate.

#include "dependencycratename/path/to/their/header.h`

Note that cross-crate imports are only made available between direct dependencies. You must directly depend on the other crate in order to #include its headers; a transitive dependency is not sufficient.

Additionally, headers from a direct dependency are only importable if the dependency's Cargo.toml manifest contains a links key. If not, its headers will not be importable from outside of the same crate. See the links manifest key in the Cargo reference.




Advanced features

The following CFG settings are only relevant to you if you are writing a library that needs to support downstream crates #include-ing its C++ public headers.

Publicly exporting header directories

CFG.exported_header_dirs (vector of absolute paths) defines a set of additional directories from which the current crate, directly dependent crates, and further crates to which this crate's headers are exported (more below) will be able to #include headers.

Adding a directory to exported_header_dirs is similar to adding it to the current build via the cc crate's Build::include, but also makes the directory available to downstream crates that want to #include one of the headers from your crate. If the dir were added only using Build::include, the downstream crate including your header would need to manually add the same directory to their own build as well.

When using exported_header_dirs, your crate must also set a links key for itself in Cargo.toml. See the links manifest key. The reason is that Cargo imposes no ordering on the execution of build scripts without a links key, which means the downstream crate's build script might otherwise execute before yours decides what to put into exported_header_dirs.

Example

One of your crate's headers wants to include a system library, such as #include "Python.h".

// build.rs

use cxx_build::CFG;
use std::path::PathBuf;

fn main() {
    let python3 = pkg_config::probe_library("python3").unwrap();
    let python_include_paths = python3.include_paths.iter().map(PathBuf::as_path);
    CFG.exported_header_dirs.extend(python_include_paths);

    cxx_build::bridge("src/bridge.rs").compile("demo");
}

Example

Your crate wants to rearrange the headers that it exports vs how they're laid out locally inside the crate's source directory.

Suppose the crate as published contains a file at ./include/myheader.h but wants it available to downstream crates as #include "foo/v1/public.h".

// build.rs

use cxx_build::CFG;
use std::path::Path;
use std::{env, fs};

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let headers = Path::new(&out_dir).join("headers");
    CFG.exported_header_dirs.push(&headers);

    // We contain `include/myheader.h` locally, but
    // downstream will use `#include "foo/v1/public.h"`
    let foo = headers.join("foo").join("v1");
    fs::create_dir_all(&foo).unwrap();
    fs::copy("include/myheader.h", foo.join("public.h")).unwrap();

    cxx_build::bridge("src/bridge.rs").compile("demo");
}

Publicly exporting dependencies

CFG.exported_header_prefixes (vector of strings) each refer to the include_prefix of one of your direct dependencies, or a prefix thereof. They describe which of your dependencies participate in your crate's C++ public API, as opposed to private use by your crate's implementation.

As a general rule, if one of your headers #includes something from one of your dependencies, you need to put that dependency's include_prefix into CFG.exported_header_prefixes (or their links key into CFG.exported_header_links; see below). On the other hand if only your C++ implementation files and not your headers are importing from the dependency, you do not export that dependency.

The significance of exported headers is that if downstream code (crate 𝒜) contains an #include of a header from your crate () and your header contains an #include of something from your dependency (𝒞), the exported dependency 𝒞 becomes available during the downstream crate 𝒜's build. Otherwise the downstream crate 𝒜 doesn't know about 𝒞 and wouldn't be able to find what header your header is referring to, and would fail to build.

When using exported_header_prefixes, your crate must also set a links key for itself in Cargo.toml.

Example

Suppose you have a crate with 5 direct dependencies and the include_prefix for each one are:

  • "crate0"
  • "group/api/crate1"
  • "group/api/crate2"
  • "group/api/contrib/crate3"
  • "detail/crate4"

Your header involves types from the first four so we re-export those as part of your public API, while crate4 is only used internally by your cc file not your header, so we do not export:

// build.rs

use cxx_build::CFG;

fn main() {
    CFG.exported_header_prefixes = vec!["crate0", "group/api"];

    cxx_build::bridge("src/bridge.rs")
        .file("src/impl.cc")
        .compile("demo");
}

For more fine grained control, there is CFG.exported_header_links (vector of strings) which each refer to the links attribute (the links manifest key) of one of your crate's direct dependencies.

This achieves an equivalent result to CFG.exported_header_prefixes by re-exporting a C++ dependency as part of your crate's public API, except with finer control for cases when multiple crates might be sharing the same include_prefix and you'd like to export some but not others. Links attributes are guaranteed to be unique identifiers by Cargo.

When using exported_header_links, your crate must also set a links key for itself in Cargo.toml.

Example

// build.rs

use cxx_build::CFG;

fn main() {
    CFG.exported_header_links.push("git2");

    cxx_build::bridge("src/bridge.rs").compile("demo");
}