aboutsummaryrefslogtreecommitdiff
path: root/book/src/build/cargo.md
diff options
context:
space:
mode:
Diffstat (limited to 'book/src/build/cargo.md')
-rw-r--r--book/src/build/cargo.md306
1 files changed, 306 insertions, 0 deletions
diff --git a/book/src/build/cargo.md b/book/src/build/cargo.md
new file mode 100644
index 00000000..82ccfb50
--- /dev/null
+++ b/book/src/build/cargo.md
@@ -0,0 +1,306 @@
+{{#title Cargo-based setup — Rust ♡ C++}}
+# 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.
+
+[cxx-build]: https://docs.rs/cxx-build
+
+```toml,hidelines
+## Cargo.toml
+# [package]
+# name = "..."
+# version = "..."
+# edition = "2018"
+
+[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.
+
+[`cc::Build`]: https://docs.rs/cc/1.0/cc/struct.Build.html
+
+```rust,noplayground
+// build.rs
+
+fn main() {
+ cxx_build::bridge("src/main.rs") // returns a cc::Build
+ .file("src/demo.cc")
+ .flag_if_supported("-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:
+
+```cpp
+#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`][CFG]** from build.rs:
+
+[CFG]: https://docs.rs/cxx-build/*/cxx_build/static.CFG.html
+
+```rust,noplayground
+// 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:
+
+```cpp
+#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.
+
+```cpp
+// 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.
+
+```cpp
+#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.
+
+```cpp
+#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][links]* in the Cargo reference.
+
+[links]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key
+
+<br><br><br>
+
+# 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`][CFG]** (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.
+
+[`Build::include`]: https://docs.rs/cc/1/cc/struct.Build.html#method.include
+
+When using `exported_header_dirs`, your crate must also set a `links` key for
+itself in Cargo.toml. See [*the `links` manifest key*][links]. 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"`.
+
+```rust,noplayground
+// 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"`.
+
+```rust,noplayground
+// 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`][CFG]** (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:
+
+```rust,noplayground
+// 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");
+}
+```
+
+<br>
+
+For more fine grained control, there is **[`CFG.exported_header_links`][CFG]**
+(vector of strings) which each refer to the `links` attribute ([*the `links`
+manifest key*][links]) 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
+
+```rust,noplayground
+// build.rs
+
+use cxx_build::CFG;
+
+fn main() {
+ CFG.exported_header_links.push("git2");
+
+ cxx_build::bridge("src/bridge.rs").compile("demo");
+}
+```