Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ビルド時ツール

このセクションでは、「ビルド時」のツール、つまりクレートのソースコードをコンパイルする前に実行されるコードを扱います。 慣例として、ビルド時のコードは build.rs ファイルに置かれ、一般に「ビルドスクリプト」と呼ばれます。 一般的なユースケースには、Rust のコード生成や、バンドルされた C/C++/asm コードのコンパイルがあります。 詳細については、この件に関する crates.io のドキュメントを参照してください。

バンドルされた C ライブラリを静的にコンパイルしてリンクする

cc-badge cat-development-tools-badge

プロジェクトで追加の C、C++、またはアセンブリが必要になるシナリオに対応するため、cc クレートは、バンドルされた C/C++/asm コードを静的ライブラリ(.a)にコンパイルするためのシンプルな API を提供しており、そのライブラリは rustc から静的リンクできます。

次の例には、Rust から使用するいくつかのバンドルされた C コード(src/hello.c)があります。 Rust のソースコードをコンパイルする前に、Cargo.toml で指定された「build」ファイル(build.rs)が実行されます。 cc クレートを使用すると、静的ライブラリファイルが生成されます(この場合は libhello.acompile docs を参照)。その後、extern ブロックで外部関数シグネチャを宣言することで、Rust からそれを使用できます。

このバンドルされた C は非常に単純なので、cc::Build に渡す必要があるソースファイルは 1 つだけです。 より複雑なビルド要件に対しては、cc::Buildinclude パスや追加のコンパイラ flag を指定するための、ひととおりのビルダーメソッドを提供しています。

Cargo.toml

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

[build-dependencies]
cc = "1"

[dependencies]
anyhow = "1"

build.rs

fn main() {
    cc::Build::new()
        .file("src/hello.c")
        .compile("hello");   // `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 anyhow::Result;
use std::ffi::CString;
use std::os::raw::c_char;

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++ ライブラリをコンパイルして静的にリンクする際の 2 つの主な違いは、ビルダーメソッド cpp(true) を使って C++ コンパイラを指定することと、C++ ソースファイルの先頭に extern "C" セクションを追加して C++ コンパイラによる名前修飾を防ぐことです。

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 ライブラリをコンパイルする

cc-badge cat-development-tools-badge

cc::Build::define を使うと、カスタム定義付きでバンドルされた C コードを簡単にビルドできます。 このメソッドは Option 値を受け取るため、#define APP_NAME "foo" のような定義や #define WELCOME のような定義も作成できます(値を持たない定義の場合は値として None を渡します)。この例では、 build.rs で設定した動的な定義を使ってバンドルされた C ファイルをビルドし、実行すると “Welcome to foo - version 1.0.2” と出力します。Cargo は、カスタム定義によっては役立つことがあるいくつかの 環境変数 を設定します。

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