Инструменты времени компиляции

Этот раздел охватывает инструменты времени компиляции, или код который запускается до момента компиляции исходного кода крейта. По соглашению, код исполняемый во время сборки находится в файле build.rs и он часто называется "скриптом сборки". Распространённые случаи использования включают генерацию Rust кода и компиляцию связанного C/C++/asm кода. Смотрите документацию по этому вопросу на crates.io для дополнительной информации.

Компиляция и статическая компоновка с идущей в комплекте библиотекой на C

cc-badge cat-development-tools-badge

Чтобы покрыть сценарии, где требуется использовать дополнительный код на C, C++ или ассемблере, есть крейт cc, который предлагает простой API для компиляции включённого в проект кода на C/C++/asm в статические библиотеки (.a), которые затем могут быть статически скомпонованы rustc.

Следующий пример содержит некоторый код на C (src/hello.c) который будет использован в Rust коде. Перед компиляцией кода на Rust файл запускается специальный "build" файл (build.rs), определённый в Cargo.toml. Используя крейт cc будет создана статическая библиотека (в данном случае libhello.a, смотрите compile docs), которая затем может быть использована из кода на Rust с помощью декларации сигнатуры внешней функции в блоке extern.

Поскольку включённый код на C очень простой, нужен только один файл с исходным кодом нужен для передачи в cc::Build. В случае более сложных сценариев, cc::Build предлагает полный набор возможностей для определения include путей и флагов flag для внешнего компилятора.

Cargo.toml

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

[dependencies]
error-chain = "0.11"

build.rs

extern crate cc;

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

#[macro_use] extern crate 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++

cc-badge cat-development-tools-badge

Компоновка с библиотекой на C++ очень похожа на компоновку с библиотекой на C. Самые главные два отличия это когда вам нужно компилировать и компоновать библиотеку на C++ вы должны определить компилятор C++ с помощью специального метода cpp(true) и также нужно предотвратить искажение имён компилятором C++ путём добавления extern "C" секции на самый верхний уровень в исходном коде C++.

Cargo.toml

[package]
...
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs

extern crate cc;

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 с использованием особых директив

cc-badge cat-development-tools-badge

На самом деле довольно просто собрать код на C с особыми директивами используя cc::Build::define. Этот метод принимает значение Option, таким образом делая возможным определить такие директивы, как, скажем, #define APP_NAME "foo" или там #define WELCOME (можно передать None для определения директивы без значения). Этот пример собирает файл на C с динамически определёнными директивами, установленными в build.rs и печатает "Welcome to foo - version 1.0.2" после запуска. Cargo устанавливает некоторые переменные окружения, которые также могут быть полезны для динамического определения директив.

Cargo.toml

[package]
...
version = "1.0.2"
build = "build.rs"

[build-dependencies]
cc = "1"

build.rs

extern crate cc;

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();
    }   
}