ビルド時ツール
このセクションでは、「ビルド時」のツール、つまりクレートのソースコードをコンパイルする前に実行されるコードを扱います。 慣例として、ビルド時のコードは build.rs ファイルに置かれ、一般に「ビルドスクリプト」と呼ばれます。 一般的なユースケースには、Rust のコード生成や、バンドルされた C/C++/asm コードのコンパイルがあります。 詳細については、この件に関する crates.io のドキュメントを参照してください。
バンドルされた C ライブラリを静的にコンパイルしてリンクする
プロジェクトで追加の C、C++、またはアセンブリが必要になるシナリオに対応するため、cc クレートは、バンドルされた C/C++/asm コードを静的ライブラリ(.a)にコンパイルするためのシンプルな API を提供しており、そのライブラリは rustc から静的リンクできます。
次の例には、Rust から使用するいくつかのバンドルされた C コード(src/hello.c)があります。
Rust のソースコードをコンパイルする前に、Cargo.toml で指定された「build」ファイル(build.rs)が実行されます。
cc クレートを使用すると、静的ライブラリファイルが生成されます(この場合は libhello.a。compile docs を参照)。その後、extern ブロックで外部関数シグネチャを宣言することで、Rust からそれを使用できます。
このバンドルされた C は非常に単純なので、cc::Build に渡す必要があるソースファイルは 1 つだけです。
より複雑なビルド要件に対しては、cc::Build は include パスや追加のコンパイラ 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++ ライブラリを静的にコンパイルしてリンクする
バンドルされた 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::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();
}
}