0

Giá trị có tính phân biệt cao (high-cardinality) cho các flags build trong Rust

Khi làm một bản demo về WebAssembly và Kubernetes, tôi muốn tạo ba binary khác nhau từ cùng một đoạn mã:

  • Native: biên dịch mã Rust thành mã máy gốc để làm chuẩn so sánh.
  • Embed: biên dịch thành WebAssembly và dùng image Docker của WasmEdge làm image nền.
  • Runtime: biên dịch thành WebAssembly, sử dụng một image nền rỗng (scratch), và thiết lập runtime khi chạy mã.

Đoạn mã là một máy chủ HTTP với một endpoint duy nhất. Để phục vụ mục đích demo, tôi muốn endpoint này trả về kiểu của image đang chạy.

curl localhost:3000/get
{"source": "native", "data": {}}

Ý tưởng là có một codebase duy nhất có thể biên dịch thành mã gốc hoặc WebAssembly. Tôi giải quyết yêu cầu này bằng cách sử dụng flag biên dịch cfg:

#[cfg(flavor = "native")]
const FLAVOR: &str = "native";

#[cfg(flavor = "embed")]
const FLAVOR: &str = "embed";

#[cfg(flavor = "runtime")]
const FLAVOR: &str = "runtime";

// Use FLAVOR later in the returned HTTP response

Build Scripts trong Rust

Một số package cần biên dịch mã không phải Rust (ví dụ thư viện C). Một số khác cần liên kết với thư viện C có sẵn hoặc phải được build từ mã nguồn. Một số cần thực hiện sinh mã trước khi build (ví dụ bộ sinh parser).

Cargo không thay thế các công cụ chuyên dụng cho các tác vụ này, nhưng có thể tích hợp với chúng thông qua build script. Đặt một file tên build.rs ở thư mục gốc của package sẽ khiến Cargo biên dịch và thực thi script đó ngay trước khi build package.

Thử nghiệm với build script đơn giản

Tạo một file build.rs ở thư mục gốc của crate:

fn main() {
    println!("Hello from build script!");
}
cargo build
Compiling high-cardinality-cfg-compile v0.1.0 (/Users/nico/projects/private/high-cardinality-cfg-compile)
 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s

Kết quả build xong nhưng không có log nào hiển thị — vì theo tài liệu:

Output của build script sẽ bị ẩn khi biên dịch bình thường. Nếu bạn muốn thấy output, hãy dùng flag -vv.

Chạy lại:

cargo build --vv

Bây giờ log sẽ hiển thị như mong đợi:

...
[high-cardinality-cfg-compile 0.1.0] Hello from build script!
...

Tạo file mã từ script thay vì hard-code

Tôi sẽ dùng build script để tạo một file Rust chứa giá trị flavor được truyền vào từ metadata trong Cargo.toml.

Vì build script chạy trước khi biên dịch nên nó không có quyền truy cập vào các flag cfg. Thay vào đó, tôi sẽ truyền giá trị này thông qua package.metadata trong Cargo.toml:

[package.metadata]
flavor = "foobar"

Sau đó thêm dependency vào build-dependencies:

[build-dependencies]
cargo_metadata = "0.19.1"

Build script:

fn main() {
    let metadata = MetadataCommand::new()                                         //1
        .exec()
        .expect("Failed to fetch cargo metadata");

    let package = metadata.root_package().expect("No root package found");        //2

    let flavor = package                                                          //3
        .metadata
        .get("flavor")
        .and_then(|f| f.as_str())
        .expect("flavor is not set in Cargo.toml under [package.metadata]");

    let dest_path = Path::new("src").join("flavor.rs");                           //4

    fs::write(&dest_path, format!("pub const FLAVOR: &str = \"{}\";\n", flavor))  //5
        .expect("Failed to write flavor.rs");

    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:warning=FLAVOR written to {}", dest_path.display());
}

Giải thích:

  • Đọc metadata từ Cargo.toml
  • Xác định package chính
  • Lấy giá trị flavor
  • Tạo file src/flavor.rs
  • Ghi nội dung hằng số vào file

Sử dụng trong mã chính

Trong mã nguồn:

mod flavor;

fn main() {
    println!("Hello from flavor {}", flavor::FLAVOR);
}

Khi chạy chương trình:

cargo run
Hello from flavor foobar

Kết luận

Trong bài viết này, tôi đã trình bày cách sử dụng các tham số build có tính phân biệt cao bằng metadata trong Cargo.toml. Tôi cũng có thể dùng biến môi trường, nhưng mục đích là để học cách sử dụng metadata.

File build.rs là một thủ thuật rất hữu ích để đạt được các mục tiêu không thể thực hiện được bằng tính năng tiêu chuẩn của Cargo.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí