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.
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.
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:
A crate can choose a prefix for its headers that is different from the crate
name by modifying CFG.include_prefix
from build.rs:
Subsequently the header located at path/to/header.h
would now be includable
as:
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.
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.
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.
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"
.
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"
.
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 #include
s 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:
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