一、规范设置
在Rust项目中,为了与C++代码进行互操作,cxx crate提供了一种便捷的方式。通过cxx-build crate,Cargo可以被扩展为一个C++构建系统。这在Cargo.toml文件中通过设置依赖来实现:
rust">[dependencies]
cxx = "1.0"
[build-dependencies]
cxx-build = "1.0"
build.rs文件是Cargo的构建脚本,它在这里用于配置cxx-build。以下是一个示例脚本,展示了如何桥接Rust和C++代码,并添加额外的C++源文件以及指定C++标准:
rust">// build.rs
fn main() {
cxx_build::bridge("src/main.rs") // 返回 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");
}
二、头文件默认根路径
在cxx-build中,C++头文件默认路径是crate。如果您的crate名称为yourcratename,并且头文件位于path/to/header.h,则在C++代码中应这样包含它:
#include "yourcratename/path/to/header.h"
如果您希望为头文件选择不同的前缀,可以通过修改build.rs中的CFG.include_prefix来实现。
rust">// build.rs
use cxx_build::CFG;
fn main() {
CFG.include_prefix = "my/project";
cxx_build::bridge(...)...
}
随后,位于path/to/header.h的标头现在可以包含为:
rust">#include "my/project/path/to/header.h"
三、包括生成的代码
如果你的#[cxx::bridge]模块包含一个extern “Rust"块,即从Rust向C++或任何共享数据结构公开的类型或函数,那么CXX生成的C++头文件可以使用Rust源文件名称添加”.h"获得。 如:
rust">// 头文件从路径path/to/lib.rs生产:
#include "yourcratename/path/to/lib.rs.h"
四、包括依赖项中的头文件
您可以手写头文件(.h文件)包含在Cargo包中,也可以包含CXX生成的头文件。
它的工作原理与本地头文件的include相同,如:
`#include "dependencycratename/path/to/their/header.h``
请注意,跨crate导入仅在直接依赖项之间可用。传递依赖性是不支持的。
此外,只有当依赖项的Cargo.toml清单包含链接键时,直接依赖项的头文件才可导入。否则,其头文件将无法从同一crate外部导入。
五、高级功能
以下CFG设置仅在您编写需要支持下游crates #include(包括其C++公共头文件)的库时与您相关。
1、公开导出头文件目录 (CFG.exported_header_dirs)
CFG.exported_header_dirs(绝对路径向量)定义了一组额外的目录,从这些目录中,当前crate直接依赖的crate以及导出此crate头文件的其他将能够#include headers。
向exported_header_dirs添加目录类似于通过cc crate的build::include将其添加到当前构建中,但也使该目录可用于希望#包含crate中某个头的下游crate。如果仅使用Build::include添加目录,则包含您的标头的下游crate也需要手动将相同的目录添加到自己的构建中。
使用exported_header_dirs时,您的板条箱还必须在Cargo.toml中为自己设置一个链接键。原因是Cargo对没有链接键的构建脚本的执行没有顺序,这意味着下游机箱的构建脚本可能会在您决定将什么放入exported_header_dirs之前执行。
例子
rust">// 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");
}
例子
你的crate想要重新排列它导出的头文件,以及它们在crate源代码目录中的本地布局。
假设发布的crate包含一个文件./include/myheader.h,但希望它作为#include "foo/v1/public.h"可供下游crates使用。
// 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");
}
公开导出依赖项
CFG.exported_header_prefixes(字符串向量)分别引用您的一个直接依赖项的include_pefixe或其前缀。它们描述了您的哪些依赖项参与了crate的C++公共API,而不是crate实现的私有使用。
一般来说,如果您的某个header#包含来自某个依赖项的内容,则需要将该依赖项的include_prefix放入CFG.exported_header_prefix中(或将其链接键放入CFG.export ed_header_links中;见下文)。另一方面,如果只有C++实现文件而不是头文件从依赖项导入,则不会导出该依赖项。
导出标头的意义在于,如果下游代码(crate𝒜)包含来自crate(")的标头的#include,并且您的标头包含来自依赖项(⻜)中的某些内容的#incleed,导出的依赖项⻜在下游机箱𝒜的构建过程中变得可用。否则,下游板条箱𝒜不知道⻜,也无法找到您的标头所指的标头,从而无法构建。
使用exported_header_prefixes时,您的板条箱还必须在Cargo.toml中为自己设置一个链接键。
例子
假设你有一个有5个直接依赖项的crate,每个依赖项的include_prefix都是:
"crate0"
"group/api/crate1"
"group/api/crate2"
"group/api/contrib/crate3"
"detail/crate4"
您的头包含前四个类型,因此我们将这些类型作为公共API的一部分重新导出,而crate4仅由您的cc文件内部使用,而不是您的头文件,因此我们不导出:
rust">// 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");
}
为了实现更细粒度的控制,有CFG.exported_header_links(字符串向量),每个链接都引用机箱直接依赖项之一的links属性(links manifest键)。
这实现了与CFG.exported_header_prefixes相同的结果,方法是将C++依赖项重新导出为机箱的公共API的一部分,但对于多个机箱可能共享相同的include_prefixes并且您希望导出一些但不导出其他机箱的情况,可以进行更精细的控制。Cargo保证链接属性是唯一的标识符。
使用exported_header_links时,您的板条箱还必须在Cargo.toml中为自己设置一个链接键。
例子
rust">// build.rs
use cxx_build::CFG;
fn main() {
CFG.exported_header_links.push("git2");
cxx_build::bridge("src/bridge.rs").compile("demo");
}