构建时工具

本章节的内容是关于构建工具的,如果大家没有听说过 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();
    }   
}