构建时工具
本章节的内容是关于构建工具的,如果大家没有听说过 build.rs
文件,强烈建议先看看这里了解下何为构建工具。
编译并静态链接一个 C 库
cc 包能帮助我们更好地跟 C/C++/汇编进行交互:它提供了简单的 API 可以将外部的库编译成静态库( .a ),然后通过 rustc
进行静态链接。
下面的例子中,我们将在 Rust 代码中使用 C 的代码: src/hello.c。在开始编译 Rust 的项目代码前,build.rs
构建脚本将先被执行。通过 cc 包,一个静态的库可以被生成( libhello.a ),然后该库将被 Rust的代码所使用:通过 extern
声明外部函数签名的方式来使用。
由于例子中的 C 代码很简单,因此只需要将一个文件传递给 cc::Build。如果大家需要更复杂的构建,cc::Build
还提供了通过 include 来包含路径的方式,以及额外的编译标志( flags )。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
[dependencies]
error-chain = "0.11"
build.rs
fn main() { cc::Build::new() .file("src/hello.c") .compile("hello"); // outputs `libhello.a` }
src/hello.c
#include <stdio.h>
void hello() {
printf("Hello from C!\n");
}
void greet(const char* name) {
printf("Hello, %s!\n", name);
}
src/main.rs
use error_chain::error_chain; use std::ffi::CString; use std::os::raw::c_char; error_chain! { foreign_links { NulError(::std::ffi::NulError); Io(::std::io::Error); } } fn prompt(s: &str) -> Result<String> { use std::io::Write; print!("{}", s); std::io::stdout().flush()?; let mut input = String::new(); std::io::stdin().read_line(&mut input)?; Ok(input.trim().to_string()) } extern { fn hello(); fn greet(name: *const c_char); } fn main() -> Result<()> { unsafe { hello() } let name = prompt("What's your name? ")?; let c_name = CString::new(name)?; unsafe { greet(c_name.as_ptr()) } Ok(()) }
编译并静态链接一个 C++ 库
链接到 C++ 库跟之前的方式非常相似。主要的区别在于链接到 C++ 库时,你需要通过构建方法 cpp(true) 来指定一个 C++ 编译器,然后在 C++ 的代码顶部添加 extern "C"
来阻止 C++ 编译器对库名进行名称重整( name mangling )。
Cargo.toml
[package]
...
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() { cc::Build::new() .cpp(true) .file("src/foo.cpp") .compile("foo"); }
src/foo.cpp
extern "C" {
int multiply(int x, int y);
}
int multiply(int x, int y) {
return x*y;
}
src/main.rs
extern { fn multiply(x : i32, y : i32) -> i32; } fn main(){ unsafe { println!("{}", multiply(5,7)); } }
为 C 库创建自定义的 define
cc::Build::define 可以让我们使用自定义的 define 来构建 C 库。
以下示例在构建脚本 build.rs
中动态定义了一个 define,然后在运行时打印出 Welcome to foo - version 1.0.2。Cargo 会设置一些环境变量,它们对于自定义的 define 会有所帮助。
Cargo.toml
[package]
...
version = "1.0.2"
build = "build.rs"
[build-dependencies]
cc = "1"
build.rs
fn main() { cc::Build::new() .define("APP_NAME", "\"foo\"") .define("VERSION", format!("\"{}\"", env!("CARGO_PKG_VERSION")).as_str()) .define("WELCOME", None) .file("src/foo.c") .compile("foo"); }
src/foo.c
#include <stdio.h>
void print_app_info() {
#ifdef WELCOME
printf("Welcome to ");
#endif
printf("%s - version %s\n", APP_NAME, VERSION);
}
src/main.rs
extern { fn print_app_info(); } fn main(){ unsafe { print_app_info(); } }